Going for an attack vector against my HMAC cipher...
From
Chris M. Thomasson@chris.m.thomasson.1@gmail.com to
sci.crypt on Thu Dec 4 23:25:38 2025
From Newsgroup: sci.crypt
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, hmac, hashlib, random, math, collections
# -----------------------------
# Cipher (TRNG-only, snapshot digest)
# -----------------------------
def ct_rand_bytes(n: int) -> str:
rb = os.urandom(n)
return "".join(chr(b) for b in rb)
class ct_secret_key:
def __init__(self, hmac_key, hash_algo, rand_n):
ds = hash_algo().digest_size
if rand_n < ds:
raise ValueError(f"rand_n ({rand_n}) must be >= digest size ({ds}).")
self.hmac_key = hmac_key
self.hash_algo = hash_algo
self.rand_n = rand_n
def ct_hmac_new(sk: ct_secret_key):
return hmac.new(sk.hmac_key.encode(), None, sk.hash_algo)
def h_digest(H: hmac.HMAC) -> bytes:
return H.copy().digest()
class ct_bin:
def __init__(self, bstr: str):
self.bytes = bstr
def ct_crypt_round(SK: ct_secret_key, P: ct_bin, M: bool) -> ct_bin:
H = ct_hmac_new(SK)
H.update(SK.hmac_key[::-1].encode())
C = ""
I_P = 0
I_P_N = len(P.bytes)
while (I_P < I_P_N):
D = h_digest(H)
I_D = 0
I_D_N = len(D)
while (I_P < I_P_N and I_D < I_D_N):
C_I_P = ord(P.bytes[I_P]) ^ D[I_D]
C += chr(C_I_P)
if (M == False): # ENCRYPT
H.update(P.bytes[I_P].encode())
H.update(chr(C_I_P).encode())
else: # DECRYPT
H.update(chr(C_I_P).encode())
H.update(P.bytes[I_P].encode())
I_P += 1
I_D += 1
return ct_bin(C)
def ct_crypt(SK: ct_secret_key, P: ct_bin, M: bool) -> ct_bin:
if (M == False): # ENCRYPT
R = ct_rand_bytes(SK.rand_n)
P.bytes = R + P.bytes
C = ct_crypt_round(SK, P, M)
C_1 = ct_bin(C.bytes[::-1])
C = ct_crypt_round(SK, C_1, M)
if (M == True): # DECRYPT
size = len(C.bytes) - SK.rand_n
C.bytes = C.bytes[SK.rand_n : SK.rand_n + size]
return C
# -----------------------------
# Oracle
# -----------------------------
TARGET_HDR = "MAGIC:"
def oracle_is_valid(SK: ct_secret_key, C: str) -> bool:
P = ct_crypt(SK, ct_bin(C), True)
return P.bytes.startswith(TARGET_HDR)
# -----------------------------
# Frequency analysis
# -----------------------------
def freq_analysis(text: str):
counter = collections.Counter(text)
total = len(text)
entropy = 0.0
for count in counter.values():
p = count / total
entropy -= p * math.log2(p)
print(f" Freq analysis: length={total}, unique={len(counter)}, entropyree{entropy:.2f} bits")
# Show top 10 most common bytes
top10 = counter.most_common(10)
print(" Top10 bytes:", [(repr(ch), cnt) for ch, cnt in top10])
# -----------------------------
# Attack A: Single-byte flips + freq analysis
# -----------------------------
def attack_A(SK: ct_secret_key, C: str, max_positions=32):
print("\n=== Attack A: Single-byte flips with frequency analysis ===")
attempts = 0
for pos in range(min(max_positions, len(C))):
for delta in range(1, 256):
attempts += 1
lst = list(C)
lst[pos] = chr(ord(lst[pos]) ^ delta)
C2 = "".join(lst)
P2 = ct_crypt(SK, ct_bin(C2), True).bytes
if oracle_is_valid(SK, C2):
print(f" -> Valid header forced at pos={pos}, delta={delta}")
freq_analysis(P2)
print(f"Attempts={attempts}")
return True
print(f"No success after {attempts} attempts.")
return False
# -----------------------------
# Main
# -----------------------------
def main():
hmac_key = "This is the HMAC Key. It should be a crypto secure key!
Damn it."
hash_algo = hashlib.sha384
rand_n = 80 # reN digest size
SK = ct_secret_key(hmac_key, hash_algo, rand_n)
victim_plaintext = "SECRET:" + "".join(chr((x * 131) & 0xFF) for x
in range(180))
C = ct_crypt(SK, ct_bin(victim_plaintext), False).bytes
print(f"Ciphertext length={len(C)}, rand_n={rand_n}, digest={hash_algo().digest_size}")
assert not oracle_is_valid(SK, C), "Original ciphertext already valid!"
attack_A(SK, C, max_positions=32)
if __name__ == "__main__":
main()
--- Synchronet 3.21a-Linux NewsLink 1.2
From
Chris M. Thomasson@chris.m.thomasson.1@gmail.com to
sci.crypt on Thu Dec 4 23:34:53 2025
From Newsgroup: sci.crypt
On 12/4/2025 11:25 PM, Chris M. Thomasson wrote:
[...]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, hmac, hashlib, random, math, collections
# -----------------------------
# Cipher (TRNG-only, snapshot digest)
# -----------------------------
def ct_rand_bytes(n: int) -> str:
rb = os.urandom(n)
return "".join(chr(b) for b in rb)
class ct_secret_key:
def __init__(self, hmac_key, hash_algo, rand_n):
ds = hash_algo().digest_size
if rand_n < ds:
raise ValueError(f"rand_n ({rand_n}) must be >= digest size ({ds}).")
self.hmac_key = hmac_key
self.hash_algo = hash_algo
self.rand_n = rand_n
def ct_hmac_new(sk: ct_secret_key):
return hmac.new(sk.hmac_key.encode(), None, sk.hash_algo)
def h_digest(H: hmac.HMAC) -> bytes:
return H.copy().digest()
class ct_bin:
def __init__(self, bstr: str):
self.bytes = bstr
def ct_crypt_round(SK: ct_secret_key, P: ct_bin, M: bool) -> ct_bin:
H = ct_hmac_new(SK)
H.update(SK.hmac_key[::-1].encode())
C = ""
I_P = 0
I_P_N = len(P.bytes)
while (I_P < I_P_N):
D = h_digest(H)
I_D = 0
I_D_N = len(D)
while (I_P < I_P_N and I_D < I_D_N):
C_I_P = ord(P.bytes[I_P]) ^ D[I_D]
C += chr(C_I_P)
if (M == False): # ENCRYPT
H.update(P.bytes[I_P].encode())
H.update(chr(C_I_P).encode())
else: # DECRYPT
H.update(chr(C_I_P).encode())
H.update(P.bytes[I_P].encode())
I_P += 1
I_D += 1
return ct_bin(C)
def ct_crypt(SK: ct_secret_key, P: ct_bin, M: bool) -> ct_bin:
if (M == False): # ENCRYPT
R = ct_rand_bytes(SK.rand_n)
P.bytes = R + P.bytes
C = ct_crypt_round(SK, P, M)
C_1 = ct_bin(C.bytes[::-1])
C = ct_crypt_round(SK, C_1, M)
if (M == True): # DECRYPT
size = len(C.bytes) - SK.rand_n
C.bytes = C.bytes[SK.rand_n : SK.rand_n + size]
return C
# -----------------------------
# Oracle
# -----------------------------
TARGET_HDR = "MAGIC:"
def oracle_is_valid(SK: ct_secret_key, C: str) -> bool:
P = ct_crypt(SK, ct_bin(C), True)
return P.bytes.startswith(TARGET_HDR)
# -----------------------------
# Analysis helpers (pure Python)
# -----------------------------
def freq_entropy(text: str):
counter = collections.Counter(text)
total = len(text)
entropy = 0.0
for count in counter.values():
p = count / total
entropy -= p * math.log2(p)
return entropy, counter
def autocorr(text: str, max_lag=16):
arr = [ord(ch) for ch in text]
mean = sum(arr)/len(arr)
arr = [x-mean for x in arr]
result = []
for lag in range(1, max_lag+1):
corr = sum(arr[i]*arr[i+lag] for i in range(len(arr)-lag))
result.append(corr/(len(arr)-lag))
return result
def show_stats(P: str):
ent, counter = freq_entropy(P)
print(f" Plaintext length={len(P)}, entropyree{ent:.2f} bits, unique={len(counter)}")
print(" Top5 bytes:", [(repr(ch), cnt) for ch, cnt in counter.most_common(5)])
ac = autocorr(P, max_lag=8)
print(" Autocorr (lags 1rCo8):", [round(x,2) for x in ac])
# -----------------------------
# Test 1: Multi-byte flip grids
# -----------------------------
def test_multi_byte(SK, C, positions=6):
print("\n=== Test 1: Multi-byte flip grids ===")
success = False
attempts = 0
for pos1 in range(positions):
for pos2 in range(pos1+1, positions):
for delta1 in (1,2):
for delta2 in (1,2):
attempts += 1
lst = list(C)
lst[pos1] = chr(ord(lst[pos1]) ^ delta1)
lst[pos2] = chr(ord(lst[pos2]) ^ delta2)
C2 = "".join(lst)
P2 = ct_crypt(SK, ct_bin(C2), True).bytes
if oracle_is_valid(SK, C2):
print(f" -> Valid header forced at
pos={pos1},{pos2} with deltas={delta1},{delta2}")
show_stats(P2)
success = True
return
print(f"No success after {attempts} attempts.")
return success
# -----------------------------
# Test 2: Entropy profiling
# -----------------------------
def test_entropy_profile(SK, C, samples=20):
print("\n=== Test 2: Entropy profiling under tampering ===")
rng = random.Random(123)
entropies = []
for s in range(samples):
lst = list(C)
pos = rng.randrange(len(C))
delta = rng.randrange(1,256)
lst[pos] = chr(ord(lst[pos]) ^ delta)
C2 = "".join(lst)
P2 = ct_crypt(SK, ct_bin(C2), True).bytes
ent, _ = freq_entropy(P2)
entropies.append(ent)
print(f" Sampled {samples} tampered decryptions.")
print(f" Entropy mean={sum(entropies)/len(entropies):.2f}, min={min(entropies):.2f}, max={max(entropies):.2f}")
# -----------------------------
# Test 3: Autocorrelation check
# -----------------------------
def test_autocorr(SK, C, samples=5):
print("\n=== Test 3: Autocorrelation checks ===")
rng = random.Random(456)
for s in range(samples):
lst = list(C)
pos = rng.randrange(len(C))
delta = rng.randrange(1,256)
lst[pos] = chr(ord(lst[pos]) ^ delta)
C2 = "".join(lst)
P2 = ct_crypt(SK, ct_bin(C2), True).bytes
ac = autocorr(P2, max_lag=8)
print(f" Sample {s}: autocorr lags 1rCo8:", [round(x,2) for x in ac])
# -----------------------------
# Main
# -----------------------------
def main():
hmac_key = "This is the HMAC Key. It should be a crypto secure key!
Damn it."
hash_algo = hashlib.sha384
rand_n = 80 # reN digest size
SK = ct_secret_key(hmac_key, hash_algo, rand_n)
victim_plaintext = "SECRET:" + "".join(chr((x * 131) & 0xFF) for x
in range(180))
C = ct_crypt(SK, ct_bin(victim_plaintext), False).bytes
print(f"Ciphertext length={len(C)}, rand_n={rand_n}, digest={hash_algo().digest_size}")
assert not oracle_is_valid(SK, C), "Original ciphertext already valid!"
test_multi_byte(SK, C, positions=6)
test_entropy_profile(SK, C, samples=20)
test_autocorr(SK, C, samples=5)
print("\nAll tests complete.")
if __name__ == "__main__":
main()
--- Synchronet 3.21a-Linux NewsLink 1.2
From
Chris M. Thomasson@chris.m.thomasson.1@gmail.com to
sci.crypt on Fri Dec 5 00:15:10 2025
From Newsgroup: sci.crypt
Experimental HMAC Cipher
Core Principles
No cleartext IV/nonce: Unlike conventional designs, this cipher does not transmit an IV or nonce in the clear. Instead, every ciphertext carries
its own veil of true random bytes, encrypted as part of the payload.
TRNG veil inclusion: Each encryption run prepends rand_n bytes of true randomness to the plaintext. These veil bytes are encrypted along with
the message, ensuring decorrelation and avalanche amplification.
Ciphertext expansion: Ciphertext size = plaintext size + rand_n. This expansion is intentional and required. The veil is not metadata; it is
part of the encrypted stream.
Hash as part of the secret: The choice of hash algorithm (SHA-256,
SHA-384, SHA-512, SHA-3, etc.) is treated as part of the secret ritual.
This departs from conventional practice, but strengthens
unpredictability in the experimental frame.
Guardrails
rand_n reN digest size: Ensures veil entropy covers the digest snapshots
of the chosen hash.
TRNG required: Pseudo-random or counter veils collapse the avalanche and
leak structure. Only true randomness preserves resilience.
Snapshot digests: HMAC state is copied before finalization, mirroring PythonrCOs semantics. This preserves ongoing state while producing
keystream blocks.
Feedback ordering: Plaintext and ciphertext feedback are applied
differently for encryption vs decryption, ensuring asymmetry and avalanche.
Properties
Bit sensitivity: A single bit flip in plaintext or veil cascades
unpredictably through ciphertext.
Ciphertext uniqueness: Identical plaintexts produce distinct ciphertexts
every run, without public nonces.
Replay resistance: No two ciphertexts are alike, even under the same key.
Attacker frustration: With veil hidden and encrypted, no alignment or
cadence probes succeed.
Failure Gallery
rand_n < digest size: Digest cadence leaks, forging rates rise.
Pseudo-random veil: Autocorrelation peaks emerge, entropy drops.
Public IV/nonce: Prefix alignment attacks succeed.
Framing
This is not a replacement for AES or ChaCha20. It is an experimental
cipher exploring avalanche, decorrelation, and ritualized entropy. Its unconventional choices (hidden veil, hash secrecy, ciphertext expansion)
are deliberate, not mistakes.
Chant
rCLGov says: veil hidden, hash secret rCo not wrong, just ritual, experiment stands.rCY
--- Synchronet 3.21a-Linux NewsLink 1.2