In this example, our client is going to sign the data with a special key before sending it to the server. When the server receives the data it will verify the signature with the same key. If the verification was successful then the client is considered as trustworthy. It uses Sodium on a PHP 7.2+ machine. Note: Read comments in the code.


Class


You might need to add "ext-sodium": "*" to your composer.json file. Also read what exactly the relevant function does here.


/**
* 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'];
}
}

Test


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);
}
}