blob: 135ff332a3ee413d9d5fb5637638d7ff0aa7ddb2 [file] [log] [blame]
#!/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()