# # format_test_vectors.py # ------------------------------------------ # Formats test vectors for ecdsa_fpga_model # # Author: Pavel Shatov # Copyright (c) 2017, 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. # # # This script reads the test vectors generated by regenerate_test_vectors.py # and writes nicely formatted C header file and Verilog include file. # # # imports # import sys import subprocess from fastecdsa.curve import P256 from fastecdsa.curve import P384 from fastecdsa.point import Point # list of curve names of interest CURVE_P256 = "p256" CURVE_P384 = "p384" # the base point for p-256 P256_GX = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 P256_GY = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 # the base point for p-384 P384_GX = 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7 P384_GY = 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f # # get part of string between two markers # #def string_between(s, s_left, s_right): # s_begin = s.index(s_left) + len(s_left) # s_end = s.index(s_right, s_begin) # return s[s_begin:s_end] # # load message from file # #def read_message(key): # with open(key + ".txt", "r") as f: # return f.readlines()[0] # # # read modulus from file # #def read_modulus(key): # openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-modulus"] # openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") # return openssl_stdout.strip().split("=")[1] # # read private exponent from file # #def read_secret(key): # openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] # openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") # openssl_secret = string_between(openssl_stdout, "privateExponent", "prime1") # openssl_secret = openssl_secret.replace(":", "") # openssl_secret = openssl_secret.replace("\n", "") # openssl_secret = openssl_secret.replace(" ", "") # return openssl_secret # # read part of private key from file # #def read_prime1(key): # openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] # openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") # openssl_secret = string_between(openssl_stdout, "prime1", "prime2") # openssl_secret = openssl_secret.replace(":", "") # openssl_secret = openssl_secret.replace("\n", "") # openssl_secret = openssl_secret.replace(" ", "") # return openssl_secret #def read_prime2(key): # openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] # openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") # openssl_secret = string_between(openssl_stdout, "prime2", "exponent1") # openssl_secret = openssl_secret.replace(":", "") # openssl_secret = openssl_secret.replace("\n", "") # openssl_secret = openssl_secret.replace(" ", "") # return openssl_secret # # read prive exponent from file # #def read_exponent1(key): # openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] # openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") # openssl_secret = string_between(openssl_stdout, "exponent1", "exponent2") # openssl_secret = openssl_secret.replace(":", "") # openssl_secret = openssl_secret.replace("\n", "") # openssl_secret = openssl_secret.replace(" ", "") # return openssl_secret #def read_exponent2(key): # openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] # openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") # openssl_secret = string_between(openssl_stdout, "exponent2", "coefficient") # openssl_secret = openssl_secret.replace(":", "") # openssl_secret = openssl_secret.replace("\n", "") # openssl_secret = openssl_secret.replace(" ", "") # return openssl_secret # # format one test vector # def format_c_header(f, curve, da, qax, qay, db, qbx, qby, sx, sy): if curve == CURVE_P256: curve_str = "P_256" if curve == CURVE_P384: curve_str = "P_384" # write all numbers in vector format_c_array(f, da, "#define " + curve_str + "_DA" + " \\\n") format_c_array(f, qax, "#define " + curve_str + "_QA_X" + " \\\n") format_c_array(f, qay, "#define " + curve_str + "_QA_Y" + " \\\n") format_c_array(f, db, "#define " + curve_str + "_DB" + " \\\n") format_c_array(f, qbx, "#define " + curve_str + "_QB_X" + " \\\n") format_c_array(f, qby, "#define " + curve_str + "_QB_Y" + " \\\n") format_c_array(f, sx, "#define " + curve_str + "_S_X" + " \\\n") format_c_array(f, sy, "#define " + curve_str + "_S_Y" + " \\\n") # # format one test vector # #def format_verilog_include(f, key, n, m, d, s, p, q, dp, dq, mp, mq): # # # calculate factor to bring message into Montgomery domain # factor = calc_montgomery_factor(int(key), n) # factor_p = calc_montgomery_factor(int(key)//2, p); # factor_q = calc_montgomery_factor(int(key)//2, q); # # # calculate helper coefficients for Montgomery multiplication # n_coeff = calc_montgomery_n_coeff(int(key), n) # p_coeff = calc_montgomery_n_coeff(int(key)//2, p) # q_coeff = calc_montgomery_n_coeff(int(key)//2, q) # # # calculate the extra coefficient Montgomery multiplication brings in # coeff = modinv(1 << int(key), n) # # # convert m into Montgomery representation # m_factor = (m * factor * coeff) % n # # # write all numbers # format_verilog_concatenation(f, m, "localparam [" + str(int(key)-1) + ":0] M_" + key + " =\n") # format_verilog_concatenation(f, n, "localparam [" + str(int(key)-1) + ":0] N_" + key + " =\n") # format_verilog_concatenation(f, n_coeff, "localparam [" + str(int(key)-1) + ":0] N_COEFF_" + key + " =\n") # format_verilog_concatenation(f, factor, "localparam [" + str(int(key)-1) + ":0] FACTOR_" + key + " =\n") # format_verilog_concatenation(f, coeff, "localparam [" + str(int(key)-1) + ":0] COEFF_" + key + " =\n") # format_verilog_concatenation(f, m_factor, "localparam [" + str(int(key)-1) + ":0] M_FACTOR_" + key + " =\n") # format_verilog_concatenation(f, d, "localparam [" + str(int(key)-1) + ":0] D_" + key + " =\n") # format_verilog_concatenation(f, s, "localparam [" + str(int(key)-1) + ":0] S_" + key + " =\n") # # format_verilog_concatenation(f, p, "localparam [" + str(int(key)//2-1) + ":0] P_" + str(int(key)//2) + " =\n") # format_verilog_concatenation(f, q, "localparam [" + str(int(key)//2-1) + ":0] Q_" + str(int(key)//2) + " =\n") # format_verilog_concatenation(f, p_coeff, "localparam [" + str(int(key)//2-1) + ":0] P_COEFF_" + str(int(key)//2) + " =\n") # format_verilog_concatenation(f, q_coeff, "localparam [" + str(int(key)//2-1) + ":0] Q_COEFF_" + str(int(key)//2) + " =\n") # format_verilog_concatenation(f, factor_p, "localparam [" + str(int(key)//2-1) + ":0] FACTOR_P_" + str(int(key)//2) + " =\n") # format_verilog_concatenation(f, factor_q, "localparam [" + str(int(key)//2-1) + ":0] FACTOR_Q_" + str(int(key)//2) + " =\n") # format_verilog_concatenation(f, dp, "localparam [" + str(int(key)//2-1) + ":0] DP_" + str(int(key)//2) + " =\n") # format_verilog_concatenation(f, dq, "localparam [" + str(int(key)//2-1) + ":0] DQ_" + str(int(key)//2) + " =\n") # format_verilog_concatenation(f, mp, "localparam [" + str(int(key)//2-1) + ":0] MP_" + str(int(key)//2) + " =\n") # format_verilog_concatenation(f, mq, "localparam [" + str(int(key)//2-1) + ":0] MQ_" + str(int(key)//2) + " =\n") # # nicely format multi-word integer into C array initializer # def format_c_array(f, n, s): # print '#define XYZ \' f.write(s) # convert number to hex string and prepend it with zeroes if necessary n_hex = hex(n).lstrip("0x").rstrip("L") while (len(n_hex) % 8) > 0: n_hex = "0" + n_hex # get number of 32-bit words num_words = len(n_hex) // 8 # print all words in n w = 0 while w < num_words: n_part = "" # add tab for every new line if w == 0: n_part += "\t{" elif (w % 4) == 0: n_part += "\t " # add current word n_part += "0x" + n_hex[8 * w : 8 * (w + 1)] # add separator or newline if (w + 1) == num_words: n_part += "}\n" else: n_part += ", " if (w % 4) == 3: n_part += "\\\n" w += 1 # write current part f.write(n_part) # write final newline f.write("\n") #def format_verilog_concatenation(f, n, s): # # # print 'localparam ZZZ =' # f.write(s) # # # convert number to hex string and prepend it with zeroes if necessary # n_hex = hex(n).split("0x")[1] # while (len(n_hex) % 8) > 0: # n_hex = "0" + n_hex # # # get number of 32-bit words # num_words = len(n_hex) // 8 # # # print all words in n # w = 0 # while w < num_words: # # n_part = "" # # if w == 0: # n_part += "\t{" # elif (w % 4) == 0: # n_part += "\t " # # n_part += "32'h" + n_hex[8 * w : 8 * (w + 1)] # # if (w + 1) == num_words: # n_part += "};\n" # else: # n_part += ", " # if (w % 4) == 3: # n_part += "\n" # w += 1 # # f.write(n_part) # # f.write("\n") # # returns d, qx, qy, where # d is private key and qx, qy is the corresponding public key # def get_key(party, curve): # generate private key filename key_file = party + "_" + curve + ".key" # retrieve key components using openssl openssl_command = ["openssl", "ec", "-in", key_file, "-noout", "-text"] openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") stdout_lines = openssl_stdout.splitlines() found_priv = False found_pub = False key_priv = "" key_pub = "" # process lines looking for "priv:" and "pub:" markers for line in stdout_lines: # found private key marker? if line.strip() == "priv:": found_priv = True found_pub = False continue # found public key marker? if line.strip() == "pub:": # openssl 1.0.2g prints 'pub: ' (extra space before newline), found_pub = True # so we need compare against line.strip(), not just line found_priv = False continue # found part of private key? if found_priv: if not line.startswith(" "): found_priv = False continue else: key_priv += line.strip() # found part of public key? if found_pub: if not line.startswith(" "): found_pub = False continue else: key_pub += line.strip() # do some cleanup and sanity checking on private key # * remove extra leading zero byte if present # * remove colons # * check length (256 bits or 384 bits) while key_priv.startswith("00"): key_priv = key_priv[2:] key_priv = key_priv.replace(":", "") if curve == CURVE_P256 and len(key_priv) != 256 / 4: sys.exit() if curve == CURVE_P384 and len(key_priv) != 384 / 4: sys.exit() # do some cleanup and sanity checking on public key # * make sure, that uncompressed form marker (0x04) is present and # then remove it # * remove colons # * check length (2x256 or 2x384 bits) if not key_pub.startswith("04"): sys.exit() key_pub = key_pub[2:] key_pub = key_pub.replace(":", "") if curve == CURVE_P256 and len(key_pub) != 2 * 256 / 4: sys.exit() if curve == CURVE_P384 and len(key_pub) != 2 * 384 / 4: sys.exit() # split public key into parts if curve == CURVE_P256: key_pub_x = key_pub[ 0: 64] key_pub_y = key_pub[ 64:128] if curve == CURVE_P384: key_pub_x = key_pub[ 0: 96] key_pub_y = key_pub[ 96:192] # convert from strings to integers key_priv = int(key_priv, 16) key_pub_x = int(key_pub_x, 16) key_pub_y = int(key_pub_y, 16) # another sanity check (make sure, that Q is actually d * G) if curve == CURVE_P256: G = Point(P256_GX, P256_GY, curve=P256) Q = Point(key_pub_x, key_pub_y, curve=P256) if curve == CURVE_P384: G = Point(P384_GX, P384_GY, curve=P384) Q = Point(key_pub_x, key_pub_y, curve=P384) # multiply using fastecdsa R = key_priv * G if R.x != Q.x: sys.exit() if R.y != Q.y: sys.exit() # done return key_priv, key_pub_x, key_pub_y if __name__ == "__main__": # list of curves to process curves = [CURVE_P256, CURVE_P384] # open output files file_h = open('ecdsa_fpga_model_ecdh_vectors.h', 'w') # file_v = open('modexp_fpga_model_vectors.v', 'w') # write headers file_h.write("/* Generated automatically, do not edit. */\n\n") # file_v.write("/* Generated automatically, do not edit. */\n\n") # process all the keys for curve in curves: # load keys da, qax, qay = get_key("alice", curve) db, qbx, qby = get_key("bob", curve) # Alice's public key if (curve == CURVE_P256): QA = Point(qax, qay, curve=P256) if (curve == CURVE_P384): QA = Point(qax, qay, curve=P384) # Bob's public key if (curve == CURVE_P256): QB = Point(qbx, qby, curve=P256) if (curve == CURVE_P384): QB = Point(qbx, qby, curve=P384) # we derive the shared secret two different ways (from Alice's and # from Bob's perspective, they must be identical of course QAB = da * QB # Alice's secret QBA = db * QA # Bob's secret if (QAB.x != QBA.x): sys.exit() if (QBA.y != QAB.y): sys.exit() print("Derived shared secret."); # format numbers and write to file format_c_header(file_h, curve, da, qax, qay, db, qbx, qby, QAB.x, QBA.y) # format_verilog_include(file_v, key, modulus, message, secret, signature, prime1, prime2, exponent1, exponent2, message1, message2) # done file_h.close() # file_v.close() # everything went just fine print("Test vectors formatted.") # # End of file #