mercredi 17 novembre 2021

Implementing the hash derivation function and instantiation for HashDRBG per NIST

I am trying to implement the hash_df function defined as part of the NIST HashDRBG standard specified here https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf section 10.3.1.

The given algorithm for hash_df from the above document is:

Hash_df (input_string, no_of_bits_to_return): 
1.   input_string: The string to be hashed. 
2.   no_of_bits_to_return: The number of bits to be returned by Hash_df. The maximum length (max_number_of_bits) is implementation dependent, but shall be less than or equal to (255 × outlen). no_of_bits_to_return is represented as a 32-bit integer. 
Output: 
 1.   status: The status returned from Hash_df. The status will indicate SUCCESS or ERROR_FLAG. 
 2. requested_bits: The result of performing the Hash_df. 
Hash_df Process: 
 1.   temp = the Null string. 
 2. len =  ceiling divide (no_of_bits_to_return / outlen)
 3.   counter = 0x01. Comment: An 8-bit binary value representing the 
integer "1". 
 4.   For i = 1 to len do 
    Comment : In step 4.1, no_of_bits_to_return is used as a 32-bit string. 
    4.1 temp = temp || Hash (counter || no_of_bits_to_return ||   input_string). 
    4.2 counter = counter + 1. 
 5.   requested_bits = leftmost (temp, no_of_bits_to_return). 
 6.   Return (SUCCESS, requested_bits).

This hash_df is then used in the instantiation function defined in section 10.1.1.2 of the same document (note that nist uses || as concatenation):

Hash_DRBG_Instantiate_algorithm (entropy_input, nonce, personalization_string,  
security_strength):  
1.   entropy_input: The string of bits obtained from the randomness source. 
2.   nonce: A string of bits as specified in Section 8.6.7. 
3.   personalization_string: The personalization string received from the consuming application. Note that the length of the personalization_string may be zero. 
4.   security_strength: The security strength for the instantiation. This parameter is optional for Hash_DRBG, since it is not used. 
Output: 
 1.   initial_working_state: The initial values for V, C, and reseed_counter (see Section 10.1.1.1). 
Hash_DRBG Instantiate Process: 
 1.   seed_material = entropy_input || nonce ||personalization_string. 
 2.   seed = Hash_df (seed_material, seedlen). 
 3.   V = seed. 
 4.   C = Hash_df ((0x00 || V), seedlen). Comment: Precede V with a byte of zeros. 
 5.   reseed_counter = 1. 
 6.   Return (V, C, reseed_counter).

Given that, I came up with the following hash_df and instantiation function:

def int_to_bytes(x: int) -> bytes:
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')


def int_from_bytes(xbytes: bytes) -> int:
    return int.from_bytes(xbytes, 'big')


def ceildiv(a, b):
    return -(a // -b)


class hashDrbg:
    V = b''
    C = b''
    reseed_counter = 0
    zer = int_to_bytes(0)
    one = int_to_bytes(1)
    two = int_to_bytes(2)
    three = int_to_bytes(3)

    def __init__(self):
        pass

    def hash_df(self, input_string, no_of_bits_to_return):
        temp = b''
        length = ceildiv(no_of_bits_to_return, 256)  # 256 is the outlen for sha 256
        counter = int_to_bytes(1)
        for i in range(1, length + 1):
            m_digest = hashlib.sha256()
            m_digest.update(counter + int_to_bytes(no_of_bits_to_return) + input_string)
            temp = temp + m_digest.digest()
            counter = int_from_bytes(counter)
            counter += 1
            counter = int_to_bytes(counter)
        requested_bits = temp[0:no_of_bits_to_return // 8]
        return requested_bits

    def instantiate(self, entropy, nonce, personalization_string, security_strength):
        # these 4 lines are to do nonce + 1, not sure if needed or not
        # nonce = int(nonce, 16)
        # nonce += 1
        # nonce = int_to_bytes(nonce)
        # nonce = nonce.hex()
        seed_material = entropy + nonce + personalization_string
        seed = self.hash_df(bytes.fromhex(seed_material), 440)
        self.V = seed
        self.C = self.hash_df(self.zer + self.V, 440)
        self.reseed_counter = 1

I have found these test vectors https://raw.githubusercontent.com/coruus/nist-testvectors/master/csrc.nist.gov/groups/STM/cavp/documents/drbg/drbgtestvectors/drbgvectors_pr_false/Hash_DRBG.txt which show the intermediate values of every step along the way. Currently, I am only concerned with getting the instantiation/hash_df step correct before moving on to reseed and generate. With the following input values for instantiate (from those test vectors):

EntropyInput = 6c623aea73bc8a59e28c6cd9c7c7ec8ca2e75190bd5dcae5978cf0c199c23f4f
Nonce = e55db067a0ed537e66886b7cda02f772
PersonalizationString = 1e59d798810083d1ff848e90b25c9927e3dfb55a0888b0339566a9f9ca7542dc

I should get the following internal values:

V = 3fb73388bd7b779aa94ff1738bfc7b80ff907a1755589e3a7646db08df608f58e3ff3b660abc591932490a5a03f79ebc6de8e655848d99
C = 48723f992acce55207e3882d69ba89684d083da32dc2e2d9fc171423c27f2024701d273447e56585607dc13d3964ae35030b6e4683988c
reseed counter = 1

But I end up with the following values:

hash_drbg = hashDrbg()

# instantiate
hash_drbg.instantiate(entropy_input, instantiation_nonce, perso_string, 128)
print("V = " + hash_drbg.V.hex())
print("C = " + hash_drbg.C.hex())
print("reseed counter = " + str(hash_drbg.reseed_counter))


V = c2b3ed040bd5d636e010c664c2952d3eaa7c071c8ef8758c77a1fd517126c8717450f32f493530c4c973e668deedcd8a3e5d1fedfdbb5b
C = 0cb62b53bf805576040f1ccbb91465cb68a21d427320bb148cf6f73c35d2cc98a6ecbf502cf4f8c5c55241c8ef9add384cea38cd7613b3
reseed counter = 1

So my question is, where am I going wrong in the above functions that is causing me to get incorrect results?




Aucun commentaire:

Enregistrer un commentaire