#!/usr/bin/python3
#
#
# Helper routines for ModExpNG randomized test vector generator.
#
#
# Copyright (c) 2019, NORDUnet A/S
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# - Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# - Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# - Neither the name of the NORDUnet nor the names of its contributors may
# be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import sys
import random
import subprocess
from enum import Enum, auto
class VectorPiece(Enum):
VectorPieceOther = auto()
VectorPieceN = auto()
VectorPieceD = auto()
VectorPieceP = auto()
VectorPieceQ = auto()
VectorPieceDP = auto()
VectorPieceDQ = auto()
VectorPieceQINV = auto()
class Vector:
# public exponent
_f4 = 0x10001
def __init__(self, length):
self._bits = length
self._n = ""
self._d = ""
self._p = ""
self._q = ""
self._dp = ""
self._dq = ""
self._qinv = ""
def _add_piece(self, type, value):
value = value.replace(":", "")
value = value.replace("\r", "")
value = value.replace("\n", "")
value = value.replace(" ", "")
if type == VectorPiece.VectorPieceN: self._n += value
elif type == VectorPiece.VectorPieceD: self._d += value
elif type == VectorPiece.VectorPieceP: self._p += value
elif type == VectorPiece.VectorPieceQ: self._q += value
elif type == VectorPiece.VectorPieceDP: self._dp += value
elif type == VectorPiece.VectorPieceDQ: self._dq += value
elif type == VectorPiece.VectorPieceQINV: self._qinv += value
else: raise Exception("Invalid vector piece type!")
def _calc_mont_factor(self, length, modulus):
return pow(2, 2*length, modulus)
def _calc_mod_coeff(self, length, modulus):
pwr = pow(2, length)
pwr_mask = pwr - 1
r = 1
b = 1
nn = ((modulus ^ pwr_mask) + 1) % pwr
for i in range(1, length):
b = (b << 1) % pwr
t = (r * nn) % pwr
if t & (1 << i): r += b
return r
def _calc_blind_y(self, x, modulus):
x_inv = self._modinv(x, modulus)
return pow(x_inv, self._f4, modulus)
def _egcd(self, a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = self._egcd(b % a, a)
return (g, x - (b // a) * y, y)
def _modinv(self, a, m):
g, x, y = self._egcd(a, m)
if g != 1:
raise Exception("_modinv() failed!")
else:
return x % m
def selfcheck(self, message, blinding):
self.m = message # message (padded)
self.n = int(self._n, 16) # modulus
self.d = int(self._d, 16) # private key
self.p = int(self._p, 16) # part of modulus
self.q = int(self._q, 16) # part of modulus
self.dp = int(self._dp, 16) # smaller exponent
self.dq = int(self._dq, 16) # smaller exponent
self.qinv = int(self._qinv, 16) # helper coefficient
self.x = blinding
self.y = self._calc_blind_y(self.x, self.n)
# check modulus
if self.n == 0:
print("ERROR: n == 0")
return False
if self.n != self.p * self.q:
print("ERROR: n != (p * q)")
return False
# check smaller exponents
if self.dp != (self.d % (self.p-1)):
print("ERROR: dp != (d % (p-1))")
return False
if self.dq != (self.d % (self.q-1)):
print("ERROR: dq != (d % (q-1))")
return False
# sign to obtain known good value
s_reference = pow(message, self.d, self.n)
# blind message
message_blinded = (message * self.y) % self.n
# sign blinded message
s_blinded = pow(message_blinded, self.d, self.n)
# unblind signature
s_unblinded = (s_blinded * self.x) % self.n
# check, that x and y actually work
if s_unblinded != s_reference:
print("ERROR: s_unblinded != s_reference!")
return False
# try to do crt with the blinded message
sp_blinded = pow(message_blinded, self.dp, self.p)
sq_blinded = pow(message_blinded, self.dq, self.q)
# recover full blinded signature
sr_blinded = sp_blinded - sq_blinded
if sr_blinded < 0: sr_blinded += self.p
sr_qinv_blinded = (sr_blinded * self.qinv) % self.p
s_crt_blinded = sq_blinded + self.q * sr_qinv_blinded
# unblind crt signature
s_crt_unblinded = (s_crt_blinded * self.x) % self.n
if s_crt_unblinded != s_reference:
print("ERROR: s_crt_unblinded != s_reference!")
return False
self.n_factor = self._calc_mont_factor(self._bits + 16, self.n)
self.p_factor = self._calc_mont_factor(self._bits // 2 + 16, self.p)
self.q_factor = self._calc_mont_factor(self._bits // 2 + 16, self.q)
self.n_coeff = self._calc_mod_coeff(self._bits + 16, self.n)
self.p_coeff = self._calc_mod_coeff(self._bits // 2 + 16, self.p)
self.q_coeff = self._calc_mod_coeff(self._bits // 2 + 16, self.q)
print("Test vector checked.")
return True
def openssl_binary(usage):
# nothing so far
openssl = ""
# user didn't overide anything
if len(sys.argv) == 1:
openssl = "openssl"
print("Using system OpenSSL library.")
# user requested some specific binary
elif len(sys.argv) == 2:
openssl = sys.argv[1]
print("Using OpenSSL binary '" + openssl + "'...")
# didn't understand command line
else:
print(usage)
# return path to selected binary (if any)
return openssl
def openssl_genrsa(binary, length):
filename = str(length) + "_randomized.key"
subprocess.call([binary, "genrsa", "-out", filename, str(length)])
def random_message(seed, length):
message = 0
num_bytes = length // 8 - 1
random.seed(seed)
for i in range(num_bytes):
message <<= 8
message += random.getrandbits(8)
return message
def random_blinding(seed, length):
blinding = 0
num_bytes = length // 8 - 1
random.seed(seed)
for i in range(num_bytes):
blinding <<= 8
blinding += random.getrandbits(8)
return blinding
def load_vector(binary, length):
vector = Vector(length)
piece_type = VectorPiece.VectorPieceOther
filename = str(length) + "_randomized.key"
openssl_command = [binary, "rsa", "-in", filename, "-noout", "-text"]
openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8").splitlines()
for line in openssl_stdout:
if line.startswith("RSA Private-Key:"): piece_type = VectorPiece.VectorPieceOther
elif line.startswith("modulus:"): piece_type = VectorPiece.VectorPieceN
elif line.startswith("publicExponent:"): piece_type = VectorPiece.VectorPieceOther
elif line.startswith("privateExponent:"): piece_type = VectorPiece.VectorPieceD
elif line.startswith("prime1:"): piece_type = VectorPiece.VectorPieceP
elif line.startswith("prime2:"): piece_type = VectorPiece.VectorPieceQ
elif line.startswith("exponent1:"): piece_type = VectorPiece.VectorPieceDP
elif line.startswith("exponent2:"): piece_type = VectorPiece.VectorPieceDQ
elif line.startswith("coefficient:"): piece_type = VectorPiece.VectorPieceQINV
else: vector._add_piece(piece_type, line)
return vector
def save_vector(vector):
filename = "vector_" + str(vector._bits) + "_randomized.py"
print("Writing to '%s'..." % filename)
f = open(filename, 'w')
f.write("# Generated automatically, do not edit.\n\n")
f.write("class Vector:\n")
f.write(" m = 0x%x\n" % vector.m)
f.write(" n = 0x%x\n" % vector.n)
f.write(" d = 0x%x\n" % vector.d)
f.write(" p = 0x%x\n" % vector.p)
f.write(" q = 0x%x\n" % vector.q)
f.write(" dp = 0x%x\n" % vector.dp)
f.write(" dq = 0x%x\n" % vector.dq)
f.write(" qinv = 0x%x\n" % vector.qinv)
f.write(" n_factor = 0x%x\n" % vector.n_factor)
f.write(" p_factor = 0x%x\n" % vector.p_factor)
f.write(" q_factor = 0x%x\n" % vector.q_factor)
f.write(" n_coeff = 0x%x\n" % vector.n_coeff)
f.write(" p_coeff = 0x%x\n" % vector.p_coeff)
f.write(" q_coeff = 0x%x\n" % vector.q_coeff)
f.write(" x = 0x%x\n" % vector.x)
f.write(" y = 0x%x\n" % vector.y)
f.close()
#
# End of file
#