<?php
declare(strict_types=1);
namespace Acme\Algorithm;
use Base64Url\Base64Url;
use Jose\Component\Core\JWK;
use const Sodium\CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES;
/**
* This algorithm is a custom algorithm that use the ChaCha20 + Poly 1305 with a 192 bits nonce (IETF variant).
*/
final class ChaCha20Poly1305IETF implements KeyEncryption //The algorithm acts as a Key Encryption algorithm
{
/**
* {@inheritdoc}
*/
public function name(): string
{
return 'ChaCha20+Poly1305+IETF'; // The name of our algorithm. This name will be used in our JWE headers
}
/**
* {@inheritdoc}
*/
public function allowedKeyTypes(): array
{
return ['oct']; // Key types for this algorithm are octet keys
}
/**
* {@inheritdoc}
*/
public function encryptKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string
{
$this->checkKey($key); // We check the key
$kek = Base64Url::decode($key->get('k')); // We retrieve the secret
$nonce = random_bytes(CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES); // We create a nonce
$additionalHeader['nonce'] = Base64Url::encode($nonce); // We add the nonce to the header
return sodium_crypto_aead_chacha20poly1305_ietf_encrypt($cek, '', $nonce, $kek); // We return the encrypted CEK
}
/**
* {@inheritdoc}
*/
public function decryptKey(JWK $key, string $encrypted_cek, array $header): string
{
$this->checkKey($key); // We check the key
$this->checkAdditionalParameters($header); // We verify the nonce is in the headers
$nonce = Base64Url::decode($header['nonce']); // We retrieve the nonce
$kek = Base64Url::decode($key->get('k')); // an the secret
$decrypted = sodium_crypto_aead_chacha20poly1305_ietf_decrypt($encrypted_cek, '', $nonce, $kek); // We try to decrypt the CEK
if (false === $decrypted) { // If it fails we throw an exception
throw new \RuntimeException('Unable to decrypt.');
}
return $decrypted; // Otherwise we return the decrypted CEK
}
/**
* @param JWK $key
*/
protected function checkKey(JWK $key)
{
if (!in_array($key->get('kty'), $this->allowedKeyTypes())) {
throw new \InvalidArgumentException('Wrong key type.');
}
if (!$key->has('k')) {
throw new \InvalidArgumentException('The key parameter "k" is missing.');
}
}
/**
* @param array $header
*/
protected function checkAdditionalParameters(array $header)
{
foreach (['nonce'] as $k) {
if (!array_key_exists($k, $header)) {
throw new \InvalidArgumentException(sprintf('Parameter "%s" is missing.', $k));
}
}
}
/**
* {@inheritdoc}
*/
public function getKeyManagementMode(): string
{
return self::MODE_ENCRYPT; //Key Management Mode is 'enc'.
}
}