You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@milagro.apache.org by sa...@apache.org on 2020/03/04 10:36:32 UTC
[incubator-milagro-MPC] 07/07: Add RSA wrappers
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 <sa...@yahoo.it>
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()