PHP Classes

File: src/KEM/DiffieHellmanKEM.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   HPKE PHP   src/KEM/DiffieHellmanKEM.php   Download  
File: src/KEM/DiffieHellmanKEM.php
Role: Class source
Content type: text/plain
Description: Class source
Class: HPKE PHP
Encrypt and decrypt data using hybrid public keys
Author: By
Last change:
Date: 8 days ago
Size: 9,669 bytes
 

Contents

Class file image Download
<?php
declare(strict_types=1);
namespace
ParagonIE\HPKE\KEM;

use
Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
use
Mdanter\Ecc\Exception\InsecureCurveException;
use
Mdanter\Ecc\Serializer\Point\UncompressedPointSerializer;
use
ParagonIE\EasyECC\EasyECC;
use
ParagonIE\EasyECC\Exception\NotImplementedException;
use
ParagonIE\HPKE\{
   
HPKE,
   
HPKEException,
   
SymmetricKey,
   
Util
};
use
ParagonIE\HPKE\Interfaces\{
   
DecapsKeyInterface,
   
EncapsKeyInterface,
   
KDFInterface,
   
KemInterface,
   
SymmetricKeyInterface
};
use
ParagonIE\HPKE\KEM\DHKEM\{
   
Curve,
   
DecapsKey,
   
EncapsKey
};
use
SodiumException;
use
TypeError;

class
DiffieHellmanKEM implements KemInterface
{
    protected ?
HPKE $hpke = null;

    public function
__construct(
        public
readonly Curve $curve,
        public
readonly KDFInterface $kdf,
    ) {}

   
/**
     * This is called Npk in the HPKE spec..
     *
     * @return int
     */
   
public function getPublicKeyLength(): int
   
{
        return
match ($this->curve) {
           
Curve::Secp256k1, Curve::NistP256 => 65,
           
Curve::NistP384 => 97,
           
Curve::NistP521 => 133,
           
Curve::X25519 => 32
       
};
    }

   
/**
     * Thisi s called Nsec in the HPKE spec.
     */
   
public function getSecretLength(): int
   
{
        return
match ($this->curve) {
           
Curve::Secp256k1, Curve::NistP256, Curve::X25519 => 32,
           
Curve::NistP384 => 48,
           
Curve::NistP521 => 64
       
};
    }

   
/**
     * Thisi s called Nsk in the HPKE spec.
     */
   
public function getSecretKeyLength(): int
   
{
        return
match ($this->curve) {
           
Curve::Secp256k1, Curve::NistP256, Curve::X25519 => 32,
           
Curve::NistP384 => 48,
           
Curve::NistP521 => 66
       
};
    }

   
/**
     * This is called Nenc in the HPKE spec.
     *
     * @return int
     */
   
public function getHeaderLength(): int
   
{
        return
$this->curve->encapsKeyLength();
    }

    public function
getKemId(): string
   
{
       
// https://www.iana.org/assignments/hpke/hpke.xhtml
       
return match ($this->curve) {
           
Curve::X25519 => "\x00\x20",
           
Curve::NistP256 => "\x00\x10",
           
Curve::NistP384 => "\x00\x11",
           
Curve::NistP521 => "\x00\x12",
           
Curve::Secp256k1 => "\x00\x16"
       
};
    }

   
/**
     * This is different from the HPKE Suite ID
     *
     * @return string
     */
   
public function getSuiteId(): string
   
{
        return
'KEM' . $this->getKemId();
    }

   
/**
     * Stubbed out so it can be overridden in unit tests
     *
     * @throws NotImplementedException
     * @throws SodiumException
     */
   
public function generatePrivateKey(EasyECC $ecc): string|PrivateKeyInterface
   
{
        if (
$ecc->getCurveName() === 'sodium') {
            return
sodium_crypto_box_keypair();
        }
        return
$ecc->generatePrivateKey();
    }

   
/**
     * Generate a keypair for Key Encapsulation.
     *
     * @return array{0: DecapsKeyInterface, 1: EncapsKeyInterface}
     *
     * @throws HPKEException
     * @throws NotImplementedException
     * @throws SodiumException
     */
   
public function generateKeys(): array
    {
       
$ecc = $this->curve->getEasyECC();
       
$keypair = $this->generatePrivateKey($ecc);

       
// Special handling for libsodium
       
if (is_string($keypair)) {
            return [
                new
DecapsKey($this->curve, sodium_crypto_box_secretkey($keypair)),
                new
EncapsKey($this->curve, sodium_crypto_box_publickey($keypair))
            ];
        }

       
// Handle all other elliptic curves
       
$sk = Util::gmpToBytes($keypair->getSecret(), $this->curve->decapsKeyLength());
       
$ser = (new UncompressedPointSerializer());
       
$pk = sodium_hex2bin($ser->serialize($keypair->getPublicKey()->getPoint()));
        return [
            new
DecapsKey($this->curve, $sk),
            new
EncapsKey($this->curve, $pk)
        ];
    }

   
/**
     * @param EncapsKey $encapsKey
     * @return array{0: SymmetricKeyInterface, 1: string}
     *
     * @throws HPKEException
     * @throws InsecureCurveException
     * @throws NotImplementedException
     * @throws SodiumException
     */
   
public function encapsulate(EncapsKeyInterface $encapsKey): array
    {
        if (
is_null($this->hpke)) {
            throw new
HPKEException('HPKE not injected');
        }
        if (!
hash_equals($encapsKey->curve->name, $this->curve->name)) {
            throw new
TypeError('Encapsulation key must be meant for this curve');
        }
        [
$ephSecret, $ephPublic] = $this->generateKeys();
        if (!
$ephSecret instanceof DecapsKey || !$ephPublic instanceof EncapsKey) {
            throw new
TypeError('Ephemeral key pair error');
        }
       
$dh = $this->scalarMult($ephSecret, $encapsKey);
       
$enc = $ephPublic->serializeForHeader();
       
$kem_context = $enc . $encapsKey->serializeForHeader();
       
$secret_length = $this->getSecretLength();
       
$shared_secret = new SymmetricKey(
           
$this->kdf->extractAndExpand(
               
suiteId: $this->getSuiteId(),
               
dh: $dh,
               
kemContext: $kem_context,
               
length: $secret_length
           
)
        );
        return [
$shared_secret, $enc];
    }

   
/**
     * @param DecapsKey $decapsKey
     * @param string $enc
     * @return SymmetricKeyInterface
     *
     * @throws HPKEException
     * @throws InsecureCurveException
     * @throws SodiumException
     */
   
public function decapsulate(
       
DecapsKeyInterface $decapsKey,
       
string $enc
   
): SymmetricKeyInterface {
        if (
is_null($this->hpke)) {
            throw new
HPKEException('HPKE not injected');
        }
        if (!
hash_equals($decapsKey->curve->name, $this->curve->name)) {
            throw new
TypeError('Encapsulation key must be meant for this curve');
        }
       
$ephPublic = new EncapsKey($decapsKey->curve, $enc);
       
$dh = $this->scalarMult($decapsKey, $ephPublic);
       
$kem_context = $enc . $decapsKey->getEncapsKey()->bytes;
       
$secret_length = $this->getSecretLength();
        return new
SymmetricKey(
           
$this->kdf->extractAndExpand($this->getSuiteId(), $dh, $kem_context, $secret_length)
        );
    }

   
/**
     * @param EncapsKey $encapsKey
     * @param DecapsKey $decapsKey
     * @return array{0: SymmetricKeyInterface, 1: string}
     *
     * @throws HPKEException
     * @throws NotImplementedException
     * @throws SodiumException
     * @throws InsecureCurveException
     */
   
public function authEncaps(EncapsKeyInterface $encapsKey, DecapsKeyInterface $decapsKey): array
    {
        if (
is_null($this->hpke)) {
            throw new
HPKEException('HPKE not injected');
        }
        if (!
hash_equals($encapsKey->curve->name, $this->curve->name)) {
            throw new
TypeError('Encapsulation key must be meant for this curve');
        }
        [
$ephSecret, $ephPublic] = $this->generateKeys();
        if (!
$ephSecret instanceof DecapsKey || !$ephPublic instanceof EncapsKey) {
            throw new
TypeError('Ephemeral key pair error');
        }
       
$dh = $this->scalarMult($ephSecret, $encapsKey) . $this->scalarMult($decapsKey, $encapsKey);
       
$enc = $ephPublic->serializeForHeader();
       
$kem_context = $enc .
           
$encapsKey->serializeForHeader() .
           
$decapsKey->getEncapsKey()->serializeForHeader();
       
$secret_length = $this->curve->secretLength();
       
$shared_secret = new SymmetricKey($this->kdf->extractAndExpand(
           
$this->getSuiteId(), $dh, $kem_context, $secret_length
       
));
        return [
$shared_secret, $enc];
    }

   
/**
     * @param DecapsKey $decapsKey
     * @param EncapsKey $encapsKey
     * @param string $enc
     * @return SymmetricKeyInterface
     *
     * @throws HPKEException
     * @throws InsecureCurveException
     * @throws SodiumException
     */
   
public function authDecaps(
       
DecapsKeyInterface $decapsKey,
       
EncapsKeyInterface $encapsKey,
       
string $enc
   
): SymmetricKeyInterface {
        if (
is_null($this->hpke)) {
            throw new
HPKEException('HPKE not injected');
        }
        if (!
hash_equals($decapsKey->curve->name, $this->curve->name)) {
            throw new
TypeError('Encapsulation key must be meant for this curve');
        }
       
$ephPublic = new EncapsKey($decapsKey->curve, $enc);
       
$dh = $this->scalarMult($decapsKey, $ephPublic) . $this->scalarMult($decapsKey, $encapsKey);
       
$kem_context = $enc .
           
$encapsKey->serializeForHeader() .
           
$decapsKey->getEncapsKey()->serializeForHeader();
       
$secret_length = $this->curve->secretLength();
        return new
SymmetricKey(
           
$this->kdf->extractAndExpand($this->getSuiteId(), $dh, $kem_context, $secret_length)
        );
    }

   
/**
     * Inject a reference to the HPKE class.
     *
     * @param HPKE $hpke
     * @return static
     */
   
public function withHPKE(HPKE $hpke): static
    {
       
$this->hpke = $hpke;
        return
$this;
    }

   
/**
     * @param DecapsKey $decapsKey
     * @param EncapsKey $encapsKey
     * @return string
     *
     * @throws HPKEException
     * @throws InsecureCurveException
     * @throws SodiumException
     */
   
protected function scalarMult(DecapsKey $decapsKey, EncapsKey $encapsKey): string
   
{
        return
$this->curve->getEasyECC()->scalarmult(
           
$decapsKey->toPrivateKey(),
           
$encapsKey->toPublicKey()
        );
    }
}