"""Tests for i2p_crypto — hash, HMAC, HKDF with byte-identical parity checks.""" import hashlib import hmac as stdlib_hmac import pytest # === Hash data structures === class TestHash: def test_create_32_bytes(self): from i2p_crypto.hash_data import Hash data = bytes(range(32)) h = Hash(data) assert h.data == data def test_reject_wrong_length(self): from i2p_crypto.hash_data import Hash with pytest.raises(ValueError): Hash(b"too short") def test_equality(self): from i2p_crypto.hash_data import Hash a = Hash(b"\x00" * 32) b = Hash(b"\x00" * 32) assert a == b def test_inequality(self): from i2p_crypto.hash_data import Hash a = Hash(b"\x00" * 32) b = Hash(b"\x01" + b"\x00" * 31) assert a != b def test_hash_code_uses_first_4_bytes(self): from i2p_crypto.hash_data import Hash data = b"\xde\xad\xbe\xef" + b"\x00" * 28 h = Hash(data) assert hash(h) == int.from_bytes(b"\xde\xad\xbe\xef", "big") def test_create_from_offset(self): from i2p_crypto.hash_data import Hash full = b"\xff" * 10 + bytes(range(32)) + b"\xff" * 10 h = Hash.create(full, 10) assert h.data == bytes(range(32)) def test_fake_hash(self): from i2p_crypto.hash_data import Hash assert Hash.FAKE_HASH.data == b"\x00" * 32 def test_bytes_conversion(self): from i2p_crypto.hash_data import Hash data = bytes(range(32)) h = Hash(data) assert bytes(h) == data def test_none_creates_zero_hash(self): from i2p_crypto.hash_data import Hash h = Hash() assert h.data == b"\x00" * 32 def test_immutable_data(self): from i2p_crypto.hash_data import Hash data = bytearray(32) h = Hash(bytes(data)) data[0] = 0xFF assert h.data[0] == 0 # Should not be affected class TestSHA1Hash: def test_create_20_bytes(self): from i2p_crypto.hash_data import SHA1Hash data = bytes(range(20)) h = SHA1Hash(data) assert h.data == data assert SHA1Hash.HASH_LENGTH == 20 def test_reject_wrong_length(self): from i2p_crypto.hash_data import SHA1Hash with pytest.raises(ValueError): SHA1Hash(b"too short") def test_equality(self): from i2p_crypto.hash_data import SHA1Hash a = SHA1Hash(b"\x00" * 20) b = SHA1Hash(b"\x00" * 20) assert a == b class TestHash384: def test_length(self): from i2p_crypto.hash_data import Hash384 assert Hash384.HASH_LENGTH == 48 h = Hash384(b"\x00" * 48) assert len(h.data) == 48 def test_reject_wrong_length(self): from i2p_crypto.hash_data import Hash384 with pytest.raises(ValueError): Hash384(b"\x00" * 32) class TestHash512: def test_length(self): from i2p_crypto.hash_data import Hash512 assert Hash512.HASH_LENGTH == 64 h = Hash512(b"\x00" * 64) assert len(h.data) == 64 def test_reject_wrong_length(self): from i2p_crypto.hash_data import Hash512 with pytest.raises(ValueError): Hash512(b"\x00" * 32) # === SHA256Generator — byte-identical with hashlib === class TestSHA256Generator: def test_known_vector_empty(self): """SHA-256 of empty string — NIST test vector.""" from i2p_crypto.sha256_generator import SHA256Generator gen = SHA256Generator.get_instance() h = gen.calculate_hash(b"") expected = bytes.fromhex( "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ) assert h.data == expected def test_known_vector_abc(self): """SHA-256 of 'abc' — NIST test vector.""" from i2p_crypto.sha256_generator import SHA256Generator gen = SHA256Generator.get_instance() h = gen.calculate_hash(b"abc") expected = bytes.fromhex( "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" ) assert h.data == expected def test_known_vector_long(self): """SHA-256 of 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'.""" from i2p_crypto.sha256_generator import SHA256Generator gen = SHA256Generator.get_instance() msg = b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" h = gen.calculate_hash(msg) expected = bytes.fromhex( "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" ) assert h.data == expected def test_matches_hashlib(self): """Byte-identical with Python's hashlib.sha256.""" from i2p_crypto.sha256_generator import SHA256Generator gen = SHA256Generator.get_instance() for data in [b"", b"hello", b"x" * 1000, bytes(range(256))]: assert gen.calculate_hash(data).data == hashlib.sha256(data).digest() def test_offset_and_length(self): from i2p_crypto.sha256_generator import SHA256Generator gen = SHA256Generator.get_instance() data = b"XXXhelloXXX" h = gen.calculate_hash(data, 3, 5) assert h.data == hashlib.sha256(b"hello").digest() def test_calculate_hash_into(self): from i2p_crypto.sha256_generator import SHA256Generator gen = SHA256Generator.get_instance() out = bytearray(48) gen.calculate_hash_into(b"test", 0, 4, out, 8) expected = hashlib.sha256(b"test").digest() assert out[8:40] == expected assert out[:8] == b"\x00" * 8 assert out[40:] == b"\x00" * 8 def test_digest_convenience(self): from i2p_crypto.sha256_generator import SHA256Generator assert SHA256Generator.digest(b"hello") == hashlib.sha256(b"hello").digest() def test_singleton(self): from i2p_crypto.sha256_generator import SHA256Generator a = SHA256Generator.get_instance() b = SHA256Generator.get_instance() assert a is b def test_returns_hash_object(self): from i2p_crypto.sha256_generator import SHA256Generator from i2p_crypto.hash_data import Hash gen = SHA256Generator.get_instance() h = gen.calculate_hash(b"data") assert isinstance(h, Hash) assert len(h.data) == 32 # === HMAC256Generator — byte-identical with hmac module === class TestHMAC256Generator: def test_rfc4231_vector_1(self): """RFC 4231 Test Case 1 for HMAC-SHA256.""" from i2p_crypto.hmac_generator import HMAC256Generator gen = HMAC256Generator.get_instance() key = b"\x0b" * 20 + b"\x00" * 12 # pad to 32 data = b"Hi There" out = bytearray(32) gen.calculate(key, data, 0, len(data), out, 0) # HMAC-SHA256 with key=0b*20 (first 32 bytes, padded with zeros) expected = stdlib_hmac.new(key[:32], data, hashlib.sha256).digest() assert bytes(out) == expected def test_matches_stdlib_hmac(self): """Byte-identical with Python's hmac.new(key, data, sha256).""" from i2p_crypto.hmac_generator import HMAC256Generator gen = HMAC256Generator.get_instance() for key, data in [ (b"\x00" * 32, b""), (bytes(range(32)), b"hello world"), (b"\xff" * 32, b"x" * 1000), (bytes(range(32)), bytes(range(256))), ]: result = gen.calculate(key, data) expected = stdlib_hmac.new(key[:32], data, hashlib.sha256).digest() assert result == expected def test_calculate_with_offset(self): from i2p_crypto.hmac_generator import HMAC256Generator gen = HMAC256Generator.get_instance() key = bytes(range(32)) data = b"XXXhelloXXX" result = gen.calculate(key, data, 3, 5) expected = stdlib_hmac.new(key, b"hello", hashlib.sha256).digest() assert result == expected def test_calculate_into_buffer(self): from i2p_crypto.hmac_generator import HMAC256Generator gen = HMAC256Generator.get_instance() key = bytes(range(32)) data = b"test" target = bytearray(48) gen.calculate(key, data, 0, 4, target, 8) expected = stdlib_hmac.new(key, data, hashlib.sha256).digest() assert target[8:40] == expected assert target[:8] == b"\x00" * 8 def test_verify_correct(self): from i2p_crypto.hmac_generator import HMAC256Generator gen = HMAC256Generator.get_instance() key = bytes(range(32)) data = b"verify me" mac = gen.calculate(key, data) assert gen.verify(key, data, 0, len(data), mac) def test_verify_wrong_mac(self): from i2p_crypto.hmac_generator import HMAC256Generator gen = HMAC256Generator.get_instance() key = bytes(range(32)) data = b"verify me" mac = bytearray(gen.calculate(key, data)) mac[0] ^= 0xFF # corrupt assert not gen.verify(key, data, 0, len(data), bytes(mac)) def test_verify_partial_length(self): from i2p_crypto.hmac_generator import HMAC256Generator gen = HMAC256Generator.get_instance() key = bytes(range(32)) data = b"partial" mac = gen.calculate(key, data) # Verify only first 16 bytes assert gen.verify(key, data, 0, len(data), mac, 0, 16) def test_singleton(self): from i2p_crypto.hmac_generator import HMAC256Generator a = HMAC256Generator.get_instance() b = HMAC256Generator.get_instance() assert a is b # === HKDF — RFC 5869 test vectors === class TestHKDF: def test_rfc5869_case1(self): """RFC 5869 Test Case 1.""" from i2p_crypto.hkdf import HKDF hkdf = HKDF() ikm = bytes.fromhex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") salt = bytes.fromhex("000102030405060708090a0b0c") info = bytes.fromhex("f0f1f2f3f4f5f6f7f8f9") expected_okm = bytes.fromhex( "3cb25f25faacd57a90434f64d0362f2a" "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" "34007208d5b887185865" ) okm = hkdf.extract_and_expand(salt, ikm, info, 42) assert okm == expected_okm def test_rfc5869_case2(self): """RFC 5869 Test Case 2.""" from i2p_crypto.hkdf import HKDF hkdf = HKDF() ikm = bytes.fromhex( "000102030405060708090a0b0c0d0e0f" "101112131415161718191a1b1c1d1e1f" "202122232425262728292a2b2c2d2e2f" "303132333435363738393a3b3c3d3e3f" "404142434445464748494a4b4c4d4e4f" ) salt = bytes.fromhex( "606162636465666768696a6b6c6d6e6f" "707172737475767778797a7b7c7d7e7f" "808182838485868788898a8b8c8d8e8f" "909192939495969798999a9b9c9d9e9f" "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" ) info = bytes.fromhex( "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" ) expected_okm = bytes.fromhex( "b11e398dc80327a1c8e7f78c596a4934" "4f012eda2d4efad8a050cc4c19afa97c" "59045a99cac7827271cb41c65e590e09" "da3275600c2f09b8367793a9aca3db71" "cc30c58179ec3e87c14c01d5c1f3434f" "1d87" ) okm = hkdf.extract_and_expand(salt, ikm, info, 82) assert okm == expected_okm def test_rfc5869_case3(self): """RFC 5869 Test Case 3 — zero-length salt and info.""" from i2p_crypto.hkdf import HKDF hkdf = HKDF() ikm = bytes.fromhex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") salt = b"" info = b"" expected_okm = bytes.fromhex( "8da4e775a563c18f715f802a063c5a31" "b8a11f5c5ee1879ec3454e5f3c738d2d" "9d201395faa4b61a96c8" ) okm = hkdf.extract_and_expand(salt, ikm, info, 42) assert okm == expected_okm def test_i2p_style_one_output(self): """Test I2P-style calculate() with one 32-byte output.""" from i2p_crypto.hkdf import HKDF hkdf = HKDF() key = bytes(range(32)) data = b"test input keying material" out = bytearray(32) result = hkdf.calculate(key, data, out=out) assert len(result) == 32 assert bytes(out) == result # Verify against manual HMAC computation prk = stdlib_hmac.new(key, data, hashlib.sha256).digest() t1 = stdlib_hmac.new(prk, b"\x01", hashlib.sha256).digest() assert result == t1 def test_i2p_style_one_output_with_info(self): """Test I2P-style calculate() with info string.""" from i2p_crypto.hkdf import HKDF hkdf = HKDF() key = bytes(range(32)) data = b"ikm" info = "some info" out = bytearray(32) result = hkdf.calculate(key, data, info=info, out=out) # Verify against manual computation prk = stdlib_hmac.new(key, data, hashlib.sha256).digest() t1 = stdlib_hmac.new(prk, info.encode("ascii") + b"\x01", hashlib.sha256).digest() assert result == t1 def test_i2p_style_two_outputs(self): """Test I2P-style calculate() with two 32-byte outputs.""" from i2p_crypto.hkdf import HKDF hkdf = HKDF() key = bytes(range(32)) data = b"key material" out = bytearray(32) out2 = bytearray(48) result = hkdf.calculate(key, data, out=out, out2=out2, off2=8) # out should have T1 assert bytes(out) == result # out2[8:40] should have T2 prk = stdlib_hmac.new(key, data, hashlib.sha256).digest() t1 = stdlib_hmac.new(prk, b"\x01", hashlib.sha256).digest() t2 = stdlib_hmac.new(prk, t1 + b"\x02", hashlib.sha256).digest() assert result == t1 assert out2[8:40] == t2 assert out2[:8] == b"\x00" * 8 def test_i2p_style_two_outputs_with_info(self): """Test I2P-style calculate() with two outputs and info.""" from i2p_crypto.hkdf import HKDF hkdf = HKDF() key = bytes(range(32)) data = b"ikm" info = "ctx" out = bytearray(32) out2 = bytearray(32) hkdf.calculate(key, data, info=info, out=out, out2=out2) # Verify prk = stdlib_hmac.new(key, data, hashlib.sha256).digest() info_bytes = info.encode("ascii") t1 = stdlib_hmac.new(prk, info_bytes + b"\x01", hashlib.sha256).digest() t2 = stdlib_hmac.new(prk, t1 + info_bytes + b"\x02", hashlib.sha256).digest() assert bytes(out) == t1 assert bytes(out2) == t2 def test_deterministic(self): from i2p_crypto.hkdf import HKDF hkdf = HKDF() key = b"\xaa" * 32 data = b"same input" r1 = hkdf.calculate(key, data) r2 = hkdf.calculate(key, data) assert r1 == r2