Skip to content
Merged
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
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,22 @@ jobs:
- name: py3.12
os: ubuntu-latest
python-version: "3.12"
- name: py3.9 with ecdsa
os: ubuntu-latest
python-version: "3.9"
opt-deps: ['ecdsa']
- name: py3.10 with ecdsa
os: ubuntu-latest
python-version: "3.10"
opt-deps: ['ecdsa']
- name: py3.11 with ecdsa
os: ubuntu-latest
python-version: "3.11"
opt-deps: ['ecdsa']
- name: py3.12 with ecdsa
os: ubuntu-latest
python-version: "3.12"
opt-deps: ['ecdsa']
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -169,6 +185,10 @@ jobs:
- name: Install build dependencies
run: |
pip install -r requirements.txt
- name: Install ecdsa
if: ${{ contains(matrix.opt-deps, 'ecdsa') }}
run: |
pip install ecdsa
- name: Display installed python package versions
run: |
pip list || :
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ However, I have not implemented AES itself, instead I import this from `pycrypto

To install dependencies, run `pip -r install requirements`.

The support for reading and writing the keys into PKCS formats requires
the python-ecdsa package. It can be installed using `pip install ecdsa`,
or by installing this package with the `pip install 'kyber-py[pkcs]'`
command.

## Using kyber-py

### ML-KEM
Expand All @@ -100,6 +105,18 @@ use:
- `ML_KEM.encaps(ek)`: generate a key and ciphertext pair `(key, ct)`
- `ML_KEM.decaps(dk, ct)`: generate the shared key `key`

Additionally there are few methods for reading and writing both types of keys
in the `ml_kem.pkcs` module:

- `ek_to_der`: for serialising the encapsulation key to a DER byte string
- `ek_to_pem`: for serialising the encapsulation key to a PEM string
- `ek_from_der`: for extracting the encapsulation key from a DER encoding
- `ek_from_pem`: for extracting the encapsulation key from a PEM encoding
- `dk_to_der`: for serialising the decapsulation key to a DER byte string
- `dk_to_pem`: for serialising the decapsulation key to a PEM string
- `dk_from_der`: for extracting the decapsulation key from a DER encoding
- `dk_from_pem`: for extracting the decapsulation key from a PEM encoding

Those, together with the `ML_KEM_512`, `ML_KEM_768`, and `ML_KEM_1024`
objects comprise the kyber-py library stable API.

Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ classifiers = [
]
license = "MIT"

[project.optional-dependencies]
pkcs = [
"ecdsa>=0.19.1",
]

[project.urls]
Homepage = "https://github.com/GiacomoPope/kyber-py"
Issues = "https://github.com/GiacomoPope/kyber-py/issues"
Expand Down
91 changes: 91 additions & 0 deletions scripts/decaps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import sys
import getopt
from kyber_py.ml_kem.pkcs import (
dk_from_pem,
dk_from_der,
)


def help_msg():
print(
f"""Usage: {sys.argv[0]} [options]
--dk FILE Decapsulation key file name
--dk-form FORM Decapsulation key format name: "DER" or "PEM, with "PEM"
being the default.
--secret FILE Name of file to write the derived secret to
--ciphertext FILE Name of file to read the ciphertext from
--help This message
"""
)


def main():
dk_file = None
dk_form = "PEM"
secret_file = None
ciphertext_file = None

argv = sys.argv[1:]
opts, args = getopt.getopt(
argv,
"",
["dk=", "dk-form=", "secret=", "ciphertext=", "help"],
)

for opt, arg in opts:
if opt == "--dk":
dk_file = arg
elif opt == "--dk-form":
dk_form = arg
elif opt == "--secret":
secret_file = arg
elif opt == "--ciphertext":
ciphertext_file = arg
elif opt == "--help":
help_msg()
sys.exit(0)
else:
print(f"Unrecognised option: {opt}")
sys.exit(1)

if args:
print(f"Unrecognised options: {args}")

if not dk_file:
print("Specify encapsulation key name with --dk option")
help_msg()
sys.exit(1)

if not secret_file:
print("Specify file name to write the derived secret to")
help_msg()
sys.exit(1)

if not ciphertext_file:
print("Specify file name to read the ciphertext from")
help_msg()
sys.exit(1)

if dk_form == "PEM":
dk_dec_func = dk_from_pem
elif dk_form == "DER":
dk_dec_func = dk_from_der
else:
print(f"Wrong name of key format: {dk_form}")
help_msg()
sys.exit(1)

with open(dk_file, "rb") as file:
kem, dk, _, _ = dk_dec_func(file.read())

with open(ciphertext_file, "rb") as file:
ciphertext = file.read()

secret = kem.decaps(dk, ciphertext)

with open(secret_file, "wb") as file:
file.write(secret)


if __name__ == "__main__":
main()
91 changes: 91 additions & 0 deletions scripts/encaps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import sys
import getopt
from kyber_py.ml_kem.pkcs import (
ek_from_pem,
ek_from_der,
)


def help_msg():
print(
f"""Usage: {sys.argv[0]} [options]
--ek FILE Encapsulation key file name
--ek-form FORM Encapsulation key format name: "DER" or "PEM, with "PEM"
being the default.
--secret FILE Name of file to write the derived secret to
--ciphertext FILE Name of file to write the ciphertext to
--help This message
"""
)


def main():
ek_file = None
ek_form = "PEM"
secret_file = None
ciphertext_file = None

argv = sys.argv[1:]
opts, args = getopt.getopt(
argv,
"",
["ek=", "ek-form=", "secret=", "ciphertext=", "help"],
)

for opt, arg in opts:
if opt == "--ek":
ek_file = arg
elif opt == "--ek-form":
ek_form = arg
elif opt == "--secret":
secret_file = arg
elif opt == "--ciphertext":
ciphertext_file = arg
elif opt == "--help":
help_msg()
sys.exit(0)
else:
print(f"Unrecognised option: {opt}")
sys.exit(1)

if args:
print(f"Unrecognised options: {args}")

if not ek_file:
print("Specify encapsulation key name with --ek option")
help_msg()
sys.exit(1)

if not secret_file:
print("Specify file name to write the derived secret to")
help_msg()
sys.exit(1)

if not ciphertext_file:
print("Specify file name to write the ciphertext to")
help_msg()
sys.exit(1)

if ek_form == "PEM":
ek_dec_func = ek_from_pem
elif ek_form == "DER":
ek_dec_func = ek_from_der
else:
print(f"Wrong name of key format: {ek_form}")
help_msg()
sys.exit(1)

with open(ek_file, "rb") as file:
kem, ek = ek_dec_func(file.read())

secret, ciphertext = kem.encaps(ek)

with open(secret_file, "wb") as file:
file.write(secret)

with open(ciphertext_file, "wb") as file:
file.write(ciphertext)


if __name__ == "__main__":
main()
122 changes: 122 additions & 0 deletions scripts/keygen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import sys
import getopt
import os
from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024

from kyber_py.ml_kem.pkcs import (
ek_to_der,
ek_to_pem,
dk_to_der,
dk_to_pem,
)


def help_msg():
print(
f"""Usage: {sys.argv[0]} [options]

--dk FILE Decapsulation key file name
--dk-form FORM Decapsulation key format name: "DER" or "PEM", with "PEM"
being the default.
--dk-cont CONT Decapsulation key contents: "seed", "expanded", or "both",
with "both" being the default.
--ek FILE Encapsulation key file name, none by default
--ek-form FORM Encapsulation key format name: "DER" or "PEM", with "PEM"
being the default.
--kem NAME Name of the KEM to use: ML-KEM-512, ML-KEM-768, or ML-KEM-1024
--help This message
"""
)


def main():
ek_file = None
ek_form = "PEM"
dk_file = None
dk_form = "PEM"
dk_cont = None
kem = None

argv = sys.argv[1:]
opts, args = getopt.getopt(
argv,
"",
["dk=", "dk-form=", "dk-cont=", "ek=", "ek-form=", "kem=", "help"],
)
for opt, arg in opts:
if opt == "--dk":
dk_file = arg
elif opt == "--dk-form":
dk_form = arg
elif opt == "--dk-cont":
dk_cont = arg
elif opt == "--ek":
ek_file = arg
elif opt == "--ek-form":
ek_form = arg
elif opt == "--kem":
kem = arg
elif opt == "--help":
help_msg()
sys.exit(0)
else:
print(f"unrecognised option: {opt}")
sys.exit(1)

if args:
print(f"unrecognised options: {args}")
sys.exit(1)

if not dk_file:
print("Specify output file with --dk option")
help_msg()
sys.exit(1)

if not kem:
print("Specify the kem to generate with --kem option")
help_msg()
sys.exit(1)

if kem == "ML-KEM-512":
kem = ML_KEM_512
elif kem == "ML-KEM-768":
kem = ML_KEM_768
elif kem == "ML-KEM-1024":
kem = ML_KEM_1024
else:
print(f"Unrecognised KEM name: {kem}")
help_msg()
sys.exit(1)

if ek_form == "PEM":
ek_out_func = ek_to_pem
elif ek_form == "DER":
ek_out_func = ek_to_der
else:
print(f"Unrecognised ek format: {ek_form}")
help_msg()
sys.exit(1)

if dk_form == "PEM":
dk_out_func = dk_to_pem
elif dk_form == "DER":
dk_out_func = dk_to_der
else:
print(f"Unrecognised dk format: {dk_form}")
help_msg()
sys.exit(1)

seed = os.urandom(64)

ek, dk = kem.key_derive(seed)

with open(dk_file, "wb") as file:
file.write(dk_out_func(kem, dk, seed, dk_cont))

if ek_file:
with open(ek_file, "wb") as file:
file.write(ek_out_func(kem, ek))


if __name__ == "__main__":
main()
Loading