From 06fffe5a94b5509d025f4d2f6105a5c636629a80 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 8 Jun 2022 13:36:23 +0200 Subject: [PATCH] Use internal implementation for X25519 key exchanges --- RNS/Cryptography/Curve25519.py | 148 +++++++++++++++++++++++++++++++++ RNS/Cryptography/__init__.py | 10 +++ RNS/Identity.py | 24 ++---- RNS/Link.py | 8 +- 4 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 RNS/Cryptography/Curve25519.py create mode 100644 RNS/Cryptography/__init__.py diff --git a/RNS/Cryptography/Curve25519.py b/RNS/Cryptography/Curve25519.py new file mode 100644 index 0000000..8d93186 --- /dev/null +++ b/RNS/Cryptography/Curve25519.py @@ -0,0 +1,148 @@ +"""A pure Python implementation of Curve25519 +This module supports both a low-level interface through curve25519(base_point, secret) +and curve25519_base(secret) that take 32-byte blocks of data as inputs and a higher +level interface using the X25519PrivateKey and X25519PublicKey classes that are +compatible with the classes in cryptography.hazmat.primitives.asymmetric.x25519 with +the same names. +""" + +# By Nicko van Someren, 2021. This code is released into the public domain. + +# #### WARNING #### + +# Since this code makes use of Python's built-in large integer types, it is NOT EXPECTED +# to run in constant time. While some effort is made to minimise the time variations, +# the underlying math functions are likely to have running times that are highly +# value-dependent, leaving this code potentially vulnerable to timing attacks. If this +# code is to be used to provide cryptographic security in an environment where the start +# and end times of the execution can be guessed, inferred or measured then it is critical +# that steps are taken to hide the execution time, for instance by adding a delay so that +# encrypted packets are not sent until a fixed time after the _start_ of execution. + + +# Implements ladder multiplication as described in "Montgomery curves and the Montgomery +# ladder" by Daniel J. Bernstein and Tanja Lange. https://eprint.iacr.org/2017/293.pdf + +# Curve25519 is a Montgomery curve defined by: +# y**2 = x**3 + A * x**2 + x mod P +# where P = 2**255-19 and A = 486662 + +import os + +P = 2 ** 255 - 19 +_A = 486662 + + +def _point_add(point_n, point_m, point_diff): + """Given the projection of two points and their difference, return their sum""" + (xn, zn) = point_n + (xm, zm) = point_m + (x_diff, z_diff) = point_diff + x = (z_diff << 2) * (xm * xn - zm * zn) ** 2 + z = (x_diff << 2) * (xm * zn - zm * xn) ** 2 + return x % P, z % P + + +def _point_double(point_n): + """Double a point provided in projective coordinates""" + (xn, zn) = point_n + xn2 = xn ** 2 + zn2 = zn ** 2 + x = (xn2 - zn2) ** 2 + xzn = xn * zn + z = 4 * xzn * (xn2 + _A * xzn + zn2) + return x % P, z % P + + +def _const_time_swap(a, b, swap): + """Swap two values in constant time""" + index = int(swap) * 2 + temp = (a, b, b, a) + return temp[index:index+2] + + +def _raw_curve25519(base, n): + """Raise the point base to the power n""" + zero = (1, 0) + one = (base, 1) + mP, m1P = zero, one + + for i in reversed(range(256)): + bit = bool(n & (1 << i)) + mP, m1P = _const_time_swap(mP, m1P, bit) + mP, m1P = _point_double(mP), _point_add(mP, m1P, one) + mP, m1P = _const_time_swap(mP, m1P, bit) + + x, z = mP + inv_z = pow(z, P - 2, P) + return (x * inv_z) % P + + +def _unpack_number(s): + """Unpack 32 bytes to a 256 bit value""" + if len(s) != 32: + raise ValueError('Curve25519 values must be 32 bytes') + return int.from_bytes(s, "little") + + +def _pack_number(n): + """Pack a value into 32 bytes""" + return n.to_bytes(32, "little") + + +def _fix_secret(n): + """Mask a value to be an acceptable exponent""" + n &= ~7 + n &= ~(128 << 8 * 31) + n |= 64 << 8 * 31 + return n + + +def curve25519(base_point_raw, secret_raw): + """Raise the base point to a given power""" + base_point = _unpack_number(base_point_raw) + secret = _fix_secret(_unpack_number(secret_raw)) + return _pack_number(_raw_curve25519(base_point, secret)) + + +def curve25519_base(secret_raw): + """Raise the generator point to a given power""" + secret = _fix_secret(_unpack_number(secret_raw)) + return _pack_number(_raw_curve25519(9, secret)) + + +class X25519PublicKey: + def __init__(self, x): + self.x = x + + @classmethod + def from_public_bytes(cls, data): + return cls(_unpack_number(data)) + + def public_bytes(self): + return _pack_number(self.x) + + +class X25519PrivateKey: + def __init__(self, a): + self.a = a + + @classmethod + def generate(cls): + return cls.from_private_bytes(os.urandom(32)) + + @classmethod + def from_private_bytes(cls, data): + return cls(_fix_secret(_unpack_number(data))) + + def private_bytes(self): + return _pack_number(self.a) + + def public_key(self): + return X25519PublicKey.from_public_bytes(_pack_number(_raw_curve25519(9, self.a))) + + def exchange(self, peer_public_key): + if isinstance(peer_public_key, bytes): + peer_public_key = X25519PublicKey.from_public_bytes(peer_public_key) + + return _pack_number(_raw_curve25519(peer_public_key.x, self.a)) \ No newline at end of file diff --git a/RNS/Cryptography/__init__.py b/RNS/Cryptography/__init__.py new file mode 100644 index 0000000..450ad95 --- /dev/null +++ b/RNS/Cryptography/__init__.py @@ -0,0 +1,10 @@ +import os +import glob + +from .Hashes import sha256 +from .HKDF import hkdf +from .PKCS7 import PKCS7 +from .Fernet import Fernet + +modules = glob.glob(os.path.dirname(__file__)+"/*.py") +__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')] diff --git a/RNS/Identity.py b/RNS/Identity.py index cf7d575..ab10fd9 100644 --- a/RNS/Identity.py +++ b/RNS/Identity.py @@ -28,11 +28,10 @@ import atexit import hashlib from .vendor import umsgpack as umsgpack -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey -from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey +from RNS.Cryptography.Curve25519 import X25519PrivateKey, X25519PublicKey from RNS.Cryptography import Fernet @@ -292,11 +291,7 @@ class Identity: def create_keys(self): self.prv = X25519PrivateKey.generate() - self.prv_bytes = self.prv.private_bytes( - encoding=serialization.Encoding.Raw, - format=serialization.PrivateFormat.Raw, - encryption_algorithm=serialization.NoEncryption() - ) + self.prv_bytes = self.prv.private_bytes() self.sig_prv = Ed25519PrivateKey.generate() self.sig_prv_bytes = self.sig_prv.private_bytes( @@ -306,10 +301,7 @@ class Identity: ) self.pub = self.prv.public_key() - self.pub_bytes = self.pub.public_bytes( - encoding=serialization.Encoding.Raw, - format=serialization.PublicFormat.Raw - ) + self.pub_bytes = self.pub.public_bytes() self.sig_pub = self.sig_prv.public_key() self.sig_pub_bytes = self.sig_pub.public_bytes( @@ -347,10 +339,7 @@ class Identity: self.sig_prv = Ed25519PrivateKey.from_private_bytes(self.sig_prv_bytes) self.pub = self.prv.public_key() - self.pub_bytes = self.pub.public_bytes( - encoding=serialization.Encoding.Raw, - format=serialization.PublicFormat.Raw - ) + self.pub_bytes = self.pub.public_bytes() self.sig_pub = self.sig_prv.public_key() self.sig_pub_bytes = self.sig_pub.public_bytes( @@ -416,10 +405,7 @@ class Identity: """ if self.pub != None: ephemeral_key = X25519PrivateKey.generate() - ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes( - encoding=serialization.Encoding.Raw, - format=serialization.PublicFormat.Raw - ) + ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes() shared_key = ephemeral_key.exchange(self.pub) diff --git a/RNS/Link.py b/RNS/Link.py index f29b39d..7f42356 100644 --- a/RNS/Link.py +++ b/RNS/Link.py @@ -20,12 +20,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey -from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey +from RNS.Cryptography.Curve25519 import X25519PrivateKey, X25519PublicKey from RNS.Cryptography import Fernet from time import sleep @@ -178,10 +177,7 @@ class Link: self.fernet = None self.pub = self.prv.public_key() - self.pub_bytes = self.pub.public_bytes( - encoding=serialization.Encoding.Raw, - format=serialization.PublicFormat.Raw - ) + self.pub_bytes = self.pub.public_bytes() self.sig_pub = self.sig_prv.public_key() self.sig_pub_bytes = self.sig_pub.public_bytes(