hkdfs module
HMAC-based key derivation function (HKDF) standalone implementation using pure Python.
- hkdfs(length, key, salt=None, info=None, hash=hashlib.sha256)[source]
Extract a pseudorandom key having
lengthbytes fromkey(and optionally also fromsaltandinfo).- Parameters:
>>> hkdfs(1024, bytes([123]), hash=hashlib.sha512).hex() '4936e6f3ad5e6cab0efd42e2f216d34b977...1bc59c8e55db51d239808e8465a3cb91d11' >>> hkdfs( ... length=1024, ... key=bytes([1]), ... salt=bytes([2]), ... info=bytes([3]), :rtype: :sphinx_autodoc_typehints_type:`\:py\:class\:\`bytes\`` ... hash=hashlib.sha512 ... ).hex()[:73] '1277a50c8cd05020dc073bd129cd84214270a0468e936c496fafee48c10a613a1a3b10fd2'
Note that the maximum supported target length is determined by the length of the output of the supplied hash function.
>>> hkdfs(255 * 32 + 1, bytes([123]), hash=hashlib.sha256) Traceback (most recent call last): ... ValueError: maximum length supported by supplied hash function is 8160 >>> len(hkdfs(255 * 32 + 1, bytes([123]), hash=hashlib.sha512)) 8161 >>> hkdfs(255 * 64 + 1, bytes([123]), hash=hashlib.sha512) Traceback (most recent call last): ... ValueError: maximum length supported by supplied hash function is 16320
The below tests correspond to the test cases found in Appendix A of RFC 5869.
>>> hkdfs( # Test Case 1: Basic test case with SHA-256 ... length=42, ... key=bytes.fromhex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'), ... salt=bytes.fromhex('000102030405060708090a0b0c'), ... info=bytes.fromhex('f0f1f2f3f4f5f6f7f8f9'), ... hash=hashlib.sha256 ... ).hex() == ( ... '3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf' + ... '1a5a4c5db02d56ecc4c5bf34007208d5b887185865' ... ) True >>> hkdfs( # Test Case 2: Test with SHA-256 and longer inputs/outputs ... length=82, ... key=bytes.fromhex( ... '000102030405060708090a0b0c0d0e0f' + ... '101112131415161718191a1b1c1d1e1f' + ... '202122232425262728292a2b2c2d2e2f' + ... '303132333435363738393a3b3c3d3e3f' + ... '404142434445464748494a4b4c4d4e4f' ... ), ... salt=bytes.fromhex( ... '606162636465666768696a6b6c6d6e6f' + ... '707172737475767778797a7b7c7d7e7f' + ... '808182838485868788898a8b8c8d8e8f' + ... '909192939495969798999a9b9c9d9e9f' + ... 'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' ... ), ... info=bytes.fromhex( ... 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + ... 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + ... 'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + ... 'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' + ... 'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff' ... ), ... hash=hashlib.sha256 ... ).hex() == ( ... 'b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97' + ... 'c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db' + ... '71cc30c58179ec3e87c14c01d5c1f3434f1d87' ... ) True >>> hkdfs( # Test Case 3: Test with SHA-256 and zero-length salt/info ... length=42, ... key=bytes.fromhex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'), ... salt=bytes(0), ... info=bytes(0), ... hash=hashlib.sha256 ... ).hex() == ( ... '8da4e775a563c18f715f802a063c5a31b8a11f5c5e' + ... 'e1879ec3454e5f3c738d2d9d201395faa4b61a96c8' ... ) True >>> hkdfs( # Test Case 4: Basic test case with SHA-1 ... length=42, ... key=bytes.fromhex('0b0b0b0b0b0b0b0b0b0b0b'), ... salt=bytes.fromhex('000102030405060708090a0b0c'), ... info=bytes.fromhex('f0f1f2f3f4f5f6f7f8f9'), ... hash=hashlib.sha1 ... ).hex() == ( ... '085a01ea1b10f36933068b56efa5ad81a4f14b822f' + ... '5b091568a9cdd4f155fda2c22e422478d305f3f896' ... ) True >>> hkdfs( # Test Case 5: Test with SHA-1 and longer inputs/outputs ... length=82, ... key=bytes.fromhex( ... '000102030405060708090a0b0c0d0e0f' + ... '101112131415161718191a1b1c1d1e1f' + ... '202122232425262728292a2b2c2d2e2f' + ... '303132333435363738393a3b3c3d3e3f' + ... '404142434445464748494a4b4c4d4e4f' ... ), ... salt=bytes.fromhex( ... '606162636465666768696a6b6c6d6e6f' + ... '707172737475767778797a7b7c7d7e7f' + ... '808182838485868788898a8b8c8d8e8f' + ... '909192939495969798999a9b9c9d9e9f' + ... 'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' ... ), ... info=bytes.fromhex( ... 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + ... 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + ... 'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + ... 'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' + ... 'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff' ... ), ... hash=hashlib.sha1 ... ).hex() == ( ... '0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ff' + ... 'e8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c' + ... '5e927336d0441f4c4300e2cff0d0900b52d3b4' ... ) True >>> hkdfs( # Test Case 6: Test with SHA-1 and zero-length salt/info ... length=42, ... key=bytes.fromhex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'), ... salt=bytes(0), ... info=bytes(0), ... hash=hashlib.sha1 ... ).hex() == ( ... '0ac1af7002b3d761d1e55298da9d0506b9ae520572' + ... '20a306e07b6b87e8df21d0ea00033de03984d34918' ... ) True >>> hkdfs( # Test Case 7: Test with SHA-1, no salt, and zero-length info ... length=42, ... key=bytes.fromhex('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c'), ... info=bytes(0), ... hash=hashlib.sha1 ... ).hex() == ( ... '2c91117204d745f3500d636a62f64f0ab3bae548aa' + ... '53d423b0d1f27ebba6f5e5673a081d70cce7acfc48' ... ) True
This function performs type and range checking, raising an exception when invoked with invalid arguments.
>>> hkdfs('abc', bytes([1])) Traceback (most recent call last): ... TypeError: length must be an integer >>> hkdfs(-1, bytes([1])) Traceback (most recent call last): ... ValueError: length must be a nonnegative integer >>> hkdfs(1024, 'abc') Traceback (most recent call last): ... TypeError: key must be a bytes-like object >>> hkdfs(1024, bytes([1]), 'abc') Traceback (most recent call last): ... TypeError: salt must be a bytes-like object >>> hkdfs(1024, bytes([1]), bytes([2]), 'abc') Traceback (most recent call last): ... TypeError: info must be a bytes-like object
The final optional argument
hashis normally expected be a valid hash function from the built-inhashlibmodule (for example, the functionhashlib.sha512). However, any object that matches the interface of the example classdigestmodbelow can be supplied.>>> class digestmod: ... digest_size: int = 64 ... block_size: int = 64 ... ... def update(d: digestmod, b: bytes): ... pass ... ... def copy(self: digestmod) -> digestmod: ... return digestmod() ... ... def digest(self: digestmod) -> bytes: ... return bytes(64) ... >>> hkdfs(1024, bytes([123]), hash=digestmod) == bytes(1024) True
No checks are performed to confirm that the supplied value for
hashconforms to the above interface. A deviation from this interface may cause an exception to be raised by an underlying internal or built-in function. This exception will either be aTypeErrorbecause the value is not callable, anAttributeErrorbecause an expected attribute is missing, or another error because the attributes do not behave as would be expected for a built-in hash function.>>> hkdfs(1024, bytes([123]), hash=123) Traceback (most recent call last): ... TypeError: 'int' object is not callable >>> class digestmod: ... digest_size: int = 64 ... block_size: int = 64 ... >>> hkdfs(1024, bytes([123]), hash=digestmod) Traceback (most recent call last): ... AttributeError: 'digestmod' object has no attribute 'update'
Consult the documentation for
hashlib.newfor more information.