// kit 01
PQC Toolkit
Production-ready post-quantum crypto. Pick stack, pick primitive, copy the code.
$ pqc_kem.php
L3
1 <?php 2 // Requires liboqs FFI binding (see downloads/pqc-kit-php). 3 // PHP has no pure-PHP ML-KEM yet; this wraps liboqs via FFI. 4 declare(strict_types=1); 5 6 final class PqcKem 7 { 8 private \FFI $oqs; 9 10 public function __construct(string $libPath = '/usr/local/lib/liboqs.so') 11 { 12 $hdr = <<<H 13 typedef struct OQS_KEM OQS_KEM; 14 OQS_KEM* OQS_KEM_new(const char* method_name); 15 int OQS_KEM_keypair(OQS_KEM* kem, uint8_t* public_key, uint8_t* secret_key); 16 int OQS_KEM_encaps(OQS_KEM* kem, uint8_t* ciphertext, uint8_t* shared_secret, const uint8_t* public_key); 17 int OQS_KEM_decaps(OQS_KEM* kem, uint8_t* shared_secret, const uint8_t* ciphertext, const uint8_t* secret_key); 18 void OQS_KEM_free(OQS_KEM* kem); 19 H; 20 $this->oqs = \FFI::cdef($hdr, $libPath); 21 } 22 23 public function keygen(): array 24 { 25 $kem = $this->oqs->OQS_KEM_new('ML-KEM-768'); 26 $pub = \FFI::new('uint8_t[1184]'); 27 $sec = \FFI::new('uint8_t[2400]'); 28 $this->oqs->OQS_KEM_keypair($kem, $pub, $sec); 29 return ['pub' => \FFI::string($pub, 1184), 'sec' => \FFI::string($sec, 2400)]; 30 } 31 32 public function encaps(string $pub): array 33 { 34 $kem = $this->oqs->OQS_KEM_new('ML-KEM-768'); 35 $ct = \FFI::new('uint8_t[1088]'); 36 $ss = \FFI::new('uint8_t[32]'); 37 $this->oqs->OQS_KEM_encaps($kem, $ct, $ss, $pub); 38 return ['ct' => \FFI::string($ct, 1088), 'ss' => \FFI::string($ss, 32)]; 39 } 40 } 41 42 function pqc_combine(string $ss_pq, string $ss_x, string $transcript): string 43 { 44 return sodium_crypto_generichash($ss_x . $ss_pq . $transcript, '', 32); 45 }
KEY SIZE
1184 B pub
SIG / CT
1088 B ct
NIST LEVEL
L3
NOTES
PHP has no native PQ; the kit ships an FFI binding to liboqs.
- liboqs 0.10+ ships ML-KEM under both old (Kyber) and new (ML-KEM) names.
- FFI has overhead; pool the
OQS_KEM_newallocations in hot paths. - For shared hosting with no liboqs, fall back to a sidecar service via Unix socket.