Skip to content

salrashid123/pqc_scratchpad

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Post-Quantum Cryptography (PQC) scratchpad

This repo is just a collection of PQC tools and sample code.



MLDSA

Digital Signatures using ML-DSA

Using openssl3.5.0 (if you don't have that version, use the dockerfile below)

openssl genpkey -algorithm ML-DSA-44  -out private.pem
openssl pkey -in private.pem -pubout -out public.pem

echo -n "bar" > /tmp/data.in.raw
openssl dgst -sign private.pem -out /tmp/data.out.signed /tmp/data.in.raw 
openssl dgst -verify public.pem -signature /tmp/data.out.signed  /tmp/data.in.raw  

as a dockerimage using pre-generated certificates

cd mldsa/
docker run -v /dev/urandom:/dev/urandom -v `pwd`/certs:/apps/certs -ti salrashid123/openssl-pqs:3.5.0-dev 

  echo -n "bar" > /tmp/data.in.raw
  openssl dgst -sign /apps/certs/server.key -out /tmp/data.out.signed /tmp/data.in.raw 
  openssl dgst -verify /apps/certs/server.pem -signature /tmp/data.out.signed  /tmp/data.in.raw  

For golang, ML-DSA isn't implemented yet at time of writing so we're using CloudFlares one here github.com/cloudflare/circl/sign/mldsa/mldsa44.

To see the signatures, run

 go run default/main.go

Please note that the PEM files generated by circl is not compatible with openssl yet circl/issue535

JWT Signature

Google Cloud KMS PQC signature verification

GCP KMS allows for certain PQC signatures and the following snippet will generate one and then use it to sign/verify in golang and openssl.

See:

export GCLOUD_USER=`gcloud config get-value core/account`
export PROJECT_ID=`gcloud config get-value core/project`

gcloud kms keys create mldsa1 --keyring=tkr1 \
   --location=us-central1 --purpose=asymmetric-signing    --default-algorithm=pq-sign-ml-dsa-65

gcloud kms keys add-iam-policy-binding mldsa1  \
        --keyring=tkr1 --location=us-central1  \
        --member=user:$GCLOUD_USER  --role=roles/cloudkms.signer

gcloud kms keys add-iam-policy-binding mldsa1 \
        --keyring=tkr1 --location=us-central1  \
        --member=user:$GCLOUD_USER  --role=roles/cloudkms.viewer

$ gcloud kms keys list --keyring=tkr1 --location=us-central1

NAME                                                                      PURPOSE          ALGORITHM                   PROTECTION_LEVEL  LABELS  PRIMARY_ID  PRIMARY_STATE
projects/core-eso/locations/us-central1/keyRings/tkr1/cryptoKeys/mldsa1   ASYMMETRIC_SIGN  PQ_SIGN_ML_DSA_65           SOFTWARE

echo -n "foo" > certs/plain.txt

## to sign
gcloud kms asymmetric-sign \
    --version 1 \
    --key mldsa1 \
    --keyring tkr1 \
    --location us-central1 \
    --input-file certs/plain.txt \
    --signature-file certs/signed.bin

## to recall the public key as b64 standard nist-pqc format
gcloud kms keys versions get-public-key 1  \
  --key=mldsa1 --keyring=tkr1   --location=us-central1 \
   --public-key-format=nist-pqc

To use golang and gcp kms to sign/verify, run

go run main.go --projectID=$PROJECT_ID

This will

  1. use GCP KMS to sign some data
  2. use GCP KMS to download the public key
  3. use https://github.com/cloudflare/circl/tree/main/sign/mldsa to convert the public key into PEM format
  4. convert the PEM format key to include ASN Object identifer that openssl understands
  1. Verify the signature using the public key

From there, if you want to use openssl to verify,

# TLS_SIGALG_ENTRY("mldsa65", "ML-DSA-65", "2.16.840.1.101.3.4.3.18", 1),
docker run -v /dev/urandom:/dev/urandom -v `pwd`/certs:/apps/certs -ti salrashid123/openssl-pqs:3.5.0-dev 

$ openssl asn1parse -inform PEM -in certs/public.pem 
    0:d=0  hl=4 l=1969 cons: SEQUENCE          
    4:d=1  hl=2 l=  10 cons: SEQUENCE          
    6:d=2  hl=2 l=   8 prim: OBJECT            :2.16.840.1.101.3.4.18
   16:d=1  hl=4 l=1953 prim: BIT STRING  
      
$ openssl asn1parse -inform PEM -in certs/public_compat.pem 
    0:d=0  hl=4 l=1970 cons: SEQUENCE          
    4:d=1  hl=2 l=  11 cons: SEQUENCE          
    6:d=2  hl=2 l=   9 prim: OBJECT            :ML-DSA-65
   17:d=1  hl=4 l=1953 prim: BIT STRING     

openssl dgst -verify certs/public_compat.pem -signature certs/signed.bin  certs/plain.txt 
Verified OK

PEM Key conversion

Tje key_comapt/ example generates a public/private key pair in golang and converts the PEM files into the format openssl understands.

THis is just a temp hack to account for issue#535:openssl parsing compatiblity issue for MLDSA

eg, starting with,

$ go run key_compat/main.go
$ docker run -v /dev/urandom:/dev/urandom -v `pwd`/certs:/apps/certs -ti salrashid123/openssl-pqs:3.5.0-dev 

$ openssl asn1parse -inform PEM -in certs/ml-dsa-65-public.pem 
    0:d=0  hl=4 l=1969 cons: SEQUENCE          
    4:d=1  hl=2 l=  10 cons: SEQUENCE          
    6:d=2  hl=2 l=   8 prim: OBJECT            :2.16.840.1.101.3.4.18
   16:d=1  hl=4 l=1953 prim: BIT STRING       

$ openssl asn1parse -inform PEM -in certs/ml-dsa-65-private.pem 
    0:d=0  hl=4 l=4055 cons: SEQUENCE          
    4:d=1  hl=2 l=   1 prim: INTEGER           :00
    7:d=1  hl=2 l=  10 cons: SEQUENCE          
    9:d=2  hl=2 l=   8 prim: OBJECT            :2.16.840.1.101.3.4.18
   19:d=1  hl=4 l=4036 prim: OCTET STRING      [HEX DUMP]:04820FC02D84F5

$ openssl asn1parse -inform PEM -in certs/public_compat.pem 
    0:d=0  hl=4 l=1970 cons: SEQUENCE          
    4:d=1  hl=2 l=  11 cons: SEQUENCE          
    6:d=2  hl=2 l=   9 prim: OBJECT            :ML-DSA-65
   17:d=1  hl=4 l=1953 prim: BIT STRING     

$ openssl asn1parse -inform PEM -in certs/private_compat.pem 
    0:d=0  hl=4 l=4052 cons: SEQUENCE          
    4:d=1  hl=2 l=   1 prim: INTEGER           :00
    7:d=1  hl=2 l=  11 cons: SEQUENCE          
    9:d=2  hl=2 l=   9 prim: OBJECT            :ML-DSA-65
   20:d=1  hl=4 l=4032 prim: OCTET STRING      [HEX DUMP]:2D84F56645A...

then sign and verify with openssl

echo -n "bar" > /tmp/data.in.raw
# openssl dgst -sign certs/ml-dsa-65-private.pem -out /tmp/data.out.signed /tmp/data.in.raw 
#    Could not find private key from certs/ml-dsa-65-private.pem

openssl dgst -sign certs/private_compat.pem -out /tmp/data.out.signed /tmp/data.in.raw 
# openssl dgst -verify certs/ml-dsa-65-public.pem -signature /tmp/data.out.signed  /tmp/data.in.raw  
  # Could not find private key of public key from certs/ml-dsa-65-public.pem
  # 808B1DA0D37F0000:error:1608010C:STORE routines:ossl_store_handle_load_result:unsupported:crypto/store/store_result.c:152:

openssl dgst -verify certs/public_compat.pem -signature /tmp/data.out.signed  /tmp/data.in.raw  
Verified OK

MLKEM

Key Encapsulation ML-KEM

## generate a key as 'seed-only'
openssl genpkey  -algorithm mlkem768 -provparam ml-kem.output_formats=bare-seed  -out priv-ml-kem-768-bare-seed.pem
openssl pkey  -in priv-ml-kem-768-bare-seed.pem  -pubout -out pub-ml-kem-768.pem

## encapsulate with public key
openssl pkeyutl -encap -inkey pub-ml-kem-768.pem  -secret /tmp/encap.dat -out /tmp/ctext.dat

## print shared key
cat /tmp/encap.dat | xxd -p -c 100
  ca08986c403dec7505bfcb214ad53c9a9af24d1547f5c87f74b785699b7eb94c

## decapsulate with private key and arrive at shared key
openssl pkeyutl -decap -inkey priv-ml-kem-768-bare-seed.pem  -in /tmp/ctext.dat | xxd -p -c 100
  ca08986c403dec7505bfcb214ad53c9a9af24d1547f5c87f74b785699b7eb94c

For golang, you can use crypto/mlkem package. The following shows how to generate and arrive at shared keys

$ go run default/main.go 
  SharedSecret: kemShared (2dae99717ffe984dac326f695a28eaea4cb314addc9000d7c8ea19a53ce06062) 
  SharedSecret: kemShared (2dae99717ffe984dac326f695a28eaea4cb314addc9000d7c8ea19a53ce06062) 

If you wanted to create a keypair using openssl and consume it in golang, you need to export the private key from openssl as seed-only:

$ go run openssl_parse/main.go 
  SharedSecret: kemShared (6mt5mhJ9iztKWZGpe1kdXCJv8/lxQuMpmZgvYJTWlyw=) 
  SharedSecret: kemShared (6mt5mhJ9iztKWZGpe1kdXCJv8/lxQuMpmZgvYJTWlyw=) 

PEM Key Conversion

The following will generate a new keypair using go mlkem package and write the keys to a file.

Note that we're writing the seed only as the private key

$ go run default/main.go
$ docker run -v /dev/urandom:/dev/urandom -v `pwd`/certs:/apps/certs -ti salrashid123/openssl-pqs:3.5.0-dev 

$ openssl asn1parse -in certs/pub-ml-kem-768.pem 
    0:d=0  hl=4 l=1202 cons: SEQUENCE          
    4:d=1  hl=2 l=  11 cons: SEQUENCE          
    6:d=2  hl=2 l=   9 prim: OBJECT            :ML-KEM-768
   17:d=1  hl=4 l=1185 prim: BIT STRING        

$ openssl asn1parse -in certs/priv-ml-kem-768.pem 
    0:d=0  hl=4 l=2494 cons: SEQUENCE          
    4:d=1  hl=2 l=   1 prim: INTEGER           :00
    7:d=1  hl=2 l=  11 cons: SEQUENCE          
    9:d=2  hl=2 l=   9 prim: OBJECT            :ML-KEM-768
   20:d=1  hl=4 l=2474 prim: OCTET STRING      [HEX DUMP]:308209A6044067E6BC81C8468080....

$ openssl asn1parse -in certs/private.pem         
    0:d=0  hl=2 l=  82 cons: SEQUENCE          
    2:d=1  hl=2 l=   1 prim: INTEGER           :00
    5:d=1  hl=2 l=  11 cons: SEQUENCE          
    7:d=2  hl=2 l=   9 prim: OBJECT            :ML-KEM-768
   18:d=1  hl=2 l=  64 prim: OCTET STRING      [HEX DUMP]:D7A99DF3E70F7281...

## encapsulate with public key
openssl pkeyutl -encap -inkey certs/private.pem  -secret /tmp/encap.dat -out /tmp/ctext.dat

## print shared key
cat /tmp/encap.dat | xxd -p -c 100
  ca08986c403dec7505bfcb214ad53c9a9af24d1547f5c87f74b785699b7eb94c

openssl pkeyutl -decap -inkey certs/private.pem  -in /tmp/ctext.dat | xxd -p -c 100
  ca08986c403dec7505bfcb214ad53c9a9af24d1547f5c87f74b785699b7eb94c   

SLH-DSA

At the moment (3/7/25), its available in the opessl provider:

docker run -v /dev/urandom:/dev/urandom -ti salrashid123/openssl-pqs:3.5.0-oqsprovider

 openssl list -signature-algorithms --provider oqsprovider

 openssl genpkey  --provider oqsprovider --provider default -algorithm sphincssha2128ssimple  -out private.pem
 openssl pkey --provider oqsprovider --provider default -in private.pem -pubout -out public.pem

 echo -n "bar" > /tmp/data.in.raw
 openssl dgst  --provider oqsprovider --provider default -sign private.pem -out /tmp/data.out.signed /tmp/data.in.raw 
 openssl dgst  --provider oqsprovider --provider default -verify public.pem -signature /tmp/data.out.signed  /tmp/data.in.raw   

TLS

For TLS, the key exchange can use a ML-KEM as shown here:

while the certificate signature can use ML-DSA. The certificates in the mldsa/certs/ folder uses this signature scheme:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 2 (0x2)
        Signature Algorithm: ML-DSA-44
        Issuer: C=US, O=Google, OU=Enterprise, CN=Single Root CA
        Validity
            Not Before: Feb 17 17:36:32 2025 GMT
            Not After : Feb 17 17:36:32 2035 GMT
        Subject: C=US, O=Google, OU=Enterprise, CN=server.domain.com
        Subject Public Key Info:
            Public Key Algorithm: ML-DSA-44  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                ML-DSA-44 Public-Key:
                pub:
                    7c:e6:e4:4b:.....

Then for key exchange with openssl, set the -curves=X25519MLKEM768

To test a client/server with openssl where the certificate

cd mldsa/

### run server
$ docker run -v /dev/urandom:/dev/urandom -v `pwd`/certs:/apps/certs --net=host -ti salrashid123/openssl-pqs:3.5.0-dev

  openssl s_server  -accept 8081    -tls1_3      -key certs/server.key -cert certs/server.crt -curves X25519MLKEM768   -www -trace

### run client
$ docker run -v /dev/urandom:/dev/urandom -v `pwd`/certs:/apps/certs --net=host -ti salrashid123/openssl-pqs:3.5.0-dev

  openssl s_client -connect localhost:8081 --servername  server.domain.com  -tls1_3 -curves X25519MLKEM768   --trace 

Then for key exchange, you'll notice X25519MLKEM768

Header:
  Version = TLS 1.2 (0x303)
  Content Type = Handshake (22)
  Length = 1210
    ServerHello, Length=1206
      server_version=0x303 (TLS 1.2)
      Random:
        gmt_unix_time=0x8994AA65
        random_bytes (len=28): 25EC949FCA8BCA01BA9D5FE8BEFD0EBD15CF9A09C6043E832F5E377A
      session_id (len=32): 52FE38957B58B19EA15082E7612C3BBAA8391495ADD4B5335001AD23F0B78396
      cipher_suite {0x13, 0x02} TLS_AES_256_GCM_SHA384
      compression_method: No Compression (0x00)
      extensions, length = 1134
        extension_type=supported_versions(43), length=2
            TLS 1.3 (772)
        extension_type=key_share(51), length=1124
            NamedGroup: X25519MLKEM768 (4588)                  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            key_exchange:  (len=1120): E56779A73A10B66A1AEE597374F0231F....

While the certificate returned used the ML-DSA algorithm:

Header:
  Version = TLS 1.2 (0x303)
  Content Type = ApplicationData (23)
  Length = 2445
  Inner Content Type = Handshake (22)
    CertificateVerify, Length=2424
      Signature Algorithm: mldsa44 (0x0904) 
      Signature (len=2420): 0A86BE0E95077266EB0....

curl

To test if a server has pqs enabled, you can use the curl oqs Dockerfile:

### test with openquantumsafe server
docker run -ti openquantumsafe/curl curl -vk --curves p521_kyber1024  https://test.openquantumsafe.org/CA.crt

 ## * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / p521_kyber1024 / id-ecPublicKey

### test with aws
docker run -ti openquantumsafe/curl curl -vk   https://kms.us-west-1.amazonaws.com

  ## * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / p384_kyber768 / RSASSA-PSS

PKI

ML-DSA

The certificates above uses ML-DSA signatures which you can generate using the openssl providers shown below and by specifying the scheme see ca_scratchpad

docker run -v /dev/urandom:/dev/urandom  -ti salrashid123/openssl-pqs:3.5.0-dev

git clone https://github.com/salrashid123/ca_scratchpad.git
cd ca_scratchpad

mkdir -p ca/root-ca/private ca/root-ca/db crl certs
chmod 700 ca/root-ca/private
cp /dev/null ca/root-ca/db/root-ca.db
cp /dev/null ca/root-ca/db/root-ca.db.attr

echo 01 > ca/root-ca/db/root-ca.crt.srl
echo 01 > ca/root-ca/db/root-ca.crl.srl

export SAN=single-root-ca

openssl genpkey -algorithm ML-DSA-44 \
      -out ca/root-ca/private/root-ca.key

openssl req -new  -config single-root-ca.conf  -key ca/root-ca/private/root-ca.key \
   -out ca/root-ca.csr  

openssl ca -selfsign     -config single-root-ca.conf  \
   -in ca/root-ca.csr     -out ca/root-ca.crt  \
   -extensions root_ca_ext

SLso see

ML-KEM

For ML-KEM you can create a certificate based on draft Internet X.509 Public Key Infrastructure - Algorithm Identifiers for the Module-Lattice-Based Key-Encapsulation Mechanism (ML-KEM)

docker run -v /dev/urandom:/dev/urandom  -ti salrashid123/openssl-pqs:3.5.0-dev

git clone https://github.com/salrashid123/ca_scratchpad.git
cd ca_scratchpad

mkdir -p ca/root-ca/private ca/root-ca/db crl certs
chmod 700 ca/root-ca/private
cp /dev/null ca/root-ca/db/root-ca.db
cp /dev/null ca/root-ca/db/root-ca.db.attr

echo 01 > ca/root-ca/db/root-ca.crt.srl
echo 01 > ca/root-ca/db/root-ca.crl.srl

export SAN=single-root-ca

openssl genpkey -algorithm ML-DSA-44 \
      -out ca/root-ca/private/root-ca.key

openssl req -new  -config single-root-ca.conf  -key ca/root-ca/private/root-ca.key \
   -out ca/root-ca.csr  

openssl ca -selfsign     -config single-root-ca.conf  \
   -in ca/root-ca.csr     -out ca/root-ca.crt  \
   -extensions root_ca_ext

# openssl genpkey  -algorithm mlkem768 -provparam ml-kem.output_formats=bare-seed  -out priv-ml-kem-768-bare-seed.pem
# openssl pkey  -in priv-ml-kem-768-bare-seed.pem  -pubout -out pub-ml-kem-768.pem


wget https://raw.githubusercontent.com/salrashid123/pqc_scratchpad/refs/heads/main/mlkem/certs/pub-ml-kem-768.pem

cat > key.conf << EOF
[ kem_ext ]
keyUsage                = critical,keyEncipherment
basicConstraints        = CA:false
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always
subjectAltName          = DNS:key1.domain.com
EOF


openssl x509 -new -CAkey ca/root-ca/private/root-ca.key \
   -CA ca/root-ca.crt -force_pubkey pub-ml-kem-768.pem -subj "/CN=ML-KEM Certificate" -out ml-kem.crt -extfile key.conf -extensions kem_ext

openssl x509 -noout -text -in ml-kem.crt

openssl x509 -pubkey -noout -in ml-kem.crt

openssl asn1parse -inform PEM -in ml-kem.crt

This will generate an x509 like this. Notice the signer is ml-dsa-44 and the key is Public Key Algorithm: ML-KEM-768

# openssl x509 -noout -text -in ml-kem.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            67:6a:d4:93:80:25:a6:d5:0b:5d:b4:0a:9e:bf:30:c7:ea:d4:96:f4
        Signature Algorithm: ML-DSA-44
        Issuer: C=US, O=Google, OU=Enterprise, CN=Single Root CA
        Validity
            Not Before: Feb 26 22:03:39 2025 GMT
            Not After : Mar 28 22:03:39 2025 GMT
        Subject: CN=ML-KEM Certificate
        Subject Public Key Info:
            Public Key Algorithm: ML-KEM-768
                ML-KEM-768 Public-Key:
                ek:
                    ba:da:2d:03:99:ca:8....
        X509v3 extensions:
            X509v3 Key Usage: critical
                Key Encipherment
            X509v3 Basic Constraints: 
                CA:FALSE
            X509v3 Subject Key Identifier: 
                CF:51:CC:7C:3E:B2:B0:6D:6D:A0:2A:0F:AB:7F:7C:86:AF:84:0A:78
            X509v3 Authority Key Identifier: 
                FA:5A:E1:FC:76:BF:E2:D2:9D:D9:88:47:BF:33:1A:76:DA:99:BC:6E
            X509v3 Subject Alternative Name: 
                DNS:key1.domain.com
    Signature Algorithm: ML-DSA-44
    Signature Value:
        03:2d:ea:fd:db:01:a0:a6,,,,

Docker images

Openssl with the built-in PQC support as well as the oqs-provider can be found on dockerhub or built from scratch:

Openssl 3.5.0

docker build -t salrashid123/openssl-pqs:3.5.0-dev -f Dockerfile .
docker run -v /dev/urandom:/dev/urandom -ti salrashid123/openssl-pqs:3.5.0-dev

  openssl -version
    OpenSSL 3.5.0-dev  (Library: OpenSSL 3.5.0-dev )

Openssl 3.4.1 with OQSProvider

docker build -t salrashid123/openssl-pqs:3.5.0-oqsprovider  -f Dockerfile.provider .
docker run -v /dev/urandom:/dev/urandom -ti salrashid123/openssl-pqs:3.5.0-oqsprovider 

  openssl list -kem-algorithms --provider oqsprovider

OpenQuantumSafe Docker images

Open Quantum Safe interop test server for quantum-safe cryptography


Releases

No releases published

Packages

No packages published