| #!/usr/bin/env python3 |
| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| """HMAC-SHA256 Python implementation |
| """ |
| |
| import argparse |
| import binascii |
| import hmac as hm |
| import hashlib # for comparison |
| import logging as log |
| import sys |
| from array import array |
| from io import StringIO |
| |
| import numpy as np |
| |
| init_h = [ |
| 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, |
| 0x1f83d9ab, 0x5be0cd19 |
| ] |
| |
| k = [ |
| 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, |
| 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, |
| 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, |
| 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, |
| 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, |
| 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, |
| 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, |
| 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, |
| 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, |
| 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, |
| 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 |
| ] |
| |
| |
| def rotr(v: np.uint32, amt: int) -> np.uint32: |
| return (v >> amt) | ((v << (32 - amt)) & 0xFFFFFFFF) |
| |
| |
| def shiftr(v: np.uint32, amt: int) -> np.uint32: |
| return (v >> amt) |
| |
| |
| def sha256(msg: bin) -> bin: |
| # Assume given message is always byte aligned |
| L = len(bytes(msg)) * 8 |
| zero_padding_length = (512 - ((L + 8 + 64) % 512)) |
| log.info("0 padding: %d bits" % (zero_padding_length)) |
| # padding |
| new_msg = msg + b'\x80' + (b'\0' * ( |
| (zero_padding_length // 8))) + L.to_bytes( |
| 8, byteorder='big') |
| new_L = len(new_msg) |
| |
| log.info("Padded message: %s" % (binascii.b2a_hex(new_msg))) |
| # Convert byte to 32bit array |
| #w_array = np.array(new_msg, dtype=np.uint32) |
| dt = np.dtype(np.uint32) |
| dt = dt.newbyteorder('>') # bigendian |
| w_array = np.frombuffer(new_msg, dtype=dt) |
| |
| # compress function |
| [h0, h1, h2, h3, h4, h5, h6, h7] = init_h |
| for i in range(0, new_L // 4, 16): # every 512 bit |
| # create w |
| # 16 entry x 32 bit word |
| w = w_array[i:i + 16] |
| #for j in range(16): |
| |
| [a, b, c, d, e, f, g, h] = [h0, h1, h2, h3, h4, h5, h6, h7] |
| for i in range(64): |
| w_entry = w[0] |
| # Calculate 16th entry and push to the end |
| # s0 = (w[i-15] rotr 7) xor (w[i-15] rotr 18) xor (w[i-15] shiftr 3) |
| # s1 = (w[i-2] rotr 17) xor (w[i-2] rotr 19) xor (w[i-2] shiftr 10) |
| # w[i] = w[i-16] + s0 + w[i-7] + s1 |
| sum_0 = rotr(w[1], 7) ^ rotr(w[1], 18) ^ shiftr(w[1], 3) |
| sum_1 = rotr(w[14], 17) ^ rotr(w[14], 19) ^ shiftr(w[14], 10) |
| w = np.append(w[1:16], 0xFFFFFFFF & (w[0] + sum_0 + w[9] + sum_1)) |
| |
| # Compress |
| S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25) |
| ch = (e & f) ^ ((~e) & g) |
| temp1 = (h + S1 + ch + k[i] + w_entry) & 0xFFFFFFFF |
| S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22) |
| maj = (a & b) ^ (a & c) ^ (b & c) |
| temp2 = (S0 + maj) & 0xFFFFFFFF |
| |
| h = g |
| g = f |
| f = e |
| e = (d + temp1) & 0xFFFFFFFF |
| d = c |
| c = b |
| b = a |
| a = (temp1 + temp2) & 0xFFFFFFFF |
| |
| h0 = (h0 + a) & 0xFFFFFFFF |
| h1 = (h1 + b) & 0xFFFFFFFF |
| h2 = (h2 + c) & 0xFFFFFFFF |
| h3 = (h3 + d) & 0xFFFFFFFF |
| h4 = (h4 + e) & 0xFFFFFFFF |
| h5 = (h5 + f) & 0xFFFFFFFF |
| h6 = (h6 + g) & 0xFFFFFFFF |
| h7 = (h7 + h) & 0xFFFFFFFF |
| |
| # merge |
| digest = np.array([h0, h1, h2, h3, h4, h5, h6, h7], dtype=dt).tobytes() |
| |
| return digest |
| |
| |
| def _hmac(key: bin, src: bin, hashf) -> bin: |
| blocksize = 64 # SHA-256 |
| if len(key) > blocksize: |
| key = hashf(key) |
| key = key + b'\00' * (blocksize - len(key)) |
| log.info("Padded Key: %s" % binascii.b2a_hex(key).decode('utf-8')) |
| ipad = bytes([x ^ 0x36 for x in key]) |
| opad = bytes([x ^ 0x5c for x in key]) |
| meta_v = hashf(ipad + src) |
| log.info("Meta hash output: %s" % binascii.b2a_hex(meta_v).decode('utf-8')) |
| sig = hashf(opad + meta_v) |
| return sig |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(prog="hmac") |
| parser.add_argument( |
| 'message', |
| nargs='?', |
| metavar='file', |
| type=argparse.FileType('rb'), |
| default=sys.stdin, |
| help="Message file.") |
| parser.add_argument( |
| '--big-endian', |
| '-b', |
| action='store_true', |
| default=True, |
| help='Input/Output are Big Endians') |
| parser.add_argument( |
| '--key', '-k', type=str, help='HMAC Secret Key in Big Endian') |
| parser.add_argument('--verbose', '-v', action='store_true', help='Verbose') |
| |
| args = parser.parse_args() |
| |
| if args.verbose: |
| log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG) |
| else: |
| log.basicConfig(format="%(levelname)s: %(message)s") |
| |
| log.info("Secret Key: %s" % args.key) |
| |
| # Convert key to binary |
| try: |
| key = bytes.fromhex(args.key) |
| except ValueError: |
| log.error("Key should be Hex format without '0x' prefix") |
| raise SystemExit(sys.exec_info()[1]) |
| |
| with args.message: |
| try: |
| src = args.message.read() |
| if args.message == sys.stdin: |
| # Converting to binary format |
| src = src.strip().encode('utf-8') |
| log.info("Message: %s" % src) |
| except ValueError: |
| raise SystemExit(sys.exec_info()[1]) |
| |
| hashlib_out = hashlib.sha256(src) |
| log.info("hashlib(BE): 0x%s" % hashlib_out.hexdigest()) |
| |
| model_out = sha256(src) |
| log.info("model (BE): 0x%s" % binascii.b2a_hex(model_out).decode('utf-8')) |
| |
| if hashlib_out.hexdigest() != binascii.b2a_hex(model_out).decode('utf-8'): |
| raise SystemExit("Miscompare") |
| |
| # HMAC |
| hashlib_hmac = hm.new(key, src, hashlib.sha256) |
| log.info("hmac: %s" % hashlib_hmac.hexdigest()) |
| |
| model_hmac = _hmac(key, src, sha256) |
| log.info("model: %s" % binascii.b2a_hex(model_hmac).decode('utf-8')) |
| |
| if hashlib_hmac.hexdigest() != binascii.b2a_hex(model_hmac).decode( |
| 'utf-8'): |
| raise SystemExit("HMAC Miscompare") |
| |
| |
| if __name__ == "__main__": |
| main() |