From 8e57d28817a48eae2d5df88cddf5dc81421c9ed2 Mon Sep 17 00:00:00 2001 From: "Pavel V. Shatov (Meister)" Date: Mon, 24 Sep 2018 21:37:16 +0300 Subject: Test vector for X25519 --- vectors/x25519/alice.key | 3 + vectors/x25519/bob.key | 3 + vectors/x25519/format_test_vector.py | 374 +++++++++++++++++++++++++++++++ vectors/x25519/regenerate_test_vector.py | 98 ++++++++ vectors/x25519/x25519_test_vector.h | 22 ++ 5 files changed, 500 insertions(+) create mode 100644 vectors/x25519/alice.key create mode 100644 vectors/x25519/bob.key create mode 100644 vectors/x25519/format_test_vector.py create mode 100644 vectors/x25519/regenerate_test_vector.py create mode 100644 vectors/x25519/x25519_test_vector.h diff --git a/vectors/x25519/alice.key b/vectors/x25519/alice.key new file mode 100644 index 0000000..939797d --- /dev/null +++ b/vectors/x25519/alice.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VuBCIEIBCMF7aoYJIROc6rNDTmtUCcgD1Fz1pemeN1bm26p5dA +-----END PRIVATE KEY----- diff --git a/vectors/x25519/bob.key b/vectors/x25519/bob.key new file mode 100644 index 0000000..775a0cd --- /dev/null +++ b/vectors/x25519/bob.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VuBCIEIBDN3nhskfw9TZ53kpmLbgHwLwpDQjr4TZT0NrF1ojtp +-----END PRIVATE KEY----- diff --git a/vectors/x25519/format_test_vector.py b/vectors/x25519/format_test_vector.py new file mode 100644 index 0000000..51f76d1 --- /dev/null +++ b/vectors/x25519/format_test_vector.py @@ -0,0 +1,374 @@ +# +# 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 +# diff --git a/vectors/x25519/regenerate_test_vector.py b/vectors/x25519/regenerate_test_vector.py new file mode 100644 index 0000000..2d56be8 --- /dev/null +++ b/vectors/x25519/regenerate_test_vector.py @@ -0,0 +1,98 @@ +# +# regenerate_test_vector.py +# ------------------------------------------------------------ +# Generates a new randomized 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: regenerate_test_vector.py [openssl_binary]" +# + +# +# This script generates a test vector. The test vector contains two +# private keys. +# + +# +# 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 + +# +# variables +# +SECRET_KEY_ALICE = "alice.key" +SECRET_KEY_BOB = "bob.key" + +OPENSSL = "" + + +# +# openssl_genpkey +# +def openssl_genpkey(binary, filename): + + subprocess.call([binary, "genpkey", "-algorithm", "X25519", "-out", filename]) + + +# +# __main__ +# +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) + + # generate two new private keys + if len(OPENSSL) > 0: + openssl_genpkey(OPENSSL, SECRET_KEY_ALICE) + openssl_genpkey(OPENSSL, SECRET_KEY_BOB) + + +# +# End of file +# diff --git a/vectors/x25519/x25519_test_vector.h b/vectors/x25519/x25519_test_vector.h new file mode 100644 index 0000000..90e65b1 --- /dev/null +++ b/vectors/x25519/x25519_test_vector.h @@ -0,0 +1,22 @@ +/* Generated automatically, do not edit. */ + +#define X25519_DA \ + {0x4097a7ba, 0x6d6e75e3, 0x995e5acf, 0x453d809c, \ + 0x40b5e634, 0x34abce39, 0x119260a8, 0xb6178c10} + +#define X25519_QA_X \ + {0x001c3a34, 0x022f49eb, 0x9261cd26, 0xe1d07b77, \ + 0x78c975d3, 0x1a26aae4, 0x3e6b7b69, 0x0c01d3b9} + +#define X25519_DB \ + {0x693ba275, 0xb136f494, 0x4df83a42, 0x430a2ff0, \ + 0x016e8b99, 0x92779e4d, 0x3dfc916c, 0x78decd10} + +#define X25519_QB_X \ + {0x0c364b59, 0x88b70c7e, 0x22871817, 0x471552a6, \ + 0x8bf22c0e, 0xe4c7e096, 0xa7e0f167, 0x2157f598} + +#define X25519_QAB_X \ + {0x6339d13b, 0x26df661e, 0x18d23492, 0x2dcc8232, \ + 0x85ef9b36, 0xfc92aa83, 0x0116b7ef, 0xcdc8c52b} + -- cgit v1.2.3