Skip to content

Instantly share code, notes, and snippets.

@TheExpertNoob
Created March 19, 2026 22:43
Show Gist options
  • Select an option

  • Save TheExpertNoob/dc9d0fc6f11b84b39e86c1f837298ec3 to your computer and use it in GitHub Desktop.

Select an option

Save TheExpertNoob/dc9d0fc6f11b84b39e86c1f837298ec3 to your computer and use it in GitHub Desktop.
Generate Tinfoil HAUTH
#!/usr/bin/env python3
"""
Usage:
python3 HAUTHgen.py <seed> <url>
Example:
python3 HAUTHgen.py "Cancel" "https://shop.nlib.cc/"
"""
import sys
import hashlib
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
def aes128_encrypt_block(key: bytes, block: bytes) -> bytes:
c = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
e = c.encryptor()
return e.update(block) + e.finalize()
def left_shift_one(b: bytearray) -> bytearray:
out = bytearray(16)
carry = 0
for i in range(15, -1, -1):
next_carry = (b[i] & 0x80) >> 7
out[i] = ((b[i] << 1) | carry) & 0xFF
carry = next_carry
return out
def derive_subkey(b: bytes) -> bytes:
arr = bytearray(b)
msb_set = (arr[0] & 0x80) != 0
arr = left_shift_one(arr)
if msb_set:
arr[15] ^= 0x87
return bytes(arr)
def aes_cmac(key: bytes, msg: bytes) -> bytes:
zero = bytes(16)
L = aes128_encrypt_block(key, zero)
K1 = derive_subkey(L)
K2 = derive_subkey(K1)
block_size = 16
msg_len = len(msg)
n = max((msg_len + block_size - 1) // block_size, 1)
is_complete = (msg_len != 0) and (msg_len % block_size == 0)
mLast = bytearray(16)
if is_complete:
last = msg[(n - 1) * block_size:]
for i in range(16):
mLast[i] = last[i] ^ K1[i]
else:
rem = msg_len % block_size
last = msg[msg_len - rem:] if rem else b''
for i in range(rem):
mLast[i] = last[i]
mLast[rem] = 0x80
for i in range(16):
mLast[i] ^= K2[i]
x = bytearray(16)
for i in range(n - 1):
block = msg[i * block_size:(i + 1) * block_size]
y = bytearray(a ^ b for a, b in zip(x, block))
x = bytearray(aes128_encrypt_block(key, bytes(y)))
y = bytearray(a ^ b for a, b in zip(x, mLast))
return aes128_encrypt_block(key, bytes(y))
def build_host_part(url: str) -> str | None:
if not url:
return None
scheme_sep = url.find("://")
if scheme_sep <= 0:
return None
netloc_start = scheme_sep + 3
if netloc_start >= len(url):
return None
end = len(url)
for ch in '/?#':
idx = url.find(ch, netloc_start)
if idx != -1:
end = min(end, idx)
netloc = url[netloc_start:end]
if not netloc:
return None
scheme = url[:scheme_sep].lower()
if scheme == 'https' and netloc.endswith(':443'):
netloc = netloc[:-4]
elif scheme == 'http' and netloc.endswith(':80'):
netloc = netloc[:-3]
return f"{scheme}://{netloc}"
def compute_hauth(seed: str, url: str) -> str:
host_part = build_host_part(url)
if not host_part:
return "0"
key = hashlib.sha256(seed.encode('utf-8')).digest()[:16]
msg = host_part.encode('utf-8') + b'\x00'
return aes_cmac(key, msg).hex().upper()
if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <seed> <url>")
sys.exit(1)
seed, url = sys.argv[1], sys.argv[2]
hauth = compute_hauth(seed, url)
print(f"HAUTH: {hauth}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment