使用PHP的请求信息签名和验证(附示例)

129 阅读1分钟

在这个例子中,我们的客户将在把数据发送给服务器之前用一个特殊的密钥签名。当服务器收到数据时,它将用相同的密钥验证签名。如果验证成功,那么客户端被认为是值得信赖的。它在PHP7.2以上的机器上使用Sodium注意:阅读代码中的注释。

类别

你可能需要在你的composer.json 文件中添加"ext-sodium": "*" 。同时阅读这里的相关函数到底是做什么的。

/**
 * Request signing without encrypting the data to prevent request tampering.
 * You have to use same "key" sign and verify data.
 *
 * Use bin2hex() on signature before sending.
 * Use hex2bin() on signature before verifying.
 */
class Sign
{
    /**
     * This is what sender computer does.
     *
     * @param string $plainData This is what sender computer will send to receiver computer
     * @param string $key The key to sign data
     *
     * @return array
     */
    public function sign(string $plainData, string $key): array
    {
        $mac = sodium_crypto_auth($plainData, $key);

        return [
            'signature' => $mac,
            'data' => $plainData,
        ];
    }

    /**
     * This is what receiver computer does.
     *
     * @param array $dataReceived This comes from the sender computer
     * @param string $key The key to verify data
     *
     * @return string
     */
    public function verify(array $dataReceived, string $key): string
    {
        if (!sodium_crypto_auth_verify($dataReceived['signature'], $dataReceived['data'], $key)) {
            sodium_memzero($key);

            throw new RuntimeException('Tempered data!');
        }

        return $dataReceived['data'];
    }
}

测试

class SignTest extends TestCase
{
    public function testSign(): void
    {
        $key = random_bytes(SODIUM_CRYPTO_AUTH_BYTES);

        $dataToBeSent = (new Sign())->sign('inanzzz', $key);

        $this->assertIsString($dataToBeSent['signature']);
        $this->assertIsString($dataToBeSent['data']);
    }

    public function testVerifyFailsOnWrongKey(): void
    {
        // Sender
        $key = random_bytes(SODIUM_CRYPTO_AUTH_BYTES);

        $dataToBeSent = (new Sign())->sign('inanzzz', $key);

        // Receiver
        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage('Tempered data!');

        $key = random_bytes(SODIUM_CRYPTO_AUTH_BYTES);

        (new Sign())->verify($dataToBeSent, $key);
    }

    public function testVerifySucceeds(): void
    {
        // Sender
        $key = random_bytes(SODIUM_CRYPTO_AUTH_BYTES);

        $dataToBeSent = (new Sign())->sign('inanzzz', $key);

        // Receiver
        $result = (new Sign())->verify($dataToBeSent, $key);

        $this->assertSame('inanzzz', $result);
    }
}