This is an automated email from the ASF dual-hosted git repository. sandreoli pushed a commit to branch add-amcl-wrappers in repository https://gitbox.apache.org/repos/asf/incubator-milagro-MPC.git
commit 7bad7517bd7242d6ded85e369b67d49df1268a26 Author: Samuele Andreoli <[email protected]> AuthorDate: Wed Mar 4 09:56:24 2020 +0000 Add RSA wrappers --- README.md | 16 ++ cmake/PythonParameters.cmake | 45 ++++++ python/CMakeLists.txt | 7 + python/amcl/CMakeLists.txt | 9 +- python/amcl/rsa.py.in | 323 ++++++++++++++++++++++++++++++++++++++ python/benchmark/CMakeLists.txt | 5 + python/benchmark/bench_rsa.py.in | 67 ++++++++ python/examples/CMakeLists.txt | 5 + python/examples/example_rsa.py.in | 56 +++++++ python/test/CMakeLists.txt | 7 + python/test/test_rsa.py.in | 143 +++++++++++++++++ 11 files changed, 681 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 760f798..d46afd8 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,22 @@ docker rm -f ${CONTAINER_ID} || true ## Python There is a Python wrapper in ./python. +You can to specify the RSA levels to build in the wrappers using +the cmake flag `PYTHON_RSA_LEVELS`. Supported levels are 2048 and 4096. +E.g. + +``` +cmake -DPYTHON_RSA_LEVELS="2048,4096" .. +``` + +In order for the RSA wrappers to work, the appropriate dynamic +libraries need to be generated and installed for AMCL. For instance, to +install the dynamic libraries for RSA 2048 and 4069, modify the AMCL cmake +build as follows. + +``` +cmake -D CMAKE_BUILD_TYPE=Release -D BUILD_SHARED_LIBS=ON -D AMCL_CHUNK=64 -D AMCL_CURVE="BLS381,SECP256K1" -D AMCL_RSA="2048,4096" -D BUILD_PAILLIER=ON -D BUILD_PYTHON=ON -D BUILD_BLS=ON -D BUILD_WCC=OFF -D BUILD_MPIN=ON -D BUILD_X509=OFF -D CMAKE_INSTALL_PREFIX=/usr/local .. +``` ## Virtual machine diff --git a/cmake/PythonParameters.cmake b/cmake/PythonParameters.cmake new file mode 100644 index 0000000..cccae1b --- /dev/null +++ b/cmake/PythonParameters.cmake @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set(PYTHON_RSA_FIELDS TB TFF BASE ML HML) +set(PYTHON_RSA_2048 1024 2048 58 2 1 ) +set(PYTHON_RSA_4096 512 4096 60 8 4 ) + +# Load RSA parameter in parent scope +function(load_rsa_fields level) + if (NOT PYTHON_RSA_${level}) + message(FATAL_ERROR "Invalid RSA level: ${level}") + endif() + + foreach(field ${PYTHON_RSA_FIELDS}) + list(FIND PYTHON_RSA_FIELDS "${field}" index) + list(GET PYTHON_RSA_${level} ${index} ${field}) + set("${field}" "${${field}}" PARENT_SCOPE) + endforeach() + + set(BD "${TB}_${BASE}" PARENT_SCOPE) +endfunction() + +# Configure file +macro(configure_rsa_file source target) + configure_file("${source}" "${target}" @ONLY) + file(READ "${target}" temp) + string(REPLACE WWW "${TFF}" temp "${temp}") + string(REPLACE XXX "${BD}" temp "${temp}") + + file(WRITE "${target}" "${temp}") +endmacro() \ No newline at end of file diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 5971f47..5c5401e 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -16,6 +16,13 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) include(PythonSiteDirs) +include(PythonParameters) + +if(NOT DEFINED PYTHON_RSA_LEVELS) + set(PYTHON_RSA_LEVELS "") +endif() + +string(REPLACE "," ";" PYTHON_RSA_LEVELS "${PYTHON_RSA_LEVELS}") add_subdirectory(amcl) add_subdirectory(test) diff --git a/python/amcl/CMakeLists.txt b/python/amcl/CMakeLists.txt index be778cc..c5481d2 100644 --- a/python/amcl/CMakeLists.txt +++ b/python/amcl/CMakeLists.txt @@ -16,12 +16,17 @@ file(GLOB SRCS *.py) file(COPY ${SRCS} DESTINATION "${PROJECT_BINARY_DIR}/python/amcl") +foreach(level ${PYTHON_RSA_LEVELS}) + load_rsa_fields(${level}) + configure_rsa_file("rsa.py.in" "${PROJECT_BINARY_DIR}/python/amcl/rsa_${TFF}.py") +endforeach() + install(DIRECTORY DESTINATION ${PYTHON_SITE_PACKAGES}/amcl DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) - + install(FILES ${SRCS} DESTINATION ${PYTHON_SITE_PACKAGES}/amcl PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE - GROUP_READ + GROUP_READ WORLD_READ) diff --git a/python/amcl/rsa.py.in b/python/amcl/rsa.py.in new file mode 100644 index 0000000..b4f3f53 --- /dev/null +++ b/python/amcl/rsa.py.in @@ -0,0 +1,323 @@ +""" +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://WWW.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +""" + +""" + +This module use cffi to access the c functions for the amcl RSA. + +""" + +from . import core_utils +import platform + +_ffi = core_utils._ffi +_ffi.cdef(""" +#define FFLEN_WWW @ML@ +#define HFLEN_WWW @HML@ + +typedef signed int sign32; + +typedef struct +{ + sign32 e; + BIG_XXX n[FFLEN_WWW]; +} rsa_public_key_WWW; + +typedef struct +{ + BIG_XXX p[HFLEN_WWW]; + BIG_XXX q[HFLEN_WWW]; + BIG_XXX dp[HFLEN_WWW]; + BIG_XXX dq[HFLEN_WWW]; + BIG_XXX c[HFLEN_WWW]; +} rsa_private_key_WWW; + +extern void FF_WWW_toOctet(octet *X, BIG_XXX *x, int n); + +extern void RSA_WWW_KEY_PAIR(csprng *R,sign32 e,rsa_private_key_WWW* PRIV,rsa_public_key_WWW* PUB,octet *P, octet* Q); +extern void RSA_WWW_ENCRYPT(rsa_public_key_WWW* PUB,octet *F,octet *G); +extern void RSA_WWW_DECRYPT(rsa_private_key_WWW* PRIV,octet *G,octet *F); +extern void RSA_WWW_PRIVATE_KEY_KILL(rsa_private_key_WWW *PRIV); +extern void RSA_WWW_fromOctet(BIG_XXX *x,octet *S); + +extern int OAEP_ENCODE(int h,octet *M,csprng *R,octet *P,octet *F); +extern int OAEP_DECODE(int h,octet *P,octet *F); +""") + +if (platform.system() == 'Windows'): + _libamcl_rsa_WWW = _ffi.dlopen("libamcl_rsa_WWW.dll") +elif (platform.system() == 'Darwin'): + _libamcl_rsa_WWW = _ffi.dlopen("libamcl_rsa_WWW.dylib") +else: + _libamcl_rsa_WWW = _ffi.dlopen("libamcl_rsa_WWW.so") + +# Constants +FFLEN = @ML@ # FF size in BIGs +FS = @TFF@ // 8 # FF size in bytes +SHA256 = 32 +SHA384 = 48 +SHA512 = 64 + +OK = 0 +FAIL = -1 + + +def generate_key_pair(rng, e, p = None, q = None): + """ Generate an RSA key pair + + Generate an RSA key pair with encryption exponent e + + Args:: + + rng: pointer to cryptographically secure prng + e: decryption exponent. Integer + p: Secret prime for the RSA modulus + q: Secret prime for the RSA modulus + + Returns:: + public_key: pointer to an RSA public key + private_key: pointer to an RSA private key + + Raises:: + Exception + """ + if p and q: + p_oct, p_oct_val = core_utils.make_octet(None, p) + q_oct, q_oct_val = core_utils.make_octet(None, q) + _ = p_oct_val, q_oct_val + rng = _ffi.NULL + else: + p_oct = _ffi.NULL + q_oct = _ffi.NULL + + public_key = _ffi.new('rsa_public_key_WWW*') + private_key = _ffi.new('rsa_private_key_WWW*') + + _libamcl_rsa_WWW.RSA_WWW_KEY_PAIR(rng, _ffi.cast("sign32", e), private_key, public_key, p_oct, q_oct) + + if p_oct is not _ffi.NULL: + core_utils.clear_octet(p_oct) + + if q_oct is not _ffi.NULL: + core_utils.clear_octet(q_oct) + + return public_key, private_key + + +def encrypt(public_key, pt): + """ RSA Encrypt + + Encrypt a message pt to the given public key + + Args:: + + public_key: RSA public key + pt: input padded message. SHA bytes + + Returns:: + ct: output ciphertext + + Raises:: + Exception + """ + pt_oct, pt_val = core_utils.make_octet(None, pt) + ct_oct, ct_val = core_utils.make_octet(FS) + _ = pt_val, ct_val + + _libamcl_rsa_WWW.RSA_WWW_ENCRYPT(public_key, pt_oct, ct_oct) + + core_utils.clear_octet(pt_oct) + + return core_utils.to_str(ct_oct) + + +def decrypt(private_key, ct): + """ RSA Decrypt + + Decrypt a ciphertext ct using the given private key + + Args:: + + private_key: RSA private key + ct: input ciphertext + + Returns:: + pt: output plaintext. SHA bytes + + Raises:: + Exception + """ + pt_oct, pt_val = core_utils.make_octet(FS) + ct_oct, ct_val = core_utils.make_octet(None, ct) + _ = pt_val, ct_val + + _libamcl_rsa_WWW.RSA_WWW_DECRYPT(private_key, ct_oct, pt_oct) + + pt = core_utils.to_str(pt_oct) + + # Clear memory + core_utils.clear_octet(pt_oct) + + return pt + + +def kill_private_key(private_key): + """ Kill RSA Private Key + + Clean secrets from an RSA private key + + Args:: + + private_key: RSA private key to kill + + Raises:: + Exception + """ + _libamcl_rsa_WWW.RSA_WWW_PRIVATE_KEY_KILL(private_key) + +def public_key_to_bytes(public_key): + """ Export public key to bytes + + Export the public key modulus as bytes. + The public key exponent can be accessed as an integer + + Args:: + + public_key: RSA private key to export + + Returns:: + + n: public modulus of the public key + + Raises:: + Exception + """ + n_oct, n_val = core_utils.make_octet(FS) + _ = n_val + + _libamcl_rsa_WWW.FF_WWW_toOctet(n_oct, public_key.n, FFLEN) + + return core_utils.to_str(n_oct) + +def public_key_from_bytes(n): + """ Import public key from bytes + + Import the public key modulus from bytes. + The public key exponent can be directly set as an integer + + Args:: + + n: public modulus of the public key + + Returns:: + + public_key: imported public key + + Raises:: + Exception + """ + n_oct, n_val = core_utils.make_octet(None, n) + _ = n_val + + public_key = _ffi.new('rsa_public_key_WWW*') + + _libamcl_rsa_WWW.RSA_WWW_fromOctet(public_key.n, n_oct) + + return public_key + +def oaep_encode(rng, sha, m, params=None): + """ Apply OAEP padding to the given message m + + OAEP padding of the message m for RSA encryption. + + Args:: + + rng: pointer to cryptograpically secure PRNG + sha: hash type. Supported types are SHA256, SHA384 and SHA512 + m: message to pad + params: optional parameter string for padding + + Returns:: + + pt: padded message + rc: 0 if the message was succesfully padded or an error code + + Raises:: + Exception + """ + if params is None: + p_oct = _ffi.NULL + else: + p_oct, p_val = core_utils.make_octet(None, params) + _ = p_val + + m_oct, m_val = core_utils.make_octet(None, m) + pt_oct, pt_val = core_utils.make_octet(FS) + _ = m_val, pt_val + + rc = _libamcl_rsa_WWW.OAEP_ENCODE(sha, m_oct, rng, p_oct, pt_oct) + if rc != 0: + return None, FAIL + + pt = core_utils.to_str(pt_oct) + + # Clean memory + core_utils.clear_octet(pt_oct) + core_utils.clear_octet(m_oct) + + return pt, OK + + +def oaep_decode(sha, pt, params=None): + """ Remove OAEP padding from the given plaintext pt + + OAEP unpadding of the plaintext pt to recover the message m + + Args:: + + sha: hash type. Supported types are SHA256, SHA384 and SHA512 + pt: plaintext from RSA decryption + + Returns:: + + m: unpadded message + rc: 0 if the message was succesfully unpadded or an error code + + Raises:: + Exception + """ + if params is None: + p_oct = _ffi.NULL + else: + p_oct, p_val = core_utils.make_octet(None, params) + _ = p_val + + pt_oct, pt_val = core_utils.make_octet(None, pt) + _ = pt_val + + rc = _libamcl_rsa_WWW.OAEP_DECODE(sha, p_oct, pt_oct) + if rc != 0: + return None, FAIL + + m = core_utils.to_str(pt_oct) + + # Clear memory + core_utils.clear_octet(pt_oct) + + return m, OK diff --git a/python/benchmark/CMakeLists.txt b/python/benchmark/CMakeLists.txt index 8a39633..3dd91bd 100644 --- a/python/benchmark/CMakeLists.txt +++ b/python/benchmark/CMakeLists.txt @@ -15,3 +15,8 @@ file(GLOB BENCH *.py) file(COPY ${BENCH} DESTINATION "${PROJECT_BINARY_DIR}/python/benchmark") + +foreach(level ${PYTHON_RSA_LEVELS}) + load_rsa_fields(${level}) + configure_rsa_file("bench_rsa.py.in" "${PROJECT_BINARY_DIR}/python/benchmark/bench_rsa_${TFF}.py") +endforeach() diff --git a/python/benchmark/bench_rsa.py.in b/python/benchmark/bench_rsa.py.in new file mode 100755 index 0000000..754928b --- /dev/null +++ b/python/benchmark/bench_rsa.py.in @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +""" +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +""" + +import os +import sys +from bench import time_func + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from amcl import core_utils, rsa_WWW + +pt_2048_hex = "53ea5dc08cd260fb3b858567287fa91552c30b2febfba213f0ae87702d068d19bab07fe574523dfb42139d68c3c5afeee0bfe4cb7969cbf382b804d6e61396144e2d0e60741f8993c3014b58b9b1957a8babcd23af854f4c356fb1662aa72bfcc7e586559dc4280d160c126785a723ebeebeff71f11594440aaef87d10793a8774a239d4a04c87fe1467b9daf85208ec6c7255794a96cc29142f9a8bd418e3c1fd67344b0cd0829df3b2bec60253196293c6b34d3f75d32f213dd45c6273d505adf4cced1057cb758fc26aeefa441255ed4e64c199ee075e7f16646182fdb464739b68ab5daff0e63e9552016824f [...] +pt_4096_hex = "53ea5dc08cd260fb3b858567287fa91552c30b2febfba213f0ae87702d068d19bab07fe574523dfb42139d68c3c5afeee0bfe4cb7969cbf382b804d6e61396144e2d0e60741f8993c3014b58b9b1957a8babcd23af854f4c356fb1662aa72bfcc7e586559dc4280d160c126785a723ebeebeff71f11594440aaef87d10793a8774a239d4a04c87fe1467b9daf85208ec6c7255794a96cc29142f9a8bd418e3c1fd67344b0cd0829df3b2bec60253196293c6b34d3f75d32f213dd45c6273d505adf4cced1057cb758fc26aeefa441255ed4e64c199ee075e7f16646182fdb464739b68ab5daff0e63e9552016824f [...] +e = 0x10001 + +seed_hex = "78d0fb6705ce77dee47d03eb5b9c5d30" + +if __name__ == "__main__": + pt = bytes.fromhex(pt_WWW_hex) + seed = bytes.fromhex(seed_hex) + + rng = core_utils.create_csprng(seed) + + m = b'test message' + + # Generate quantities for bench run + public_key, private_key = rsa_WWW.generate_key_pair(rng, e) + + enc, rc = rsa_WWW.oaep_encode(rng, rsa_WWW.SHA256, m) + assert rc == 0, 'OAEP encode failure' + + _, rc = rsa_WWW.oaep_decode(rsa_WWW.SHA256, enc) + assert rc == 0, 'OAEP decode failure' + + # Run benchmark + fncall = lambda: rsa_WWW.generate_key_pair(rng, e) + time_func("rsa_WWW.generate_key_pair", fncall) + + fncall = lambda: rsa_WWW.encrypt(public_key, pt) + time_func("rsa_WWW.encrypt ", fncall, unit = 'us') + + fncall = lambda: rsa_WWW.decrypt(private_key, pt) + time_func("rsa_WWW.decrypt ", fncall) + + fncall = lambda: rsa_WWW.oaep_encode(rng, rsa_WWW.SHA256, m) + time_func("rsa_WWW.oaep_encode ", fncall, unit = 'us') + + fncall = lambda: rsa_WWW.oaep_decode(rsa_WWW.SHA256, enc) + time_func("rsa_WWW.oaep_decode ", fncall, unit = 'us') \ No newline at end of file diff --git a/python/examples/CMakeLists.txt b/python/examples/CMakeLists.txt index 1d768ba..763c69b 100644 --- a/python/examples/CMakeLists.txt +++ b/python/examples/CMakeLists.txt @@ -15,3 +15,8 @@ file(GLOB EXAMPLES *.py) file(COPY ${EXAMPLES} DESTINATION "${PROJECT_BINARY_DIR}/python/examples") + +foreach(level ${PYTHON_RSA_LEVELS}) + load_rsa_fields(${level}) + configure_rsa_file("example_rsa.py.in" "${PROJECT_BINARY_DIR}/python/examples/example_rsa_${TFF}.py") +endforeach() diff --git a/python/examples/example_rsa.py.in b/python/examples/example_rsa.py.in new file mode 100755 index 0000000..66025d9 --- /dev/null +++ b/python/examples/example_rsa.py.in @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +""" +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +""" + +import os +import sys + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from amcl import core_utils, rsa_WWW + +seed_hex = "78d0fb6705ce77dee47d03eb5b9c5d30" + +e = 0x10001 + +if __name__ == "__main__": + seed = bytes.fromhex(seed_hex) + rng = core_utils.create_csprng(seed) + + m = b'test message' + + print('Generate key pair') + public_key, private_key = rsa_WWW.generate_key_pair(rng, e) + + print(f"\nEncode message '{m.decode('utf-8')}'") + pt, rc = rsa_WWW.oaep_encode(rng, rsa_WWW.SHA256, m) + assert rc == 0, 'Failure OAEP padding message' + + print(f"\nEncrypt plaintext '{pt.hex()}'") + ct = rsa_WWW.encrypt(public_key, pt) + + print(f"\nDecrypt cyphertext {ct.hex()}") + dec_pt = rsa_WWW.decrypt(private_key, ct) + + print(f"\nDecode plaintext '{dec_pt.hex()}'") + dec_m, rc = rsa_WWW.oaep_decode(rsa_WWW.SHA256, dec_pt) + assert rc == 0, 'Failure OAEP unpadding message' + + print(f"Recovered message '{dec_m.decode('utf-8')}'") diff --git a/python/test/CMakeLists.txt b/python/test/CMakeLists.txt index bdc05c3..ced24cd 100644 --- a/python/test/CMakeLists.txt +++ b/python/test/CMakeLists.txt @@ -67,3 +67,10 @@ if(NOT CMAKE_BUILD_TYPE STREQUAL "ASan") add_python_test(test_python_mpc_nm_commit test_nm_commit.py) add_python_test(test_python_mpc_zk_factoring test_zk_factoring.py) endif(NOT CMAKE_BUILD_TYPE STREQUAL "ASan") + +foreach(level ${PYTHON_RSA_LEVELS}) + load_rsa_fields(${level}) + configure_rsa_file("test_rsa.py.in" "${PROJECT_BINARY_DIR}/python/test/test_rsa_${TFF}.py") + + add_python_test(test_python_rsa_${TFF} test_rsa_${TFF}.py) +endforeach() diff --git a/python/test/test_rsa.py.in b/python/test/test_rsa.py.in new file mode 100755 index 0000000..0e53533 --- /dev/null +++ b/python/test/test_rsa.py.in @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 + +""" +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +""" + +import os +import sys +import json +import unittest + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from amcl import core_utils, rsa_WWW + +p_2048_hex = "94f689d07ba20cf7c7ca7ccbed22ae6b40c426db74eaee4ce0ced2b6f52a5e136663f5f1ef379cdbb0c4fdd6e4074d6cff21082d4803d43d89e42fd8dfa82b135aa31a8844ffea25f255f956cbc1b9d8631d01baf1010d028a190b94ce40f3b72897e8196df19edf1ff62e6556f2701d52cef1442e3301db7608ecbdcca703db" +q_2048_hex = "9a9ad73f246df853e129c589925fdad9df05606a61081e62e72be4fb33f6e5ec492cc734f28bfb71fbe2ba9a11e4c02e2c0d103a5cbb0a9d6402c07de63b1b995dd72ac8f29825d66923a088b421fb4d52b0b855d2f5dde2be9b0ca0cee6f7a94e5566735fe6cff1fcad3199602f88528d19aa8d0263adff8f5053c38254a2a3" +pt_2048_hex = "53ea5dc08cd260fb3b858567287fa91552c30b2febfba213f0ae87702d068d19bab07fe574523dfb42139d68c3c5afeee0bfe4cb7969cbf382b804d6e61396144e2d0e60741f8993c3014b58b9b1957a8babcd23af854f4c356fb1662aa72bfcc7e586559dc4280d160c126785a723ebeebeff71f11594440aaef87d10793a8774a239d4a04c87fe1467b9daf85208ec6c7255794a96cc29142f9a8bd418e3c1fd67344b0cd0829df3b2bec60253196293c6b34d3f75d32f213dd45c6273d505adf4cced1057cb758fc26aeefa441255ed4e64c199ee075e7f16646182fdb464739b68ab5daff0e63e9552016824f [...] + +p_4096_hex = "b18f69bd4e52677d48d846055988877ce9e97b962f01e3f425f3101a6a589f020c858b1ee5ae8f79e4c63ce2356d8a9a4ef144d3a55e05badfbebdb0e97594cdb4ebebd6177b2eb04149aa463ede7ba2216657e3b4de42f496c0d493b4d734131e63edcde042d951b9bf285622b9d69e9ee170156deeb173725032a952068e685aa31a8844ffea25f255f956cbc1b9d8631d01baf1010d028a190b94ce40f3b72897e8196df19edf1ff62e6556f2701d52cef1442e3301db7608ecbdcca6ef9994f689d07ba20cf7c7ca7ccbed22ae6b40c426db74eaee4ce0ced2b6f52a5e136663f5f1ef379cdbb0c4fdd6e4074 [...] +q_4096_hex = "e87190e478b1132e3c05ade06a196858b4d24a4c8350ce9ecda7f0a1c4e3e75c136c250dd8b67e377670021e4810e0f19f3ecdc780b836febc939fc7ad7c70300323bf4b24f03e8656bb49614fcbfe0687fef150ce34e646806a2b4369259ecc2c01c796be2a2317f4a9974f4ee101a63ac1383091fde717dac1fe529abb6a276559c8185776c332b98f51d55c85311af1138e9a8858693142d0109383929143d17ed7645d22afcad045d85eba7c5df02ed0bd4d9a8f22d30865d538ba933a1579377f979390894ab558922352acaa05d94aa8fa9d273f35912d5efabaaf647ebdb03e55db04941df0409bc2a124a [...] +pt_4096_hex = "53ea5dc08cd260fb3b858567287fa91552c30b2febfba213f0ae87702d068d19bab07fe574523dfb42139d68c3c5afeee0bfe4cb7969cbf382b804d6e61396144e2d0e60741f8993c3014b58b9b1957a8babcd23af854f4c356fb1662aa72bfcc7e586559dc4280d160c126785a723ebeebeff71f11594440aaef87d10793a8774a239d4a04c87fe1467b9daf85208ec6c7255794a96cc29142f9a8bd418e3c1fd67344b0cd0829df3b2bec60253196293c6b34d3f75d32f213dd45c6273d505adf4cced1057cb758fc26aeefa441255ed4e64c199ee075e7f16646182fdb464739b68ab5daff0e63e9552016824f [...] + +e = 0x10001 + + +class TestBareRSA(unittest.TestCase): + """ Test RSA2048 API """ + + def setUp(self): + # Deterministic PRNG for testing purposes + seed_hex = "78d0fb6705ce77dee47d03eb5b9c5d30" + seed = bytes.fromhex(seed_hex) + self.rng = core_utils.create_csprng(seed) + + self.p = bytes.fromhex(p_WWW_hex) + self.q = bytes.fromhex(q_WWW_hex) + self.pt = bytes.fromhex(pt_WWW_hex) + + def test_pq(self): + public_key, private_key = rsa_WWW.generate_key_pair(None, e, p=self.p, q=self.q) + + ct = rsa_WWW.encrypt(public_key, self.pt) + pt = rsa_WWW.decrypt(private_key, ct) + self.assertEqual(pt, self.pt) + + def test_rng(self): + public_key, private_key = rsa_WWW.generate_key_pair(self.rng, e) + + ct = rsa_WWW.encrypt(public_key, self.pt) + pt = rsa_WWW.decrypt(private_key, ct) + self.assertEqual(pt, self.pt) + + +class TestOAEP(unittest.TestCase): + """ Test RSA2048 OAEP encryption/decryption """ + + def setUp(self): + # Deterministic PRNG for testing purposes + seed_hex = "78d0fb6705ce77dee47d03eb5b9c5d30" + seed = bytes.fromhex(seed_hex) + self.rng = core_utils.create_csprng(seed) + + self.p = bytes.fromhex(p_WWW_hex) + self.q = bytes.fromhex(q_WWW_hex) + self.pt = bytes.fromhex(pt_WWW_hex) + + self.long_bytes = ('a'*(rsa_WWW.FS + 1)).encode('utf-8') + + def test_consistency(self): + m = b'test_message' + pt, rc = rsa_WWW.oaep_encode(self.rng, rsa_WWW.SHA256, m) + self.assertEqual(rc, 0) + + m_dec, rc = rsa_WWW.oaep_decode(rsa_WWW.SHA256, pt) + self.assertEqual(rc, 0) + self.assertEqual(m, m_dec) + + def test_encryption_decryption(self): + public_key, private_key = rsa_WWW.generate_key_pair(None, e, self.p, self.q) + + m = b'test_encryption_decryption' + pt, rc = rsa_WWW.oaep_encode(self.rng, rsa_WWW.SHA256, m) + self.assertEqual(rc, 0) + + ct = rsa_WWW.encrypt(public_key, pt) + pt_dec = rsa_WWW.decrypt(private_key, ct) + self.assertEqual(pt_dec, pt) + + m_dec, rc = rsa_WWW.oaep_decode(rsa_WWW.SHA256, pt_dec) + self.assertEqual(rc, 0) + self.assertEqual(m_dec, m) + + def test_encoding_error(self): + enc, rc = rsa_WWW.oaep_encode(self.rng, rsa_WWW.SHA256, self.long_bytes) + self.assertEqual(rc, -1) + self.assertEqual(enc, None) + + def test_decoding_error(self): + dec, rc = rsa_WWW.oaep_decode(rsa_WWW.SHA256, self.long_bytes) + self.assertEqual(rc, -1) + self.assertEqual(dec, None) + + +class TestIO(unittest.TestCase): + """ Test RSA2048 I/O for public key """ + + def setUp(self): + p = bytes.fromhex(p_WWW_hex) + q = bytes.fromhex(q_WWW_hex) + self.public_key, self.private_key = rsa_WWW.generate_key_pair(None, e, p, q) + + self.pt = bytes.fromhex(pt_WWW_hex) + + def test_consistency(self): + n = rsa_WWW.public_key_to_bytes(self.public_key) + e = self.public_key.e + + public_key = rsa_WWW.public_key_from_bytes(n) + public_key.e = e + + ct = rsa_WWW.encrypt(public_key, self.pt) + pt = rsa_WWW.decrypt(self.private_key, ct) + self.assertEqual(pt, self.pt) + +if __name__ == '__main__': + # Run tests + unittest.main()
