<?php
declare(strict_types=1);
namespace ParagonIE\HPKE\KDF;
use ParagonIE\HPKE\{
Hash,
SymmetricKey
};
use ParagonIE\HPKE\Interfaces\{
KDFInterface,
SymmetricKeyInterface
};
use function hash, hash_hmac;
class HKDF implements KDFInterface
{
private int $digestLength;
public function __construct(
public readonly Hash $hash
) {
$this->digestLength = strlen(
hash($this->hash->value, '', true)
);
}
public function getHashLength(): int
{
return $this->digestLength;
}
public function getKdfId(): string
{
return match ($this->hash) {
Hash::Sha256 => "\x00\x01",
Hash::Sha384 => "\x00\x02",
Hash::Sha512 => "\x00\x03",
};
}
public function deriveBytes(
#[\SensitiveParameter]
string|SymmetricKeyInterface $ikm,
#[\SensitiveParameter]
string $info = '',
#[\SensitiveParameter]
string $salt = '',
int $length = 32
): string {
return hash_hkdf(
$this->hash->value,
$ikm instanceof SymmetricKeyInterface ? $ikm->bytes : $ikm,
$length,
$info,
$salt
);
}
public function deriveSymmetricKey(
#[\SensitiveParameter]
string|SymmetricKeyInterface $ikm,
#[\SensitiveParameter]
string $info = '',
#[\SensitiveParameter]
string $salt = ''
): SymmetricKeyInterface {
return new SymmetricKey($this->deriveBytes($ikm, $info, $salt));
}
public function extract(
#[\SensitiveParameter]
string|SymmetricKeyInterface $ikm,
#[\SensitiveParameter]
?string $salt = null
): string {
if (is_null($salt)) {
$salt = str_repeat("\0", $this->digestLength);
}
return hash_hmac(
$this->hash->value,
$ikm instanceof SymmetricKeyInterface ? $ikm->bytes : $ikm,
$salt,
true
);
}
public function expand(
#[\SensitiveParameter]
string|SymmetricKeyInterface $prk,
#[\SensitiveParameter]
string $info,
#[\SensitiveParameter]
int $length
): string {
$lastBlock = '';
$t = '';
for ($index = 1; strlen($t) < $length; ++$index) {
$lastBlock = hash_hmac(
$this->hash->value,
$lastBlock . $info . pack('C', $index),
$prk instanceof SymmetricKeyInterface ? $prk->bytes : $prk,
true
);
$t .= $lastBlock;
}
return substr($t, 0, $length);
}
/**
* @param string $suiteId
* @param string|SymmetricKeyInterface $ikm
* @param string $label
* @param ?string $salt
* @return string
*/
public function labeledExtract(
string $suiteId,
#[\SensitiveParameter]
string|SymmetricKeyInterface $ikm,
#[\SensitiveParameter]
string $label,
#[\SensitiveParameter]
?string $salt = null
): string {
$labeled_ikm = "HPKE-v1" .
$suiteId .
$label .
($ikm instanceof SymmetricKeyInterface ? $ikm->bytes : $ikm);
return $this->extract($labeled_ikm, $salt);
}
/**
* @param string $suiteId
* @param string|SymmetricKeyInterface $prk
* @param string $label
* @param string $info
* @param int $length
* @return string
*/
public function labeledExpand(
string $suiteId,
#[\SensitiveParameter] string|SymmetricKeyInterface $prk,
#[\SensitiveParameter] string $label,
#[\SensitiveParameter] string $info,
int $length
): string {
$labeled_info = pack('n', $length) .
'HPKE-v1' .
$suiteId .
$label .
$info;
return $this->expand($prk, $labeled_info, $length);
}
/**
* @param string $suiteId
* @param string $dh
* @param string $kemContext
* @param int $length
* @return string
*/
public function extractAndExpand(
string $suiteId,
#[\SensitiveParameter] string $dh,
string $kemContext,
int $length
): string {
return $this->labeledExpand(
suiteId: $suiteId,
prk: $this->labeledExtract(
suiteId: $suiteId,
ikm: $dh,
label: 'eae_prk'
),
label: 'shared_secret',
info: $kemContext,
length: $length
);
}
}
|