Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post-Quantum Algorithms Support Draft #1920

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
113 changes: 113 additions & 0 deletions README.md
@@ -1,3 +1,116 @@
# Post-Quantum support
- adds the functionality to handle FALCON, SPHINCS+ and Dilithium5 operations
- currently, only Dilithium5 was tested in https://github.com/Muzosh/Post-Quantum-Authentication-On-The-Web

**Before usage, call the following function atleast once if you are using OIDs from [here](https://github.com/open-quantum-safe/oqs-provider/blob/main/ALGORITHMS.md#oids)**
```php
ASN1::loadOIDs([
"dilithium2" => "1.3.6.1.4.1.2.267.7.4.4",
"p256_dilithium2" => "1.3.9999.2.7.1",
"rsa3072_dilithium2" => "1.3.9999.2.7.2",
"dilithium3" => "1.3.6.1.4.1.2.267.7.6.5",
"p384_dilithium3" => "1.3.9999.2.7.3",
"dilithium5" => "1.3.6.1.4.1.2.267.7.8.7",
"p521_dilithium5" => "1.3.9999.2.7.4",
"dilithium2_aes" => "1.3.6.1.4.1.2.267.11.4.4",
"p256_dilithium2_aes" => "1.3.9999.2.11.1",
"rsa3072_dilithium2_aes" => "1.3.9999.2.11.2",
"dilithium3_aes" => "1.3.6.1.4.1.2.267.11.6.5",
"p384_dilithium3_aes" => "1.3.9999.2.11.3",
"dilithium5_aes" => "1.3.6.1.4.1.2.267.11.8.7",
"p521_dilithium5_aes" => "1.3.9999.2.11.4",
"falcon512" => "1.3.9999.3.1",
"p256_falcon512" => "1.3.9999.3.2",
"rsa3072_falcon512" => "1.3.9999.3.3",
"falcon1024" => "1.3.9999.3.4",
"p521_falcon1024" => "1.3.9999.3.5",
"sphincsharaka128frobust" => "1.3.9999.6.1.1",
"p256_sphincsharaka128frobust" => "1.3.9999.6.1.2",
"rsa3072_sphincsharaka128frobust" => "1.3.9999.6.1.3",
"sphincsharaka128fsimple" => "1.3.9999.6.1.4",
"p256_sphincsharaka128fsimple" => "1.3.9999.6.1.5",
"rsa3072_sphincsharaka128fsimple" => "1.3.9999.6.1.6",
"sphincsharaka128srobust" => "1.3.9999.6.1.7",
"p256_sphincsharaka128srobust" => "1.3.9999.6.1.8",
"rsa3072_sphincsharaka128srobust" => "1.3.9999.6.1.9",
"sphincsharaka128ssimple" => "1.3.9999.6.1.10",
"p256_sphincsharaka128ssimple" => "1.3.9999.6.1.11",
"rsa3072_sphincsharaka128ssimple" => "1.3.9999.6.1.12",
"sphincsharaka192frobust" => "1.3.9999.6.2.1",
"p384_sphincsharaka192frobust" => "1.3.9999.6.2.2",
"sphincsharaka192fsimple" => "1.3.9999.6.2.3",
"p384_sphincsharaka192fsimple" => "1.3.9999.6.2.4",
"sphincsharaka192srobust" => "1.3.9999.6.2.5",
"p384_sphincsharaka192srobust" => "1.3.9999.6.2.6",
"sphincsharaka192ssimple" => "1.3.9999.6.2.7",
"p384_sphincsharaka192ssimple" => "1.3.9999.6.2.8",
"sphincsharaka256frobust" => "1.3.9999.6.3.1",
"p521_sphincsharaka256frobust" => "1.3.9999.6.3.2",
"sphincsharaka256fsimple" => "1.3.9999.6.3.3",
"p521_sphincsharaka256fsimple" => "1.3.9999.6.3.4",
"sphincsharaka256srobust" => "1.3.9999.6.3.5",
"p521_sphincsharaka256srobust" => "1.3.9999.6.3.6",
"sphincsharaka256ssimple" => "1.3.9999.6.3.7",
"p521_sphincsharaka256ssimple" => "1.3.9999.6.3.8",
"sphincssha256128frobust" => "1.3.9999.6.4.1",
"p256_sphincssha256128frobust" => "1.3.9999.6.4.2",
"rsa3072_sphincssha256128frobust" => "1.3.9999.6.4.3",
"sphincssha256128fsimple" => "1.3.9999.6.4.4",
"p256_sphincssha256128fsimple" => "1.3.9999.6.4.5",
"rsa3072_sphincssha256128fsimple" => "1.3.9999.6.4.6",
"sphincssha256128srobust" => "1.3.9999.6.4.7",
"p256_sphincssha256128srobust" => "1.3.9999.6.4.8",
"rsa3072_sphincssha256128srobust" => "1.3.9999.6.4.9",
"sphincssha256128ssimple" => "1.3.9999.6.4.10",
"p256_sphincssha256128ssimple" => "1.3.9999.6.4.11",
"rsa3072_sphincssha256128ssimple" => "1.3.9999.6.4.12",
"sphincssha256192frobust" => "1.3.9999.6.5.1",
"p384_sphincssha256192frobust" => "1.3.9999.6.5.2",
"sphincssha256192fsimple" => "1.3.9999.6.5.3",
"p384_sphincssha256192fsimple" => "1.3.9999.6.5.4",
"sphincssha256192srobust" => "1.3.9999.6.5.5",
"p384_sphincssha256192srobust" => "1.3.9999.6.5.6",
"sphincssha256192ssimple" => "1.3.9999.6.5.7",
"p384_sphincssha256192ssimple" => "1.3.9999.6.5.8",
"sphincssha256256frobust" => "1.3.9999.6.6.1",
"p521_sphincssha256256frobust" => "1.3.9999.6.6.2",
"sphincssha256256fsimple" => "1.3.9999.6.6.3",
"p521_sphincssha256256fsimple" => "1.3.9999.6.6.4",
"sphincssha256256srobust" => "1.3.9999.6.6.5",
"p521_sphincssha256256srobust" => "1.3.9999.6.6.6",
"sphincssha256256ssimple" => "1.3.9999.6.6.7",
"p521_sphincssha256256ssimple" => "1.3.9999.6.6.8",
"sphincsshake256128frobust" => "1.3.9999.6.7.1",
"p256_sphincsshake256128frobust" => "1.3.9999.6.7.2",
"rsa3072_sphincsshake256128frobust" => "1.3.9999.6.7.3",
"sphincsshake256128fsimple" => "1.3.9999.6.7.4",
"p256_sphincsshake256128fsimple" => "1.3.9999.6.7.5",
"rsa3072_sphincsshake256128fsimple" => "1.3.9999.6.7.6",
"sphincsshake256128srobust" => "1.3.9999.6.7.7",
"p256_sphincsshake256128srobust" => "1.3.9999.6.7.8",
"rsa3072_sphincsshake256128srobust" => "1.3.9999.6.7.9",
"sphincsshake256128ssimple" => "1.3.9999.6.7.10",
"p256_sphincsshake256128ssimple" => "1.3.9999.6.7.11",
"rsa3072_sphincsshake256128ssimple" => "1.3.9999.6.7.12",
"sphincsshake256192frobust" => "1.3.9999.6.8.1",
"p384_sphincsshake256192frobust" => "1.3.9999.6.8.2",
"sphincsshake256192fsimple" => "1.3.9999.6.8.3",
"p384_sphincsshake256192fsimple" => "1.3.9999.6.8.4",
"sphincsshake256192srobust" => "1.3.9999.6.8.5",
"p384_sphincsshake256192srobust" => "1.3.9999.6.8.6",
"sphincsshake256192ssimple" => "1.3.9999.6.8.7",
"p384_sphincsshake256192ssimple" => "1.3.9999.6.8.8",
"sphincsshake256256frobust" => "1.3.9999.6.9.1",
"p521_sphincsshake256256frobust" => "1.3.9999.6.9.2",
"sphincsshake256256fsimple" => "1.3.9999.6.9.3",
"p521_sphincsshake256256fsimple" => "1.3.9999.6.9.4",
"sphincsshake256256srobust" => "1.3.9999.6.9.5",
"p521_sphincsshake256256srobust" => "1.3.9999.6.9.6",
"sphincsshake256256ssimple" => "1.3.9999.6.9.7",
"p521_sphincsshake256256ssimple" => "1.3.9999.6.9.8",
]);
```

# phpseclib - PHP Secure Communications Library

[![CI Status](https://github.com/phpseclib/phpseclib/actions/workflows/ci.yml/badge.svg?branch=master&event=push "CI Status")](https://github.com/phpseclib/phpseclib/actions/workflows/ci.yml?query=branch%3Amaster)
Expand Down
4 changes: 2 additions & 2 deletions phpseclib/Crypt/Common/AsymmetricKey.php
100644 → 100755
Expand Up @@ -116,7 +116,7 @@ protected static function initialize_static_variables(): void
}

self::loadPlugins('Keys');
if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH') {
if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH' && static::ALGORITHM != 'DILITHIUM') {
self::loadPlugins('Signature');
}
}
Expand Down Expand Up @@ -213,7 +213,7 @@ public static function loadParameters($key): AsymmetricKey
*
* @return static
*/
public static function loadFormat(string $type, string $key, ?string $password = null): AsymmetricKey
public static function loadFormat(string $type, string|array $key, ?string $password = null): AsymmetricKey
{
self::initialize_static_variables();

Expand Down
60 changes: 60 additions & 0 deletions phpseclib/Crypt/DILITHIUM.php
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace phpseclib3\Crypt;

use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\DILITHIUM\PrivateKey;
use phpseclib3\Crypt\DILITHIUM\PublicKey;
use phpseclib3\Exception\UnsupportedAlgorithmException;

abstract class DILITHIUM extends AsymmetricKey
{
public const ALGORITHM = 'DILITHIUM';

protected string $pem;
protected string $raw_bit_string;
protected string $method_name;

protected function __construct()
{
parent::__construct();
$this->hash = new Hash('sha512');

exec(
"openssl list -providers",
$output,
$return
);
$has_oqsprovider = array_search('oqsprovider', array_map('trim', $output)) !== false;

// set proper engine
self::$engines = [
'oqsphp' => extension_loaded('oqsphp'),
'PQC-OpenSSL' => $has_oqsprovider
];
}

protected static function onLoad(array $components)
{
$key = $components['isPublicKey'] ?
new PublicKey() :
new PrivateKey();

$key->method_name = $components['method_name'];
$key->pem = $components['pem'];
$key->raw_bit_string = $components['raw'];

return $key;
}

public function withHash(string $hash): AsymmetricKey
{
if ($hash != 'sha512') {
throw new UnsupportedAlgorithmException('Post-quantum algorithm only supports sha512 as a hash');
}

return parent::withHash($hash);
}
}
72 changes: 72 additions & 0 deletions phpseclib/Crypt/DILITHIUM/Formats/Keys/PEM.php
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace phpseclib3\Crypt\DILITHIUM\Formats\Keys;

use OQS_SIGNATURE;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps\PrivateKeyInfo;
use phpseclib3\File\ASN1\Maps\PublicKeyInfo;

abstract class PEM
{
public static function load($array, ?string $password = null): array
{
$key_pem = $array['key_pem'];
$method_name = $array['method_name'];

$components = [
'isPublicKey' => str_contains($key_pem, 'PUBLIC'),
'isPrivateKey' => str_contains($key_pem, 'PRIVATE'),
'method_name' => $method_name,
];

if (!$components['isPublicKey'] && !$components['isPrivateKey']) {
throw new UnexpectedValueException('Key should be a PEM formatted string');
}

$components['pem'] = $key_pem;

// extract raw key
$extractedBER = ASN1::extractBER($key_pem);
$decodedBER = ASN1::decodeBER($extractedBER);

if ($components['isPrivateKey']) {
$raw_key = ASN1::asn1map($decodedBER[0], PrivateKeyInfo::MAP)['privateKey'];
$sig = new OQS_SIGNATURE($method_name);
$max_length = $sig->length_private_key;

// PQC-OpenSSL encodes privates keys as 0x04 or 0x03 || length || private_key || public_key
// We need to extract private_key only
if (strlen($raw_key) > $max_length) {
$bytearray = unpack('c*', $raw_key);

$offset = 0;
// if it still has ASN1 type and length
if ($bytearray[1] == 0x04 || $bytearray[1] == 0x03) {
// 0x80 indicates that second byte encodes number of bytes containing length
$len_bytes = ($bytearray[2] & 0x80) == 0x80 ? 1 + ($bytearray[2] & 0x7f) : 1;
// 1 is for type 0x04 or 0x03, rest is length_bytes
$offset = 1 + $len_bytes;
}
$private_key_raw = pack('c*', ...array_slice($bytearray, $offset, $max_length));
}
$components['raw'] = $private_key_raw;
} else if ($components['isPublicKey']) {
$raw_key = ASN1::asn1map($decodedBER[0], PublicKeyInfo::MAP)['publicKey'];

// Check if first byte in string is 0
// If it is, it means that the public key is encoded as a positive integer and we need to remove first byte in order to extract the public key
// If it is not, it means that the public key is encoded as a bit string - TODO: fact check this
if (unpack('c', $raw_key)[1] === 0) {
// Remove first byte from bit string
$raw_key = pack('c*', ...array_slice(unpack('c*', $raw_key), 1));
}
$components['raw'] = $raw_key;
}

return $components;
}
}
85 changes: 85 additions & 0 deletions phpseclib/Crypt/DILITHIUM/PrivateKey.php
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace phpseclib3\Crypt\DILITHIUM;

use phpseclib3\Crypt\DILITHIUM;
use phpseclib3\Crypt\Common;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps\PrivateKeyInfo;

final class PrivateKey extends DILITHIUM implements Common\PrivateKey
{
use Common\Traits\PasswordProtected;

public function sign($message)
{
if (self::$engines["PQC-OpenSSL"] && !empty($this->pem)) {
if (openssl_sign($message, $signature, $this->pem, $this->hash->getHash())) {
return $signature;
} else {
throw new RuntimeException("openssl_sign failed: " . openssl_error_string());
}
} else if (self::$engines['oqsphp'] && !empty($this->raw_bit_string)) {
$signature = '';
$oqs_signature = new OQS_SIGNATURE($this->method_name);

if (OQS_SUCCESS === $oqs_signature->sign($signature, $this->hash->hash($message), $this->raw_bit_string)) {
return $signature;
} else {
throw new RuntimeException("OQS_SIGNATURE->sign failed");
}
} else {
throw new RuntimeException("No engine available");
}
}

public function getPublicKey(): string
{
if (self::$engines["PQC-OpenSSL"] && !empty($this->pem)) {
$private_key = openssl_pkey_get_private($this->pem);
$private_key_details = openssl_pkey_get_details($private_key);

return $private_key_details['key'];
} else if (self::$engines['oqsphp'] && !empty($this->pem)) {
$extractedBER = ASN1::extractBER($this->pem);
$decodedBER = ASN1::decodeBER($extractedBER);
$private_key_raw = ASN1::asn1map($decodedBER[0], PrivateKeyInfo::MAP)['privateKey'];

$sig = new OQS_SIGNATURE($this->method_name);
$public_key_length = $sig->length_public_key;
$private_key_length = $sig->length_private_key;

// PQC-OpenSSL encodes privates keys as 0x04 or 0x03 || length || private_key || public_key
// We need to extract public_key only
if (strlen($private_key_raw) >= ($private_key_length + $public_key_length)) {
$bytearray = unpack('c*', $private_key_raw);

$offset = $private_key_length;
// if it still has ASN1 type and length
if ($bytearray[1] == 0x04 || $bytearray[1] == 0x03) {
// 0x80 indicates that second byte encodes number of bytes containing length
$len_bytes = ($bytearray[2] & 0x80) == 0x80 ? 1 + ($bytearray[2] & 0x7f) : 1;
// 1 is for type 0x04 or 0x03, rest is length_bytes
$offset = 1 + $len_bytes + $private_key_length;
}
return pack('c*', ...array_slice($bytearray, $offset, $public_key_length));
} else {
throw new RuntimeException("Could not extract public key from private key");
}
} else {
throw new RuntimeException("No engine available");
}
}

public function toString(string $type, array $options = []): string
{
if ((self::$engines["PQC-OpenSSL"] && self::$engines['oqsphp']) && !empty($this->pem)) {
return $this->pem;
} else {
throw new RuntimeException("No data available");
}
}
}
41 changes: 41 additions & 0 deletions phpseclib/Crypt/DILITHIUM/PublicKey.php
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace phpseclib3\Crypt\DILITHIUM;

use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DILITHIUM;
use phpseclib3\Exception\RuntimeException;

final class PublicKey extends DILITHIUM implements Common\PublicKey
{
use Common\Traits\Fingerprint;

public function verify($message, $signature)
{
if (self::$engines["PQC-OpenSSL"] && !empty($this->pem)) {
$result = openssl_verify($message, $signature, $this->pem, $this->hash->getHash());
if ($result === false) {
throw new RuntimeException("openssl_verify failed: " . openssl_error_string());
} else {
return boolval($result);
}
} else if (self::$engines['oqsphp'] && !empty($this->raw_bit_string)) {
$oqs_signature = new \OQS_SIGNATURE($this->method_name);

return \OQS_SUCCESS === $oqs_signature->verify($this->hash->hash($message), $signature, $this->raw_bit_string);
} else {
throw new RuntimeException("No engine available");
}
}

public function toString(string $type, array $options = []): string
{
if ((self::$engines["PQC-OpenSSL"] || self::$engines['oqsphp']) && !empty($this->pem)) {
return $this->pem;
} else {
throw new RuntimeException("No data available");
}
}
}