import hashlib
import hmac
import os
import subprocess
from binascii import hexlify, unhexlify
try:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
except ImportError:
print("ERROR: python-cryptography not installed.")
print("Install: sudo pacman -S python-cryptography or pip install cryptography")
exit(1)
PASS = 0
FAIL = 0
def check(name, expected, actual):
global PASS, FAIL
if expected == actual:
PASS += 1
print(f" ✅ {name}")
else:
FAIL += 1
print(f" ❌ {name}")
print(f" expected: {expected.hex() if isinstance(expected, bytes) else expected}")
print(f" actual: {actual.hex() if isinstance(actual, bytes) else actual}")
print("\n=== 1. HMAC-SHA1 (RFC 2202) ===")
mac = hmac.new(bytes([0x0b]*20), b"Hi There", hashlib.sha1).digest()
check("RFC2202 case 1 (20-byte key)",
unhexlify("b617318655057264e28bc0b6fb378c8ef146be00"), mac)
mac = hmac.new(b"Jefe", b"what do ya want for nothing?", hashlib.sha1).digest()
check("RFC2202 case 2",
unhexlify("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"), mac)
mac = hmac.new(bytes([0xaa]*20), bytes([0xdd]*50), hashlib.sha1).digest()
check("RFC2202 case 4 (50-byte data)",
unhexlify("125d7342b9ac11cd91a39af48aa17b4f63f175d3"), mac)
print("\n=== 2. PBKDF2-HMAC-SHA1 (RFC 6070) ===")
kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=20, salt=b"salt", iterations=1)
dk = kdf.derive(b"password")
check("RFC6070 c=1",
unhexlify("0c60c80f961f0e71f3a9b524af6012062fe037a6"), dk)
kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=20, salt=b"salt", iterations=2)
dk = kdf.derive(b"password")
check("RFC6070 c=2",
unhexlify("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"), dk)
kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=20, salt=b"salt", iterations=4096)
dk = kdf.derive(b"password")
check("RFC6070 c=4096",
unhexlify("4b007901b765489abead49d926f721d065a429c1"), dk)
print("\n=== 3. string_to_key (RFC 3962 Appendix B, c=1200) ===")
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def nfold(data, nbits):
nbytes = nbits // 8
slen = len(data)
if slen == 0:
return bytes(nbytes)
import math
lcm_val = slen * nbytes // math.gcd(slen, nbytes)
def rotate_right(buf, nbits):
nbytes = nbits // 8
remain = nbits % 8
result = bytearray(len(buf))
for i in range(len(buf)):
idx1 = (len(buf) + i - nbytes) % len(buf)
idx2 = (len(buf) + i - nbytes - 1) % len(buf)
if remain == 0:
result[i] = buf[idx1]
else:
result[i] = (buf[idx1] >> remain) | ((buf[idx2] << (8 - remain)) & 0xFF)
return bytes(result)
bigstr = bytearray()
for i in range(lcm_val // slen):
bigstr.extend(rotate_right(data, 13 * i))
def add_ones_complement(a, b):
carry = 0
result = bytearray(len(a))
for i in range(len(a) - 1, -1, -1):
s = a[i] + b[i] + carry
result[i] = s & 0xFF
carry = s >> 8
while carry:
carry = 0
for i in range(len(a) - 1, -1, -1):
s = result[i] + 1
result[i] = s & 0xFF
if s >> 8 == 0:
break
carry = s >> 8
return bytes(result)
result = bytes(nbytes)
first = True
for p in range(0, lcm_val, nbytes):
slice_data = bytes(bigstr[p:p + nbytes])
if first:
result = slice_data
first = False
else:
result = add_ones_complement(result, slice_data)
return result
def dk_aes(key, constant, out_len):
block = nfold(constant, 128)
result = bytearray()
cipher = Cipher(algorithms.AES(key), modes.ECB())
encryptor = cipher.encryptor()
while len(result) < out_len:
ct = encryptor.update(block)
result.extend(ct[:min(16, out_len - len(result))])
block = ct
return bytes(result[:out_len])
def string_to_key(password, salt, iter_count, key_len):
kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=key_len, salt=salt, iterations=iter_count)
tkey = kdf.derive(password)
return dk_aes(tkey, b"kerberos", key_len)
k = string_to_key(b"password", b"ATHENA.MIT.EDUraeburn", 1200, 16)
check("s2k AES128 c=1200",
unhexlify("4c01cd46d632d01e6dbe230a01ed642a"), k)
k = string_to_key(b"password", b"ATHENA.MIT.EDUraeburn", 1200, 32)
check("s2k AES256 c=1200",
unhexlify("55a6ac740ad17b4846941051e1e8b0a7548d93b0ab30a8bc3ff16280382b8c2a"), k)
print("\n=== 4. AES-128-CTS (RFC 3962 Appendix B) ===")
key = b"chicken teriyaki"
iv = bytes(16)
def cts_encrypt(key, iv, plaintext):
bs = 16
if len(plaintext) <= bs:
cipher = Cipher(algorithms.AES(key), modes.ECB())
e = cipher.encryptor()
if len(plaintext) < bs:
block = plaintext + bytes(bs - len(plaintext))
else:
block = plaintext
ct = e.update(block) + e.finalize()
return ct[:len(plaintext)]
if len(plaintext) % bs == 0:
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
e = cipher.encryptor()
ct = e.update(plaintext) + e.finalize()
ct_list = bytearray(ct)
last_two = len(ct) - 2*bs
ct_list[last_two:last_two+bs], ct_list[last_two+bs:] = ct_list[last_two+bs:], ct_list[last_two:last_two+bs]
return bytes(ct_list)
else:
print(f" ⚠ CTS non-multiple ({len(plaintext)}B) not verified in Python")
return None
ct = cts_encrypt(key, iv, b"I would like the ")
if ct:
check("CTS 17 bytes", unhexlify("c6353568f2bf8cb4d8a580362da7ff7f97"), ct)
ct = cts_encrypt(key, iv, b"I would like the General Gau's ")
if ct:
check("CTS 31 bytes", unhexlify("fc00783e0efdb2c1d445d4c8eff7ed2297687268d6ecccc0c07b25e25ecfe5"), ct)
ct = cts_encrypt(key, iv, b"I would like the General Gau's C")
if ct:
check("CTS 32 bytes", unhexlify("39312523a78662d5be7fcbcc98ebf5a897687268d6ecccc0c07b25e25ecfe584"), ct)
pt64 = b"I would like the General Gau's Chicken, please, and wonton soup."
ct = cts_encrypt(key, iv, pt64)
if ct:
expected = unhexlify("97687268d6ecccc0c07b25e25ecfe58439312523a78662d5be7fcbcc98ebf5a84807efe836ee89a526730dbc2f7bc8409dad8bbb96c4cdc03bc103e1a194bbd8")
check("CTS 64 bytes", expected, ct)
print(f"\n{'='*40}")
print(f"Results: {PASS} passed, {FAIL} failed, {PASS+FAIL} total")