2022-06-08 13:36:23 +02:00
|
|
|
# By Nicko van Someren, 2021. This code is released into the public domain.
|
2022-06-08 15:52:37 +02:00
|
|
|
# Small modifications for use in Reticulum, and constant time key exchange
|
|
|
|
# added by Mark Qvist in 2022.
|
2022-06-08 13:36:23 +02:00
|
|
|
|
2022-06-08 15:52:37 +02:00
|
|
|
# WARNING! Only the X25519PrivateKey.exchange() method attempts to hide execution time.
|
|
|
|
# In the context of Reticulum, this is sufficient, but it may not be in other systems. 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.
|
2022-06-08 13:36:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
import os
|
2022-06-08 15:52:37 +02:00
|
|
|
import time
|
2022-06-08 13:36:23 +02:00
|
|
|
|
|
|
|
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:
|
2022-06-08 15:52:37 +02:00
|
|
|
MIN_EXEC_TIME = 0.002
|
|
|
|
MAX_EXEC_TIME = 0.5
|
|
|
|
DELAY_WINDOW = 10
|
|
|
|
|
|
|
|
T_CLEAR = None
|
|
|
|
T_MAX = 0
|
|
|
|
|
2022-06-08 13:36:23 +02:00
|
|
|
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)
|
|
|
|
|
2022-06-08 15:52:37 +02:00
|
|
|
start = time.time()
|
|
|
|
|
|
|
|
shared = _pack_number(_raw_curve25519(peer_public_key.x, self.a))
|
|
|
|
|
|
|
|
end = time.time()
|
|
|
|
duration = end-start
|
|
|
|
|
|
|
|
if X25519PrivateKey.T_CLEAR == None:
|
|
|
|
X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
|
|
|
|
|
|
|
|
if end > X25519PrivateKey.T_CLEAR:
|
|
|
|
X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
|
|
|
|
X25519PrivateKey.T_MAX = 0
|
|
|
|
|
|
|
|
if duration < X25519PrivateKey.T_MAX or duration < X25519PrivateKey.MIN_EXEC_TIME:
|
|
|
|
target = start+X25519PrivateKey.T_MAX
|
|
|
|
|
|
|
|
if target > start+X25519PrivateKey.MAX_EXEC_TIME:
|
|
|
|
target = start+X25519PrivateKey.MAX_EXEC_TIME
|
|
|
|
|
|
|
|
if target < start+X25519PrivateKey.MIN_EXEC_TIME:
|
|
|
|
target = start+X25519PrivateKey.MIN_EXEC_TIME
|
|
|
|
|
|
|
|
try:
|
|
|
|
time.sleep(target-time.time())
|
|
|
|
except Exception as e:
|
|
|
|
pass
|
|
|
|
|
|
|
|
elif duration > X25519PrivateKey.T_MAX:
|
|
|
|
X25519PrivateKey.T_MAX = duration
|
|
|
|
|
|
|
|
return shared
|