<?php
declare(strict_types=1);
namespace ParagonIE\HPKE\Tests\KDF;
use ParagonIE\HPKE\AEAD\{
AES128GCM,
AES256GCM,
ChaCha20Poly1305
};
use ParagonIE\HPKE\Hash;
use ParagonIE\HPKE\KDF\HKDF;
use ParagonIE\HPKE\KEM\{
DHKEM\Curve,
DiffieHellmanKEM
};
use PHPUnit\Framework\Attributes\{
CoversClass,
DataProvider
};
use PHPUnit\Framework\TestCase;
use SodiumException;
#[CoversClass(HKDF::class)]
class HKDFTest extends TestCase
{
/**
* Omits the SHA-1 test vectors
*/
public static function rfc5869TestVectors(): array
{
$sha256 = new HKDF(Hash::Sha256);
return [
'Test Case 1' => [
$sha256,
'077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5',
'3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865',
'0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b',
'f0f1f2f3f4f5f6f7f8f9',
42,
'000102030405060708090a0b0c',
],
'Test Case 2' => [
$sha256,
'06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244',
'b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87',
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f',
'b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff',
82,
'606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf'
],
'Test Case 3' => [
$sha256,
'19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04',
'8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8',
'0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b',
'',
42,
null
]
];
}
public static function rfc9180(): array
{
$sha256 = new HKDF(Hash::Sha256);
$sha512 = new HKDF(Hash::Sha512);
// KEMs
$kem_x25519_id = (new DiffieHellmanKEM(Curve::X25519, $sha256))->getKemId();
$kem_p256_id = (new DiffieHellmanKEM(Curve::NistP256, $sha256))->getKemId();
$kem_p521_id = (new DiffieHellmanKEM(Curve::NistP521, $sha512))->getKemId();
// KDFs
$kdf_sha256_id = $sha256->getKdfId();
$kdf_sha512_id = $sha512->getKdfId();
// AEADs
$aes128 = (new AES128GCM());
$aes256 = (new AES256GCM());
$chapoly = (new ChaCha20Poly1305());
// Return the test cases:
return [
'DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM' => [
$sha256,
"HPKE" . $kem_x25519_id . $kdf_sha256_id . $aes128->getAeadId(),
[
'nk' =>
$aes128->keyLength(),
'mode' =>
'00',
'info' =>
'4f6465206f6e2061204772656369616e2055726e',
'shared_secret' =>
'fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc',
'key_schedule_context' =>
'00725611c9d98c07c03f60095cd32d400d8347d45ed67097bbad50fc56da742d07cb6cffde367bb0565ba28bb02c90744a20f5ef37f30523526106f637abb05449',
'secret' =>
'12fff91991e93b48de37e7daddb52981084bd8aa64289c3788471d9a9712f397',
'key' =>
'4531685d41d65f03dc48f6b8302c05b0',
'base_nonce' =>
'56d890e5accaaf011cff4b7d',
'exporter_secret' =>
'45ff1c2e220db587171952c0592d5f5ebe103f1561a2614e38f2ffd47e99e3f8',
]
],
'DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305' => [
$sha256,
"HPKE" . $kem_x25519_id . $kdf_sha256_id . $chapoly->getAeadId(),
[
'nk' =>
$chapoly->keyLength(),
'mode' =>
'00',
'info' =>
'4f6465206f6e2061204772656369616e2055726e',
'shared_secret' =>
'0bbe78490412b4bbea4812666f7916932b828bba79942424abb65244930d69a7',
'key_schedule_context' =>
'00431df6cd95e11ff49d7013563baf7f11588c75a6611ee2a4404a49306ae4cfc5b69c5718a60cc5876c358d3f7fc31ddb598503f67be58ea1e798c0bb19eb9796',
'secret' =>
'5b9cd775e64b437a2335cf499361b2e0d5e444d5cb41a8a53336d8fe402282c6',
'key' =>
'ad2744de8e17f4ebba575b3f5f5a8fa1f69c2a07f6e7500bc60ca6e3e3ec1c91',
'base_nonce' =>
'5c4d98150661b848853b547f',
'exporter_secret' =>
'a3b010d4994890e2c6968a36f64470d3c824c8f5029942feb11e7a74b2921922',
]
],
'DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-128-GCM' => [
$sha256,
"HPKE" . $kem_p256_id . $kdf_sha256_id . $aes128->getAeadId(),
[
'nk' =>
$aes128->keyLength(),
'mode' =>
'00',
'info' =>
'4f6465206f6e2061204772656369616e2055726e',
'shared_secret' =>
'c0d26aeab536609a572b07695d933b589dcf363ff9d93c93adea537aeabb8cb8',
'key_schedule_context' =>
'00b88d4e6d91759e65e87c470e8b9141113e9ad5f0c8ceefc1e088c82e6980500798e486f9c9c09c9b5c753ac72d6005de254c607d1b534ed11d493ae1c1d9ac85',
'secret' =>
'2eb7b6bf138f6b5aff857414a058a3f1750054a9ba1f72c2cf0684a6f20b10e1',
'key' =>
'868c066ef58aae6dc589b6cfdd18f97e',
'base_nonce' =>
'4e0bc5018beba4bf004cca59',
'exporter_secret' =>
'14ad94af484a7ad3ef40e9f3be99ecc6fa9036df9d4920548424df127ee0d99f',
]
],
'DHKEM(P-256, HKDF-SHA256), HKDF-SHA512, AES-128-GCM' => [
$sha512,
"HPKE" . $kem_p256_id . $kdf_sha512_id . $aes128->getAeadId(),
[
'nk' =>
$aes128->keyLength(),
'mode' =>
'00',
'info' =>
'4f6465206f6e2061204772656369616e2055726e',
'shared_secret' =>
'02f584736390fc93f5b4ad039826a3fa08e9911bd1215a3db8e8791ba533cafd',
'key_schedule_context' =>
'005b8a3617af7789ee716e7911c7e77f84cdc4cc46e60fb7e19e4059f9aeadc00585e26874d1ddde76e551a7679cd47168c466f6e1f705cc9374c192778a34fcd5ca221d77e229a9d11b654de7942d685069c633b2362ce3b3d8ea4891c9a2a87a4eb7cdb289ba5e2ecbf8cd2c8498bb4a383dc021454d70d46fcbbad1252ef4f9',
'secret' =>
'0c7acdab61693f936c4c1256c78e7be30eebfe466812f9cc49f0b58dc970328dfc03ea359be0250a471b1635a193d2dfa8cb23c90aa2e25025b892a725353eeb',
'key' =>
'090ca96e5f8aa02b69fac360da50ddf9',
'base_nonce' =>
'9c995e621bf9a20c5ca45546',
'exporter_secret' =>
'4a7abb2ac43e6553f129b2c5750a7e82d149a76ed56dc342d7bca61e26d494f4855dff0d0165f27ce57756f7f16baca006539bb8e4518987ba610480ac03efa8',
]
],
'DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305' => [
$sha256,
"HPKE" . $kem_p256_id . $kdf_sha256_id . $chapoly->getAeadId(),
[
'nk' =>
$chapoly->keyLength(),
'mode' =>
'00',
'info' =>
'4f6465206f6e2061204772656369616e2055726e',
'shared_secret' =>
'806520f82ef0b03c823b7fc524b6b55a088f566b9751b89551c170f4113bd850',
'key_schedule_context' =>
'00b738cd703db7b4106e93b4621e9a19c89c838e55964240e5d3f331aaf8b0d58b2e986ea1c671b61cf45eec134dac0bae58ec6f63e790b1400b47c33038b0269c',
'secret' =>
'fe891101629aa355aad68eff3cc5170d057eca0c7573f6575e91f9783e1d4506',
'key' =>
'a8f45490a92a3b04d1dbf6cf2c3939ad8bfc9bfcb97c04bffe116730c9dfe3fc',
'base_nonce' =>
'726b4390ed2209809f58c693',
'exporter_secret' =>
'4f9bd9b3a8db7d7c3a5b9d44fdc1f6e37d5d77689ade5ec44a7242016e6aa205',
]
],
'DHKEM(P-521, HKDF-SHA512), HKDF-SHA512, AES-256-GCM' => [
$sha512,
"HPKE" . $kem_p521_id . $kdf_sha512_id . $aes256->getAeadId(),
[
'nk' =>
$aes256->keyLength(),
'mode' =>
'00',
'info' =>
'4f6465206f6e2061204772656369616e2055726e',
'shared_secret' =>
'776ab421302f6eff7d7cb5cb1adaea0cd50872c71c2d63c30c4f1d5e43653336fef33b103c67e7a98add2d3b66e2fda95b5b2a667aa9dac7e59cc1d46d30e818',
'key_schedule_context' =>
'0083a27c5b2358ab4dae1b2f5d8f57f10ccccc822a473326f543f239a70aee46347324e84e02d7651a10d08fb3dda739d22d50c53fbfa8122baacd0f9ae5913072ef45baa1f3a4b169e141feb957e48d03f28c837d8904c3d6775308c3d3faa75dd64adfa44e1a1141edf9349959b8f8e5291cbdc56f62b0ed6527d692e85b09a4',
'secret' =>
'49fd9f53b0f93732555b2054edfdc0e3101000d75df714b98ce5aa295a37f1b18dfa86a1c37286d805d3ea09a20b72f93c21e83955a1f01eb7c5eead563d21e7',
'key' =>
'751e346ce8f0ddb2305c8a2a85c70d5cf559c53093656be636b9406d4d7d1b70',
'base_nonce' =>
'55ff7a7d739c69f44b25447b',
'exporter_secret' =>
'e4ff9dfbc732a2b9c75823763c5ccc954a2c0648fc6de80a58581252d0ee3215388a4455e69086b50b87eb28c169a52f42e71de4ca61c920e7bd24c95cc3f992',
]
],
];
}
/**
* @throws SodiumException
*/
#[DataProvider('rfc5869TestVectors')]
public function testRfc5869(
HKDF $kdf,
string $prk_hex,
string $okm_hex,
string $ikm_hex,
string $info_hex,
int $length,
?string $salt_hex = null
): void {
$ikm = sodium_hex2bin($ikm_hex);
$info = sodium_hex2bin($info_hex);
$salt = is_null($salt_hex) ? '' : sodium_hex2bin($salt_hex);
// Test the combined mode
$output = $kdf->deriveBytes($ikm, $info, $salt, $length);
$this->assertSame($okm_hex, sodium_bin2hex($output));
// Now let's test extract/expand separately
$prk = $kdf->extract($ikm, $salt);
$this->assertSame($prk_hex, sodium_bin2hex($prk), 'HKDF-extract');
$result = $kdf->expand($prk, $info, $length);
$this->assertSame($okm_hex, sodium_bin2hex($result), 'HKDF-expand');
}
/**
* @throws SodiumException
*/
#[DataProvider('rfc9180')]
public function testRfc9180(
HKDF $kdf,
string $suiteId,
array $testVectorsInHex = []
): void {
// We aren't testing it at this level.
$info = sodium_hex2bin($testVectorsInHex['info']);
$mode = sodium_hex2bin($testVectorsInHex['mode']);
$info_hash = $kdf->labeledExtract(
suiteId: $suiteId,
ikm: $info,
label: 'info_hash'
);
$psk_id_hash = $kdf->labeledExtract(
suiteId: $suiteId,
ikm: '',
label: 'psk_id_hash'
);
$this->assertSame(
$testVectorsInHex['key_schedule_context'],
sodium_bin2hex($mode . $psk_id_hash . $info_hash),
'info hashing'
);
$shared_secret = sodium_hex2bin($testVectorsInHex['shared_secret']);
$actual_secret = $kdf->labeledExtract(
suiteId: $suiteId,
ikm: '',
label: 'secret',
salt: $shared_secret
);
$this->assertSame(
$testVectorsInHex['secret'],
sodium_bin2hex($actual_secret),
'secret'
);
$nk = $testVectorsInHex['nk'];
$secret = sodium_hex2bin($testVectorsInHex['secret']);
$key_schedule_context = sodium_hex2bin($testVectorsInHex['key_schedule_context']);
$actual_key = $kdf->labeledExpand(
suiteId: $suiteId,
prk: $secret,
label: 'key',
info: $key_schedule_context,
length: $nk
);
$this->assertSame(
$testVectorsInHex['key'],
sodium_bin2hex($actual_key),
'key'
);
$actual_base_nonce = $kdf->labeledExpand(
suiteId: $suiteId,
prk: $secret,
label: 'base_nonce',
info: $key_schedule_context,
length: 12 // This is pretty universal for all AEADs in scope
);
$this->assertSame(
$testVectorsInHex['base_nonce'],
sodium_bin2hex($actual_base_nonce),
'base_nonce'
);
$actual_exp = $kdf->labeledExpand(
suiteId: $suiteId,
prk: $secret,
label: 'exp',
info: $key_schedule_context,
length: $kdf->getHashLength()
);
$this->assertSame(
$testVectorsInHex['exporter_secret'],
sodium_bin2hex($actual_exp),
'exporter_secret'
);
}
}
|