From 16842a8b9a520485d849be592ff4e2484303b887 Mon Sep 17 00:00:00 2001 From: Vlad Gheorghiu Date: Wed, 13 Nov 2019 12:05:04 -0500 Subject: [PATCH 1/3] added support for RNGs from oqs/rand.h added support for RNGs from oqs/rand.h added support for RNGs from oqs/rand.h --- .gitignore | 117 ++++++++++++++++++++++++++++++++++++- CHANGES.txt | 11 ++++ LICENSE.txt | 2 +- README.md | 21 ++++--- RELEASE.md | 16 ++--- examples/example.py | 80 ------------------------- examples/kem.py | 36 ++++++++++++ examples/rand.py | 23 ++++++++ examples/sig.py | 38 ++++++++++++ oqs/__init__.py | 6 +- oqs/{wrapper.py => oqs.py} | 78 +++++++++++++------------ oqs/rand.py | 58 ++++++++++++++++++ setup.py | 2 +- tests/test_kem.py | 16 +++-- tests/test_sig.py | 28 ++++++--- 15 files changed, 379 insertions(+), 153 deletions(-) create mode 100644 CHANGES.txt delete mode 100644 examples/example.py create mode 100644 examples/kem.py create mode 100644 examples/rand.py create mode 100644 examples/sig.py rename oqs/{wrapper.py => oqs.py} (85%) create mode 100644 oqs/rand.py diff --git a/.gitignore b/.gitignore index 3e067e9..6894774 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,119 @@ __pycache__ liboqs.so *.pyc -test-results \ No newline at end of file +test-results + +.swp +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# PyCharm & virtualenv +.idea +bin/ +include/ +lib/ +spell/ +pip-selfcheck.json +pyvenv.cfg + +# vim +*.swp \ No newline at end of file diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..d77a849 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,11 @@ +Version 0.2.1 - January xx, 2020 + - Added a signature example + - Added partial support for RNGs from + - Added an RNG example + +Version 0.2.0 - October 8, 2019 + - This release updates for compatibility with liboqs 0.2.0, which contains + new/updated algorithms based on NIST Round 2 submissions. + +Version 0.1.0 - April 23, 2019 + - Initial release diff --git a/LICENSE.txt b/LICENSE.txt index 21b15ef..07bc6e8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Open Quantum Safe +Copyright (c) 2018-2020 Open Quantum Safe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b1acdac..4606a95 100644 --- a/README.md +++ b/README.md @@ -33,25 +33,29 @@ Contents The project contains the following files: - - **`oqs/wrapper.py`: a Python 3 module wrapper for the liboqs C library.** - - `examples/example.py`: illustrates the usage of the liboqs-python wrapper. - - `tests/test_*.py`: unit tests for the liboqs-python wrapper. + - **`oqs/oqs.py`: a Python 3 module wrapper for the liboqs C library.** + - `oqs/rand.py`: a Python 3 module supporting RNGs from `` + - `examples/kem.py`: key encapsulation example + - `examples/rand.py`: RNG example + - `examples/sig.py`: signature example + - `tests`: unit tests Usage ----- -liboqs-python defines two main classes: `KeyEncapsulation` and `Signature`, providing post-quantum key encapsulation and signture mechanisms, respectively. Each must be instantiated with a string identifying one of mechanisms supported by liboqs; these can be enumerated using the `get_enabled_KEM_mechanisms` and `get_enabled_sig_mechanisms` functions. The `example.py` file demonstrates the wrapper's API. +liboqs-python defines two main classes: `KeyEncapsulation` and `Signature`, providing post-quantum key encapsulation and signture mechanisms, respectively. Each must be instantiated with a string identifying one of mechanisms supported by liboqs; these can be enumerated using the `get_enabled_KEM_mechanisms` and `get_enabled_sig_mechanisms` functions. The files in `examples/` demonstrate the wrapper's API. +Support for alternative RNGs is provided via the `randombytes[*]` functions. liboqs installation ------------------- liboqs-python depends on the liboqs C library; it must be compiled as a Linux/macOS library or Windows DLL, and installed in one of: -- any file path specified by the `LIBOQS_INSTALL_PATH` environment variable (e.g. `LIBOQS_INSTALL_PATH="/path/to/liboqs.so"`) +- any file path specified by the `LIBOQS_INSTALL_PATH` environment variable (e.g. `LIBOQS_INSTALL_PATH="/usr/local/bin/liboqs.so"`; **do not forget to specify `liboqs.so` at the end**) - system-wide folder - the liboqs Python module's current folder -`wrapper.py` checks the above locations in that order. At present, only liboqs master branch can be installed; see the [liboqs project](https://github.com/open-quantum-safe/liboqs/) for installation instructions. +`oqs/oqs.py` checks the above locations in that order. At present, only liboqs master branch can be installed; see the [liboqs project](https://github.com/open-quantum-safe/liboqs/) for installation instructions. liboqs-python does not depend on any other Python packages. The package isn't hosted on PyPI yet, but can be installed into a virtualenv using: @@ -71,9 +75,9 @@ The liboqs-python project should be in the `PYTHONPATH`: As any python module, liboqs wrapper components can be imported into python programs with `import oqs`. -To run the example program: +To run an example program: - python3 examples/example.py + python3 examples/kem.py To run the unit tests with a test runner (e.g. nose or rednose (`apt install python3-nose python3-rednose` or `pip3 install nose rednose`)): @@ -113,6 +117,7 @@ Contributors to the liboqs-python wrapper include: - Ben Davies (University of Waterloo) - Christian Paquin (Microsoft Research) +- Vlad Gheorghiu (evolutionQ, University of Waterloo) ### Support diff --git a/RELEASE.md b/RELEASE.md index ed92794..ff74517 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,23 +1,25 @@ -liboqs-python version 0.2.0 +liboqs-python version 0.2.1 =========================== About ----- -The **Open Quantum Safe (OQS) project** has the goal of developing and prototyping quantum-resistant cryptography. More information on OQS can be found on our website: https://openquantumsafe.org/ and on Github at https://github.com/open-quantum-safe/. +The **Open Quantum Safe (OQS) project** has the goal of developing and prototyping quantum-resistant cryptography. More information on OQS can be found on our website: https://openquantumsafe.org/ and on Github at https://github.com/open-quantum-safe/. -**liboqs** is an open source C library for quantum-resistant cryptographic algorithms. See more about liboqs at [https://github.com/open-quantum-safe/liboqs/](https://github.com/open-quantum-safe/liboqs/), including a list of supported algorithms. +**liboqs** is an open source C library for quantum-resistant cryptographic algorithms. See more about liboqs at [https://github.com/open-quantum-safe/liboqs/](https://github.com/open-quantum-safe/liboqs/), including a list of supported algorithms. -**liboqs-python** is an open source Python 3 wrapper for the liboqs C library for quantum-resistant cryptographic algorithms. Details about liboqs-python can be found in [README.md](https://github.com/open-quantum-safe/liboqs-python/blob/master/README.md). See in particular limitations on intended use. +**liboqs-python** is an open source Python 3 wrapper for the liboqs C library for quantum-resistant cryptographic algorithms. Details about liboqs-python can be found in [README.md](https://github.com/open-quantum-safe/liboqs-python/blob/master/README.md). See in particular limitations on intended use. Release notes ============= -This release of liboqs-python was released on October 8, 2019. Its release page on GitHub is https://github.com/open-quantum-safe/liboqs-python/releases/tag/0.2.0. +This release of liboqs-python was released on January xx, 2020. Its release page on GitHub is https://github.com/open-quantum-safe/liboqs-python/releases/tag/0.2.1. What's New ---------- -This is the second release of liboqs-python. +This is the third release of liboqs-python. +This release added partial support for RNGs from ``, together +with a signature example and an RNG example. +For a list of changes see [CHANGES.txt](https://github.com/open-quantum-safe/liboqs-python/blob/master/CHANGES.txt). -This release updates for compatibility with liboqs 0.2.0, which contains new/updated algorithms based on NIST Round 2 submissions. diff --git a/examples/example.py b/examples/example.py deleted file mode 100644 index 68c79ca..0000000 --- a/examples/example.py +++ /dev/null @@ -1,80 +0,0 @@ -# illustrates how to use the python OQS wrapper - -from pprint import pprint -import oqs - - -####################################################################### -# KEM example -####################################################################### - -kems = oqs.get_enabled_KEM_mechanisms() - -print('Enabled KEM mechanisms:') -pprint(kems) - -# create client and server with default KEM mechanism -kemalg = "DEFAULT" -with oqs.KeyEncapsulation(kemalg) as client: - with oqs.KeyEncapsulation(kemalg) as server: - print("\nStarting key encapsulation") - pprint(client.details) - - # client generates its keypair - public_key = client.generate_keypair() - # optionally, the secret key can be obtained by calling export_secret_key() - # and the client can later be reinstantiated with the key pair: - # secret_key = client.export_secret_key() - # store key pair, wait... (session resumption): - # client = oqs.OQS_KEM(kemalg, secret_key) - - # the server encapsulates its secret using the client's public key - ciphertext, shared_secret_server = server.encap_secret(public_key) - - # the client decapsulates the the server's ciphertext to obtain the shared secret - shared_secret_client = client.decap_secret(ciphertext) - - if shared_secret_client == shared_secret_server: - print("success: shared secrets are equal") - else: - print("error: shared secrets are NOT equal") - -print() - -####################################################################### -# Signature example -####################################################################### - -sigs = oqs.get_enabled_sig_mechanisms() - -print('Enabled Signature mechanisms:') -pprint(sigs) - -# create signer and verifier with default signature mechanism -sigalg = "DEFAULT" -with oqs.Signature(sigalg) as signer: - with oqs.Signature(sigalg) as verifier: - print("\nStarting signature") - pprint(signer.details) - - # the signer generates its keypair - signer_public_key = signer.generate_keypair() - # optionally, the secret key can be obtained by calling export_secret_key() - # and the signer can later be reinstantiated with the key pair: - # signer_secret_key = signer.export_secret_key() - # store key pair, wait... (session resumption): - # signer = oqs.OQS_SIG(sigalg, signer_secret_key) - - # the message to sign - message = b'This is the message to sign' - - # the signer signs the message - signature = signer.sign(message) - - # the verifier verifies the signature on the message - is_valid = verifier.verify(message, signature, signer_public_key) - - if is_valid: - print("signature is valid") - else: - print("signature is invalid") diff --git a/examples/kem.py b/examples/kem.py new file mode 100644 index 0000000..2890de7 --- /dev/null +++ b/examples/kem.py @@ -0,0 +1,36 @@ +# key encapsulation Python example + +from pprint import pprint +import oqs + +####################################################################### +# KEM example +####################################################################### + +kems = oqs.get_enabled_KEM_mechanisms() + +print('Enabled KEM mechanisms:') +pprint(kems) + +# create client and server with default KEM mechanisms +kemalg = "DEFAULT" +with oqs.KeyEncapsulation(kemalg) as client: + with oqs.KeyEncapsulation(kemalg) as server: + print("\nKey encapsulation details:") + pprint(client.details) + + # client generates its keypair + public_key = client.generate_keypair() + # optionally, the secret key can be obtained by calling export_secret_key() + # and the client can later be re-instantiated with the key pair: + # secret_key = client.export_secret_key() + # store key pair, wait... (session resumption): + # client = oqs.KeyEncapsulation(kemalg, secret_key) + + # the server encapsulates its secret using the client's public key + ciphertext, shared_secret_server = server.encap_secret(public_key) + + # the client decapsulates the the server's ciphertext to obtain the shared secret + shared_secret_client = client.decap_secret(ciphertext) + + print("\nShared secretes coincide:", shared_secret_client == shared_secret_server) diff --git a/examples/rand.py b/examples/rand.py new file mode 100644 index 0000000..2a3c93d --- /dev/null +++ b/examples/rand.py @@ -0,0 +1,23 @@ +# various RNGs Python example + +import oqs.rand as oqsrand # must be explicitly imported + +####################################################################### +# randomness example +####################################################################### + +# set the entropy seed to some random values +entropy_seed = [0] * 48 +entropy_seed[0] = 100 +entropy_seed[20] = 200 +entropy_seed[47] = 150 + +oqsrand.randombytes_nist_kat_init(bytes(entropy_seed)) +oqsrand.randombytes_switch_algorithm('NIST-KAT') +print('{:17s}'.format("NIST-KAT:"), ' '.join('{:02X}'.format(x) for x in oqsrand.randombytes(32))) + +oqsrand.randombytes_switch_algorithm("OpenSSL") +print('{:17s}'.format("OpenSSL:"), ' '.join('{:02X}'.format(x) for x in oqsrand.randombytes(32))) + +oqsrand.randombytes_switch_algorithm("system") +print('{:17s}'.format("System (default):"), ' '.join('{:02X}'.format(x) for x in oqsrand.randombytes(32))) diff --git a/examples/sig.py b/examples/sig.py new file mode 100644 index 0000000..77860aa --- /dev/null +++ b/examples/sig.py @@ -0,0 +1,38 @@ +# signature Python example + +from pprint import pprint +import oqs + +####################################################################### +# signature example +####################################################################### + +sigs = oqs.get_enabled_sig_mechanisms() + +print('Enabled signature mechanisms:') +pprint(sigs) + +message = "This is the message to sign".encode() + +# create signer and verifier with default signature mechanisms +sigalg = "DEFAULT" +with oqs.Signature(sigalg) as signer: + with oqs.Signature(sigalg) as verifier: + print("\nSignature details:") + pprint(signer.details) + + # signer generates its keypair + signer_public_key = signer.generate_keypair() + # optionally, the secret key can be obtained by calling export_secret_key() + # and the signer can later be re-instantiated with the key pair: + # secret_key = signer.export_secret_key() + # store key pair, wait... (session resumption): + # signer = oqs.Signature(sigalg, secret_key) + + # signer signs the message + signature = signer.sign(message) + + # verifier verifies the signature + is_valid = verifier.verify(message, signature, signer_public_key) + + print("\nValid signature?", is_valid) diff --git a/oqs/__init__.py b/oqs/__init__.py index c7db430..f82845b 100644 --- a/oqs/__init__.py +++ b/oqs/__init__.py @@ -1,5 +1 @@ -from oqs.wrapper import KeyEncapsulation -from oqs.wrapper import Signature -from oqs.wrapper import MechanismNotEnabledError, MechanismNotSupportedError -from oqs.wrapper import get_enabled_KEM_mechanisms, get_supported_KEM_mechanisms -from oqs.wrapper import get_enabled_sig_mechanisms, get_supported_sig_mechanisms +from oqs.oqs import * diff --git a/oqs/wrapper.py b/oqs/oqs.py similarity index 85% rename from oqs/wrapper.py rename to oqs/oqs.py index c9739ba..9488e06 100644 --- a/oqs/wrapper.py +++ b/oqs/oqs.py @@ -15,13 +15,12 @@ # import platform to learn the OS we're on import platform - -LIBOQS_SHARED_OBJ = 'oqs' if platform.system() == 'Windows' else 'liboqs.so' -LIBOQS_INSTALL_PATH = 'LIBOQS_INSTALL_PATH' - +_LIBOQS_SHARED_OBJ = 'oqs' if platform.system() == 'Windows' else 'liboqs.so' +_LIBOQS_INSTALL_PATH = 'LIBOQS_INSTALL_PATH' # expected return value from native OQS functions -_OQS_SUCCESS = 0 +OQS_SUCCESS = 0 +OQS_ERROR = -1 def _load_shared_obj(): @@ -29,11 +28,11 @@ def _load_shared_obj(): paths = [] # try custom env var first - if LIBOQS_INSTALL_PATH in os.environ: - paths.append(os.environ[LIBOQS_INSTALL_PATH]) + if _LIBOQS_INSTALL_PATH in os.environ: + paths.append(os.environ[_LIBOQS_INSTALL_PATH]) # search typical locations too - paths += [ctu.find_library('oqs'), os.path.join(os.curdir, LIBOQS_SHARED_OBJ)] + paths += [ctu.find_library('oqs'), os.path.join(os.curdir, _LIBOQS_SHARED_OBJ)] dll = ct.windll if platform.system() == 'Windows' else ct.cdll for path in paths: @@ -59,20 +58,18 @@ class MechanismNotSupportedError(Exception): def __init__(self, alg_name): """ - :param alg_name: requested algorithm name + :param alg_name: requested algorithm name. """ self.alg_name = alg_name self.message = alg_name + ' is not supported by OQS' class MechanismNotEnabledError(MechanismNotSupportedError): - """ - Exception raised when an algorithm is not supported but not enabled by OQS. - """ + """Exception raised when an algorithm is not supported but not enabled by OQS.""" def __init__(self, alg_name): """ - :param alg_name: requested algorithm name + :param alg_name: requested algorithm name. """ self.alg_name = alg_name self.message = alg_name + ' is not supported but not enabled by OQS' @@ -108,9 +105,11 @@ class KeyEncapsulation(ct.Structure): def __init__(self, alg_name, secret_key=None): """ - Create new KeyEncapsulation with the given algorithm. - :param alg_name: KEM mechanism algorithm name - :param secret_key: optional if generating by generate_keypair() later + Creates new KeyEncapsulation with the given algorithm. + + :param alg_name: KEM mechanism algorithm name. Enabled KEM mechanisms can be obtained with + get_enabled_KEM_mechanisms(). + :param secret_key: optional if generating by generate_keypair() later. """ self.alg_name = alg_name if alg_name not in _enabled_KEMs: @@ -126,7 +125,7 @@ def __init__(self, alg_name, secret_key=None): 'name': self._kem.contents.method_name.decode(), 'version': self._kem.contents.alg_version.decode(), 'claimed_nist_level': int(self._kem.contents.claimed_nist_level), - 'is_ind_cca' : bool(self._kem.contents.ind_cca), + 'is_ind_cca': bool(self._kem.contents.ind_cca), 'length_public_key': int(self._kem.contents.length_public_key), 'length_secret_key': int(self._kem.contents.length_secret_key), 'length_ciphertext': int(self._kem.contents.length_ciphertext), @@ -150,7 +149,7 @@ def generate_keypair(self): public_key = ct.create_string_buffer(self._kem.contents.length_public_key) self.secret_key = ct.create_string_buffer(self._kem.contents.length_secret_key) rv = liboqs.OQS_KEM_keypair(self._kem, ct.byref(public_key), ct.byref(self.secret_key)) - return bytes(public_key) if rv == _OQS_SUCCESS else 0 + return bytes(public_key) if rv == OQS_SUCCESS else 0 def export_secret_key(self): """Exports the secret key.""" @@ -166,7 +165,7 @@ def encap_secret(self, public_key): ciphertext = ct.create_string_buffer(self._kem.contents.length_ciphertext) shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret) rv = liboqs.OQS_KEM_encaps(self._kem, ct.byref(ciphertext), ct.byref(shared_secret), my_public_key) - return bytes(ciphertext), bytes(shared_secret) if rv == _OQS_SUCCESS else 0 + return bytes(ciphertext), bytes(shared_secret) if rv == OQS_SUCCESS else 0 def decap_secret(self, ciphertext): """ @@ -177,7 +176,7 @@ def decap_secret(self, ciphertext): my_ciphertext = ct.create_string_buffer(ciphertext, self._kem.contents.length_ciphertext) shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret) rv = liboqs.OQS_KEM_decaps(self._kem, ct.byref(shared_secret), my_ciphertext, self.secret_key) - return bytes(shared_secret) if rv == _OQS_SUCCESS else 0 + return bytes(shared_secret) if rv == OQS_SUCCESS else 0 def free(self): """Releases the native resources.""" @@ -186,7 +185,7 @@ def free(self): liboqs.OQS_MEM_cleanse(ct.byref(self.secret_key), self._kem.contents.length_secret_key) def __repr__(self): - return "Key encapsulation mechanism: " + self._kem.contents.method_name.decode() + return 'Key encapsulation mechanism: ' + self._kem.contents.method_name.decode() liboqs.OQS_KEM_new.restype = ct.POINTER(KeyEncapsulation) @@ -194,9 +193,10 @@ def __repr__(self): def is_KEM_enabled(alg_name): - """Returns True if the KEM algorithm is enabled. + """ + Returns True if the KEM algorithm is enabled. - :param alg_name: a KEM mechanism algorithm name + :param alg_name: a KEM mechanism algorithm name. """ return liboqs.OQS_KEM_alg_is_enabled(ct.create_string_buffer(alg_name.encode())) @@ -212,7 +212,7 @@ def get_enabled_KEM_mechanisms(): def get_supported_KEM_mechanisms(): - """Returns list of supported KEM mechanisms.""" + """Returns the list of supported KEM mechanisms.""" return _supported_KEMs @@ -243,12 +243,13 @@ class Signature(ct.Structure): ("verify_cb", ct.c_void_p) ] - def __init__(self, alg_name, secret_key = None): + def __init__(self, alg_name, secret_key=None): """ - Create new Signature with the given algorithm. - :param alg_name: a signature mechanism algorithm name. Enabled signature - mechanisms can be obtained with get_enabled_KEM_mechanisms(). - :param secret_key: optional, if generated by generate_keypair() + Creates new Signature with the given algorithm. + + :param alg_name: a signature mechanism algorithm name. Enabled signature mechanisms can be obtained with + get_enabled_sig_mechanisms(). + :param secret_key: optional, if generated by generate_keypair(). """ if alg_name not in _enabled_sigs: # perhaps it's a supported but not enabled alg @@ -257,7 +258,7 @@ def __init__(self, alg_name, secret_key = None): else: raise MechanismNotSupportedError(alg_name) - self._sig = liboqs.OQS_SIG_new( ct.create_string_buffer(alg_name.encode())) + self._sig = liboqs.OQS_SIG_new(ct.create_string_buffer(alg_name.encode())) self.details = { 'name': self._sig.contents.method_name.decode(), 'version': self._sig.contents.alg_version.decode(), @@ -285,7 +286,7 @@ def generate_keypair(self): public_key = ct.create_string_buffer(self._sig.contents.length_public_key) self.secret_key = ct.create_string_buffer(self._sig.contents.length_secret_key) rv = liboqs.OQS_SIG_keypair(self._sig, ct.byref(public_key), ct.byref(self.secret_key)) - return bytes(public_key) if rv == _OQS_SUCCESS else 0 + return bytes(public_key) if rv == OQS_SUCCESS else 0 def export_secret_key(self): """Exports the secret key.""" @@ -301,12 +302,12 @@ def sign(self, message): my_message = ct.create_string_buffer(message, len(message)) message_len = ct.c_int(len(my_message)) signature = ct.create_string_buffer(self._sig.contents.length_signature) - sig_len = ct.c_int(self._sig.contents.length_signature) # initialize to maximum signature size + sig_len = ct.c_int(self._sig.contents.length_signature) # initialize to maximum signature size rv = liboqs.OQS_SIG_sign(self._sig, ct.byref(signature), ct.byref(sig_len), my_message, message_len, self.secret_key) - return bytes(signature[:sig_len.value]) if rv == _OQS_SUCCESS else 0 + return bytes(signature[:sig_len.value]) if rv == OQS_SUCCESS else 0 def verify(self, message, signature, public_key): """ @@ -326,7 +327,7 @@ def verify(self, message, signature, public_key): my_public_key = ct.create_string_buffer(public_key, self._sig.contents.length_public_key) rv = liboqs.OQS_SIG_verify(self._sig, my_message, message_len, my_signature, sig_len, my_public_key) - return True if rv == _OQS_SUCCESS else False + return True if rv == OQS_SUCCESS else False def free(self): """Releases the native resources.""" @@ -335,7 +336,7 @@ def free(self): liboqs.OQS_MEM_cleanse(ct.byref(self.secret_key), self._sig.contents.length_secret_key) def __repr__(self): - return "Signature mechanism: " + self._sig.contents.method_name.decode() + return 'Signature mechanism: ' + self._sig.contents.method_name.decode() liboqs.OQS_SIG_new.restype = ct.POINTER(Signature) @@ -343,9 +344,10 @@ def __repr__(self): def is_sig_enabled(alg_name): - """Returns True if the signature algorithm is enabled. + """ + Returns True if the signature algorithm is enabled. - :param alg_name: a signature mechanism algorithm name + :param alg_name: a signature mechanism algorithm name. """ return liboqs.OQS_SIG_alg_is_enabled(ct.create_string_buffer(alg_name.encode())) @@ -361,5 +363,5 @@ def get_enabled_sig_mechanisms(): def get_supported_sig_mechanisms(): - """Returns list of supported signature mechanisms.""" + """Returns the list of supported signature mechanisms.""" return _supported_sigs diff --git a/oqs/rand.py b/oqs/rand.py new file mode 100644 index 0000000..d6959c4 --- /dev/null +++ b/oqs/rand.py @@ -0,0 +1,58 @@ +""" +Open Quantum Safe (OQS) Python Wrapper for liboqs + +The liboqs project provides post-quantum public key cryptography algorithms: +https://github.com/open-quantum-safe/liboqs + +This module provides a Python 3 interface to libOQS RNGs. +""" + +import oqs + + +def randombytes(bytes_to_read): + """ + Generates random bytes. This implementation uses either the default RNG algorithm ("system"), or whichever + algorithm has been selected by random_bytes_switch_algorithm(). + + :param bytes_to_read: the number of random bytes to generate. + :return: random bytes. + """ + result = oqs.ct.create_string_buffer(bytes_to_read) + oqs.liboqs.OQS_randombytes(result, oqs.ct.c_int(bytes_to_read)) + return bytes(result) + + +def randombytes_switch_algorithm(alg_name): + """ + Switches the core OQS_randombytes to use the specified algorithm. See liboqs headers for more details. + + :param alg_name: algorithm name, possible values are "system", "NIST-KAT", "OpenSSL". + """ + if oqs.liboqs.OQS_randombytes_switch_algorithm(oqs.ct.create_string_buffer(alg_name.encode())) != oqs.OQS_SUCCESS: + raise RuntimeError('Can not switch algorithm') + + +def randombytes_nist_kat_init(entropy_input, personalization_string=None): + """ + Initializes the NIST DRBG with the an entropy seed. + + :param entropy_input: entropy input seed, must be exactly 48 bytes long. + :param personalization_string: optional personalization string, which, if present, must be at least 48 bytes long. + """ + if len(entropy_input) != 48: + raise ValueError('The entropy source must be exactly 48 bytes long') + + if personalization_string is not None: + if len(personalization_string) < 48: + raise ValueError('The personalization string must be either empty or at least 48 bytes long') + oqs.liboqs.OQS_randombytes_nist_kat_init(oqs.ct.create_string_buffer(entropy_input), + oqs.ct.create_string_buffer(personalization_string), 256) + + oqs.liboqs.OQS_randombytes_nist_kat_init(oqs.ct.create_string_buffer(entropy_input), 0, 256) + +# def randombytes_in_place(random_array, bytes_to_read): +# raise NotImplementedError + +# def randombytes_custom_algorithm(fun): +# raise NotImplementedError diff --git a/setup.py b/setup.py index 67f8b0e..edd43dd 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='liboqs-python', - version='0.2.0', + version='0.2.1', author='liboqs team', author_email='contact@openquantumsafe.org', packages=find_packages(exclude=('tests', 'docs', 'examples')), diff --git a/tests/test_kem.py b/tests/test_kem.py index 4f0d728..c85273a 100644 --- a/tests/test_kem.py +++ b/tests/test_kem.py @@ -1,9 +1,11 @@ import oqs import random + def test_correctness(): for alg_name in oqs.get_enabled_KEM_mechanisms(): - yield (check_correctness, alg_name) + yield check_correctness, alg_name + def check_correctness(alg_name): kem = oqs.KeyEncapsulation(alg_name) @@ -13,9 +15,11 @@ def check_correctness(alg_name): assert shared_secret_client == shared_secret_server kem.free() + def test_wrong_ciphertext(): for alg_name in oqs.get_enabled_KEM_mechanisms(): - yield (check_wrong_ciphertext, alg_name) + yield check_wrong_ciphertext, alg_name + def check_wrong_ciphertext(alg_name): kem = oqs.KeyEncapsulation(alg_name) @@ -26,6 +30,7 @@ def check_wrong_ciphertext(alg_name): assert shared_secret_client != shared_secret_server kem.free() + def test_not_supported(): try: kem = oqs.KeyEncapsulation('bogus') @@ -34,7 +39,7 @@ def test_not_supported(): pass except E: raise AssertionError("An unexpected exception was raised. " + E) - + def test_not_enabled(): # TODO: test broken as the compiled lib determines which algorithms are @@ -50,10 +55,13 @@ def test_not_enabled(): except E: raise AssertionError("An unexpected exception was raised. " + E) + if __name__ == '__main__': try: import nose2 + nose2.main() except ImportError: import nose - nose.runmodule() \ No newline at end of file + + nose.runmodule() diff --git a/tests/test_sig.py b/tests/test_sig.py index 359b2ad..2cdaf49 100644 --- a/tests/test_sig.py +++ b/tests/test_sig.py @@ -1,9 +1,11 @@ import oqs import random + def test_correctness(): for alg_name in oqs.get_enabled_sig_mechanisms(): - yield (check_correctness, alg_name) + yield check_correctness, alg_name + def check_correctness(alg_name): message = bytes(random.getrandbits(8) for _ in range(100)) @@ -13,9 +15,11 @@ def check_correctness(alg_name): assert sig.verify(message, signature, public_key) sig.free() + def test_wrong_message(): for alg_name in oqs.get_enabled_sig_mechanisms(): - yield (check_wrong_message, alg_name) + yield check_wrong_message, alg_name + def check_wrong_message(alg_name): message = bytes(random.getrandbits(8) for _ in range(100)) @@ -23,12 +27,14 @@ def check_wrong_message(alg_name): public_key = sig.generate_keypair() signature = sig.sign(message) wrong_message = bytes(random.getrandbits(8) for _ in range(100)) - assert not(sig.verify(wrong_message, signature, public_key)) + assert not (sig.verify(wrong_message, signature, public_key)) sig.free() + def test_wrong_signature(): for alg_name in oqs.get_enabled_sig_mechanisms(): - yield (check_wrong_signature, alg_name) + yield check_wrong_signature, alg_name + def check_wrong_signature(alg_name): message = bytes(random.getrandbits(8) for _ in range(100)) @@ -36,12 +42,14 @@ def check_wrong_signature(alg_name): public_key = sig.generate_keypair() signature = sig.sign(message) wrong_signature = bytes(random.getrandbits(8) for _ in range(sig.details['length_signature'])) - assert not(sig.verify(message, wrong_signature, public_key)) + assert not (sig.verify(message, wrong_signature, public_key)) sig.free() + def test_wrong_public_key(): for alg_name in oqs.get_enabled_sig_mechanisms(): - yield (check_wrong_public_key, alg_name) + yield check_wrong_public_key, alg_name + def check_wrong_public_key(alg_name): message = bytes(random.getrandbits(8) for _ in range(100)) @@ -49,9 +57,10 @@ def check_wrong_public_key(alg_name): public_key = sig.generate_keypair() signature = sig.sign(message) wrong_public_key = bytes(random.getrandbits(8) for _ in range(sig.details['length_public_key'])) - assert not(sig.verify(message, signature, wrong_public_key)) + assert not (sig.verify(message, signature, wrong_public_key)) sig.free() + def test_not_supported(): try: sig = oqs.Signature('bogus') @@ -76,10 +85,13 @@ def test_not_enabled(): except E: raise AssertionError("An unexpected exception was raised. " + E) + if __name__ == '__main__': try: import nose2 + nose2.main() except ImportError: import nose - nose.runmodule() \ No newline at end of file + + nose.runmodule() From 329d3e2b1e673806c2306bf6bf2d78b6c3559e1b Mon Sep 17 00:00:00 2001 From: Douglas Stebila Date: Wed, 22 Jan 2020 20:47:06 -0500 Subject: [PATCH 2/3] Remove commented out code --- oqs/rand.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/oqs/rand.py b/oqs/rand.py index d6959c4..bde31b4 100644 --- a/oqs/rand.py +++ b/oqs/rand.py @@ -50,9 +50,3 @@ def randombytes_nist_kat_init(entropy_input, personalization_string=None): oqs.ct.create_string_buffer(personalization_string), 256) oqs.liboqs.OQS_randombytes_nist_kat_init(oqs.ct.create_string_buffer(entropy_input), 0, 256) - -# def randombytes_in_place(random_array, bytes_to_read): -# raise NotImplementedError - -# def randombytes_custom_algorithm(fun): -# raise NotImplementedError From 6c1a39762cd8daea4b6baba3f3af754be55246f7 Mon Sep 17 00:00:00 2001 From: Douglas Stebila Date: Wed, 22 Jan 2020 20:50:00 -0500 Subject: [PATCH 3/3] Update release notes and documentation for version 0.2.1 --- CHANGES.txt | 2 +- README.md | 5 +++-- RELEASE.md | 8 ++++---- setup.py | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d77a849..d60a6e0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -Version 0.2.1 - January xx, 2020 +Version 0.2.1 - January 22, 2020 - Added a signature example - Added partial support for RNGs from - Added an RNG example diff --git a/README.md b/README.md index 4606a95..836ab09 100644 --- a/README.md +++ b/README.md @@ -116,13 +116,14 @@ The Open Quantum Safe project is led by [Douglas Stebila](https://www.douglas.st Contributors to the liboqs-python wrapper include: - Ben Davies (University of Waterloo) -- Christian Paquin (Microsoft Research) - Vlad Gheorghiu (evolutionQ, University of Waterloo) +- Christian Paquin (Microsoft Research) +- Douglas Stebila (University of Waterloo) ### Support Financial support for the development of Open Quantum Safe has been provided by Amazon Web Services and the Tutte Institute for Mathematics and Computing. -We'd like to make a special acknowledgement to the companies who have dedicated programmer time to contribute source code to OQS, including Amazon Web Services, evolutionQ, and Microsoft Research. +We'd like to make a special acknowledgement to the companies who have dedicated programmer time to contribute source code to OQS, including Amazon Web Services, Cisco Systems, evolutionQ, IBM Research, and Microsoft Research. Research projects which developed specific components of OQS have been supported by various research grants, including funding from the Natural Sciences and Engineering Research Council of Canada (NSERC); see the source papers for funding acknowledgments. diff --git a/RELEASE.md b/RELEASE.md index ff74517..b66c37e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -13,13 +13,13 @@ The **Open Quantum Safe (OQS) project** has the goal of developing and prototypi Release notes ============= -This release of liboqs-python was released on January xx, 2020. Its release page on GitHub is https://github.com/open-quantum-safe/liboqs-python/releases/tag/0.2.1. +This release of liboqs-python was released on January 22, 2020. Its release page on GitHub is https://github.com/open-quantum-safe/liboqs-python/releases/tag/0.2.1. What's New ---------- This is the third release of liboqs-python. -This release added partial support for RNGs from ``, together -with a signature example and an RNG example. -For a list of changes see [CHANGES.txt](https://github.com/open-quantum-safe/liboqs-python/blob/master/CHANGES.txt). +This release added partial support for RNGs from ``, together with a signature example and an RNG example. + +For a list of changes see [CHANGES.txt](https://github.com/open-quantum-safe/liboqs-python/blob/master/CHANGES.txt). diff --git a/setup.py b/setup.py index edd43dd..e9c4278 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='liboqs-python', version='0.2.1', - author='liboqs team', + author='Open Quantum Safe project', author_email='contact@openquantumsafe.org', packages=find_packages(exclude=('tests', 'docs', 'examples')), scripts=[],