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

Support for storing private keys as seeds #89

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,12 @@ To install dependencies, run `pip -r install requirements`.

### ML-KEM

There are three functions exposed on the `ML_KEM` class which are intended for
There are four functions exposed on the `ML_KEM` class which are intended for
use:

- `ML_KEM.keygen()`: generate a keypair `(ek, dk)`
- `ML_KEM.key_derive(seed)`: generate a keypair `(ek, dk)` from the provided
seed
- `ML_KEM.encaps(ek)`: generate a key and ciphertext pair `(key, ct)`
- `ML_KEM.decaps(dk, ct)`: generate the shared key `key`

Expand Down
88 changes: 88 additions & 0 deletions interop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
Tools that allow use of the OpenSSL Encapsulation and Decapsulation API.

**Note:** this code expects draft-ietf-lamps-kyber-certificates-06 compatible
behaviour.


OpenSSL setup
-------------

To enable support for PQC algorithms in OpenSSL: install oqsprovider and
modify the `openssl.cnf` file to enable the oqsprovider:
```
[provider_sect]
default = default_sect
oqsprovider = oqsprovider_sect
[oqsprovider_sect]
activate = 1
```
If you've done that properly, the `openssl list -kem-algorithms` will list
ML-KEM as valid options.

OpenSSL keygen
--------------

To generate the private (decapsulation) key using OpenSSL:
```
openssl genpkey -out private-key.pem -algorithm mlkem512
```
(See other algorithm names in https://github.com/open-quantum-safe/oqs-provider)

To extract the public (encapsulation) key using OpenSSL:
```
openssl pkey -pubout -in public-key.pem -out pub.pem
```

Compile OpenSSL helper apps:
----------------------------

Compile the encapsulation and decapsulation helper apps:
```
gcc -o openssl-decap -lcrypto openssl-decap.c
gcc -o openssl-encap -lcrypto openssl-encap.c
```

OpenSSL encapsulation
---------------------
To encapsulate a shared secret:
```
./openssl-encap -k public-key.pem -s secret.bin -c ciphertext.bin
```

OpenSSL decapsulation
---------------------
To decapsulate a shared secret:
```
./openssl-decap -k private-key.pem -s secret-dec.bin -c ciphertext.bin
```

kyber-py setup
--------------
As the key formats use ASN.1 and PEM encoding, they require presence
of the `ecdsa` library. Install it using your distribution package manager
or using `pip`:
```
pip install ecdsa
```

Kyber-py key gen
----------------
To generate both private (decapsulation) and public (encapsulation) keys
with kyber-py, run:
```
PYTHONPATH=../src python ml_kem_keygen.py ML-KEM-512 public-key.pem private-key.pem
```

Kyber-py encapsulation
-----------------------
To encapsulate a shared secret:
```
PYTHONPATH=../src python ml_kem_encap.py public-key.pem secret.bin ciphertext.bin
```

Kyber-py decapsulation
----------------------
To decapsulate a shared secret:
```
PYTHONPATH=../src python ml_kem_decap.py private-key.pem secret-dec.bin ciphertext.bin
```
55 changes: 55 additions & 0 deletions interop/ml_kem_decap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import sys

if len(sys.argv) != 4:
raise ValueError(f"Usage: {sys.argv[0]} dk.pem secret.bin ciphertext.bin")

from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024

OIDS = {
(2, 16, 840, 1, 101, 3, 4, 4, 1): ML_KEM_512,
(2, 16, 840, 1, 101, 3, 4, 4, 2): ML_KEM_768,
(2, 16, 840, 1, 101, 3, 4, 4, 3): ML_KEM_1024,
}

import ecdsa.der as der

with open(sys.argv[1], "rt") as ek_file:
ek_pem = ek_file.read()

ek_der = der.unpem(ek_pem)

s1, empty = der.remove_sequence(ek_der)
if empty != b"":
raise der.UnexpectedDER("Trailing junk after DER public key")

ver, rest = der.remove_integer(s1)

if ver != 0:
raise der.UnexpectedDER("Unexpected format version")

alg_id, rest = der.remove_sequence(rest)

alg_id, empty = der.remove_object(alg_id)
if alg_id not in OIDS:
raise der.UnexpectedDER(f"Not recognised algoritm OID: {alg_id}")
if empty != b"":
raise der.UnexpectedDER("parameters specified for ML-KEM OID")

kem = OIDS[alg_id]

key, empty = der.remove_octet_string(rest)
if empty != b"":
raise der.UnexpectedDER("Trailing junk after the key")

assert len(key) == 64
_, dk = kem.key_derive(key)

with open(sys.argv[3], "rb") as encaps_file:
encaps = encaps_file.read()

secret = kem.decaps(dk, encaps)

with open(sys.argv[2], "wb") as secret_file:
secret_file.write(secret)

print("done")
48 changes: 48 additions & 0 deletions interop/ml_kem_encap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import sys

if len(sys.argv) != 4:
raise ValueError(f"Usage: {sys.argv[0]} ek.pem secret.bin ciphertext.bin")

from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024

OIDS = {
(2, 16, 840, 1, 101, 3, 4, 4, 1): ML_KEM_512,
(2, 16, 840, 1, 101, 3, 4, 4, 2): ML_KEM_768,
(2, 16, 840, 1, 101, 3, 4, 4, 3): ML_KEM_1024,
}

import ecdsa.der as der

with open(sys.argv[1], "rt") as ek_file:
ek_pem = ek_file.read()

ek_der = der.unpem(ek_pem)

s1, empty = der.remove_sequence(ek_der)
if empty != b"":
raise der.UnexpectedDER("Trailing junk after DER public key")

alg_id, rem = der.remove_sequence(s1)

alg_id, rest = der.remove_object(alg_id)
if alg_id not in OIDS:
raise der.UnexpectedDER(f"Not recognised algoritm OID: {alg_id}")

if rest != b"":
raise der.UnexpectedDER("parameters specified for ML-KEM OID")

kem = OIDS[alg_id]

key, empty = der.remove_bitstring(rem, 0)
if empty != b"":
raise der.UnexpectedDER("Trailing junk after the public key bitstring")

secret, encaps = kem.encaps(key)

with open(sys.argv[2], "wb") as secret_file:
secret_file.write(secret)

with open(sys.argv[3], "wb") as encaps_file:
encaps_file.write(encaps)

print("done")
52 changes: 52 additions & 0 deletions interop/ml_kem_key_extract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import sys

if len(sys.argv) != 3:
raise ValueError(f"Usage: {sys.argv[0]} dk.pem ek.pem")

from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024

OIDS = {
(2, 16, 840, 1, 101, 3, 4, 4, 1): ML_KEM_512,
(2, 16, 840, 1, 101, 3, 4, 4, 2): ML_KEM_768,
(2, 16, 840, 1, 101, 3, 4, 4, 3): ML_KEM_1024,
}

import ecdsa.der as der

with open(sys.argv[1], "rt") as ek_file:
ek_pem = ek_file.read()

ek_der = der.unpem(ek_pem)

s1, empty = der.remove_sequence(ek_der)
if empty != b"":
raise der.UnexpectedDER("Trailing junk after DER public key")

ver, rest = der.remove_integer(s1)

if ver != 0:
raise der.UnexpectedDER("Unexpected format version")

alg_id, rest = der.remove_sequence(rest)

alg_id, empty = der.remove_object(alg_id)
if alg_id not in OIDS:
raise der.UnexpectedDER(f"Not recognised algoritm OID: {alg_id}")
if empty != b"":
raise der.UnexpectedDER("parameters specified for ML-KEM OID")

kem = OIDS[alg_id]

key, empty = der.remove_octet_string(rest)
if empty != b"":
raise der.UnexpectedDER("Trailing junk after the key")

assert len(key) == 64
ek, _ = kem.key_derive(key)

with open(sys.argv[2], "wb") as ek_file:
encoded = der.encode_sequence(
der.encode_sequence(der.encode_oid(*alg_id)),
der.encode_bitstring(ek, 0),
)
ek_file.write(der.topem(encoded, "PUBLIC KEY"))
43 changes: 43 additions & 0 deletions interop/ml_kem_keygen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import sys

if len(sys.argv) != 4:
raise ValueError(
f"Usage: {sys.argv[0]} ML-KEM-(512|768|1024) ek.pem dk.pem"
)

if sys.argv[1] == "ML-KEM-512":
from kyber_py.ml_kem.default_parameters import ML_KEM_512 as ML_KEM

oid = (2, 16, 840, 1, 101, 3, 4, 4, 1)
elif sys.argv[1] == "ML-KEM-768":
from kyber_py.ml_kem.default_parameters import ML_KEM_768 as ML_KEM

oid = (2, 16, 840, 1, 101, 3, 4, 4, 2)
elif sys.argv[1] == "ML-KEM-1024":
from kyber_py.ml_kem.default_parameters import ML_KEM_1024 as ML_KEM

oid = (2, 16, 840, 1, 101, 3, 4, 4, 3)
else:
raise ValueError(f"Unrecognised algorithm: {sys.argv[1]}")

import ecdsa.der as der
import os

seed = os.urandom(64)

ek, _ = ML_KEM.key_derive(seed)

with open(sys.argv[2], "wb") as ek_file:
encoded = der.encode_sequence(
der.encode_sequence(der.encode_oid(*oid)),
der.encode_bitstring(ek, 0),
)
ek_file.write(der.topem(encoded, "PUBLIC KEY"))

with open(sys.argv[3], "wb") as dk_file:
encoded = der.encode_sequence(
der.encode_integer(0),
der.encode_sequence(der.encode_oid(*oid)),
der.encode_octet_string(seed),
)
dk_file.write(der.topem(encoded, "PRIVATE KEY"))
Loading
Loading