1

I am trying to write a program that calculates hmac of a string. I am allowed to use hashlib library but not hmac. Here is my code:

from hashlib import sha256

def string_to_decimal(string):
    res = ''
    for char in string:
        res += hex(ord(char))[2:]
    return int(res, 16)

def decimal_to_string(number):
    hex_number = hex(number)[2:]
    hex_number = hex_number[::-1]
    res = ''
    for i in range(0, len(hex_number), 2):
        if i + 1 < len(hex_number):
            res += chr(int(hex_number[i + 1] + hex_number[i], 16))
        else:
            res += chr(int(hex_number[i], 16))

    return res[::-1]

def calculate_ipad_opad(block_size, ipad, opad):
    res_ipad = 0
    res_opad = 0
    for i in range(block_size // 8):
        res_ipad = (res_ipad * 256) + ipad
        res_opad = (res_opad * 256) + opad
    return res_ipad, res_opad

def integer_to_bytes(number):
    res = list()
    while number > 0:
        res.append(number % 256)
        number //= 256
    
    return bytes(res[::-1])

block_size = 512

msg = 'The quick brown fox jumps over the lazy dog'
key = 'key'

ipad = 54
opad = 92

block_ipad, block_opad = calculate_ipad_opad(block_size, ipad, opad)

key_decimal = string_to_decimal(key)
si = key_decimal ^ block_ipad
so = key_decimal ^ block_opad

a = sha256(integer_to_bytes(so) + sha256(integer_to_bytes(si) + msg.encode()).digest())

print(a.digest())

I know the key length is not more than the block size. I used Wikipedia to write the code. But it doesn't work properly. Could you please help me with that???

EDIT: I expected the output of the code to be f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8. But the output is 3d6243123b984bcc17cb96eb61c2b47d27545c3a9119b623be7932e846bf0643.

ssaf
  • 86
  • 1
  • 6
  • @jps thank you for your comment. I edited that. – ssaf May 11 '21 at 18:54
  • how did you calculate expected output ? – furas May 11 '21 at 19:27
  • 1
    @furas: I tried with https://www.freeformatter.com/hmac-generator.html hmac sha256 and got the expected value that OP posted. – jps May 11 '21 at 19:29
  • 1
    @ssaf Best to look up or generate intermediate values for the various steps using a known good implementation. – Maarten Bodewes May 11 '21 at 19:29
  • 1
    Uh, I don't see that `ipad` and `opad` have the right values. These should be byte arrays of the given value, not a *multiplication with* the value. – Maarten Bodewes May 11 '21 at 19:33
  • 1
    your calculations doesn't fit me to description on Wikipedia. You don't pad key (`key ← Pad(key, blockSize)`) you don't create bytes list with repeated values `0x5c` and `0x36` – furas May 11 '21 at 19:51
  • @MaartenBodewes multiplicating ipad to 256 shifts the ipad 8 bits to left. i.e 0x36 * 0x100 = 0x3600. then adding the result with 0x36 makes it repeated twice. i.e 0x3600 + 0x36 = 0x3636. this process is repeated for block_size times – ssaf May 12 '21 at 04:26
  • 1
    Ugh. OK, don't know what is wrong with `<<` and really you should do this byte-by-byte, but I guess this may work. – Maarten Bodewes May 12 '21 at 21:52

1 Answers1

2

The key must be padded with 0x00 values on the right side up to the block size of the digest (if the key size is smaller than the block size, which is the case here). You can achieve this by e.g. adding in string_to_decimal() just before the return:

res = res.ljust(64 * 2, "0")

With this change, the expected result is provided.

For an implementation, it is better to use a more detailed specification of the algorithm, e.g. FIPS PUB 198-1 instead of the highly abbreviated description in Wikipedia. Furthermore, you are on the safe side if official test vectors are applied.


The conversion to decimal numbers is actually not necessary, i.e. it is possible to work directly with bytes like objects, which simplifies the implementation considerably:

from hashlib import sha256

msg = 'The quick brown fox jumps over the lazy dog'
key = 'key'

block_size_bytes = 64

block_opad_bytes = b'\x5c' * block_size_bytes
block_ipad_bytes = b'\x36' * block_size_bytes
key_bytes = key.encode().ljust(block_size_bytes, b'\0')

si_bytes = bytes([a ^ b for (a, b) in zip(block_ipad_bytes, key_bytes)]) 
so_bytes = bytes([a ^ b for (a, b) in zip(block_opad_bytes, key_bytes)]) 

a = sha256(so_bytes + sha256(si_bytes + msg.encode()).digest())

print(a.digest().hex()) # f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8
Topaco
  • 40,594
  • 4
  • 35
  • 62