# # format_test_vector.py # ----------------------------------------- # Formats test vector for x25519_fpga_model # # Author: Pavel Shatov # Copyright (c) 2017-2018, 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. # # USAGE = "USAGE: format_test_vector.py [openssl_binary]" # # # This script reads the test vector generated by regenerate_test_vector.py # and writes nicely formatted C header file and Verilog include file. # # # IMPORTANT: This will only work with OpenSSL 1.1.0, version 1.0.2 doesn't # support the X25519 algoritm. If you system library is the older version, # you can pass location of specific OpenSSL binary to use on the command line. # # # imports # import sys import subprocess import pysodium # # variables # OPENSSL = "" # # format one test vector # def format_c_header( f, da, qax, db, qbx, qabx): # convert to integers da_int = int(da.hex(), 16) db_int = int(db.hex(), 16) qax_int = int(qax.hex(), 16) qbx_int = int(qbx.hex(), 16) qabx_int = int(qabx.hex(), 16) # write all numbers in vector format_c_array(f, da_int, "#define X25519_DA" + " \\\n") format_c_array(f, qax_int, "#define X25519_QA_X" + " \\\n") format_c_array(f, db_int, "#define X25519_DB" + " \\\n") format_c_array(f, qbx_int, "#define X25519_QB_X" + " \\\n") format_c_array(f, qabx_int, "#define X25519_QAB_X" + " \\\n") # # # # format one test vector # # # def format_verilog_include( f, curve, n, # da, qax, qay, # db, qbx, qby, # sx, sy, # gx, gy, # hx, hy, # qa2x, qa2y, # qb2x, qb2y): # if curve == CURVE_P256: # curve_str = "P_256" # msb_index = "255" # if curve == CURVE_P384: # curve_str = "P_384" # msb_index = "383" # # write all numbers in vector # format_verilog_concatenation(f, n, "localparam [" + msb_index + ":0] " + curve_str + "_N" + " =\n") # format_verilog_concatenation(f, da, "localparam [" + msb_index + ":0] " + curve_str + "_DA" + " =\n") # format_verilog_concatenation(f, qax, "localparam [" + msb_index + ":0] " + curve_str + "_QA_X" + " =\n") # format_verilog_concatenation(f, qay, "localparam [" + msb_index + ":0] " + curve_str + "_QA_Y" + " =\n") # format_verilog_concatenation(f, qa2x, "localparam [" + msb_index + ":0] " + curve_str + "_QA2_X" + " =\n") # format_verilog_concatenation(f, qa2y, "localparam [" + msb_index + ":0] " + curve_str + "_QA2_Y" + " =\n") # format_verilog_concatenation(f, db, "localparam [" + msb_index + ":0] " + curve_str + "_DB" + " =\n") # format_verilog_concatenation(f, qbx, "localparam [" + msb_index + ":0] " + curve_str + "_QB_X" + " =\n") # format_verilog_concatenation(f, qby, "localparam [" + msb_index + ":0] " + curve_str + "_QB_Y" + " =\n") # format_verilog_concatenation(f, qb2x, "localparam [" + msb_index + ":0] " + curve_str + "_QB2_X" + " =\n") # format_verilog_concatenation(f, qb2y, "localparam [" + msb_index + ":0] " + curve_str + "_QB2_Y" + " =\n") # format_verilog_concatenation(f, sx, "localparam [" + msb_index + ":0] " + curve_str + "_S_X" + " =\n") # format_verilog_concatenation(f, sy, "localparam [" + msb_index + ":0] " + curve_str + "_S_Y" + " =\n") # format_verilog_concatenation(f, gx, "localparam [" + msb_index + ":0] " + curve_str + "_G_X" + " =\n") # format_verilog_concatenation(f, gy, "localparam [" + msb_index + ":0] " + curve_str + "_G_Y" + " =\n") # format_verilog_concatenation(f, hx, "localparam [" + msb_index + ":0] " + curve_str + "_H_X" + " =\n") # format_verilog_concatenation(f, hy, "localparam [" + msb_index + ":0] " + curve_str + "_H_Y" + " =\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 where # d is private key and qx is the corresponding public key # def get_key(openssl, party): # generate private key filename key_file = party + ".key" # retrieve key components using openssl openssl_command = [openssl, "pkey", "-in", key_file, "-noout", "-text"] openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") stdout_lines = openssl_stdout.splitlines() key_priv = "" key_pub = "" found_priv = False found_pub = False # process lines looking for "priv:" and "pub:" markers for line in stdout_lines: # found private key marker? if line == "priv:": found_priv = True found_pub = False continue # found public key marker? if line == "pub:": found_pub = True 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 colons # * check length (256 bits or 384 bits) key_priv = key_priv.replace(":", "") if len(key_priv) != (256 / 4): sys.exit() # do some cleanup and sanity checking on public key # * remove colons # * check length (256 bits) key_pub = key_pub.replace(":", "") if len(key_pub) != (256 / 4): sys.exit() # convert to byte arrays key_priv_bytes = bytes.fromhex(key_priv) key_pub_bytes = bytes.fromhex(key_pub) # change byte order to msb...lsb key_priv_bytes = key_priv_bytes[::-1] key_pub_bytes = key_pub_bytes[::-1] # convert to integers key_priv_int = int(key_priv_bytes.hex(), 16) key_pub_int = int(key_pub_bytes.hex(), 16) # another sanity check (make sure, that key_pub is actually key_priv * G) key_pub_calc_bytes = pysodium.crypto_scalarmult_curve25519_base(key_priv_bytes[::-1])[::-1] key_pub_calc_int = int(key_pub_calc_bytes.hex(), 16) if (key_pub_int != key_pub_calc_int): sys.exit() # done return key_priv_bytes, key_pub_bytes # # returns the shared secret key # def calc_shared_key(key_priv_bytes, key_pub_bytes): key_shared_bytes = pysodium.crypto_scalarmult_curve25519(key_priv_bytes[::-1], key_pub_bytes[::-1])[::-1] return key_shared_bytes if __name__ == "__main__": # detect whether user requested some specific binary if len(sys.argv) == 1: OPENSSL = "openssl" print("Using system OpenSSL library.") elif len(sys.argv) == 2: OPENSSL = sys.argv[1] print("Using OpenSSL binary '" + OPENSSL + "'...") else: print(USAGE) if len(OPENSSL) > 0: # open output files file_h = open('x25519_test_vector.h', 'w') #file_v = open('ecdh_test_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") # load keys da, qax = get_key(OPENSSL, "alice") db, qbx = get_key(OPENSSL, "bob") # we derive the shared secret two different ways (from Alice's and # from Bob's perspective, they must be identical of course qabx = calc_shared_key(da, qbx) # Alice's secret qbax = calc_shared_key(db, qax) # Bob's secret if (qabx != qbax): sys.exit() print("Derived shared secret."); # format numbers and write to file format_c_header( file_h, da, qax, db, qbx, qabx) # format_verilog_include( file_v, next_curve, n, # da, qax, qay, # db, qbx, qby, # QAB.x, QBA.y, # G.x, G.y, # H.x, H.y, # QA2.x, QA2.y, # QB2.x, QB2.y) # done file_h.close() #file_v.close() # everything went just fine print("Test vector formatted.") # # End of file #