• 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