Hi sci.crypt,
After seeing it in the newsgroup so many times, I wanted to write my
own implementation of this cipher. I haven't looked into the cipher in
too much detail, only enough to make our outputs match. Therefore I am
not endorsing its use nor am I denouncing it in any way.
The reference implementation that I tried to replicate the behavior of
is the JS version here [1].
From my tests, it is able to decrypt messages created by the JS
version, and encrypt messages that the JS version can then decrypt.
[1]:
https://fractallife247.com/test/hmac_cipher/ver_0_0_0_1/
I'll be pasting the code below.
#!/usr/bin/env python3
import hmac
import os
import sys
# ct_hmac_cipher.py
# Python implementation of Chris M. Thomasson's HMAC cipher
# Copyright (C) Gokberk Yaltirakli, 2021
# The reference implementation can be found at
#
https://fractallife247.com/test/hmac_cipher/ver_0_0_0_1/.
# This is an implementation of Chris M. Thosmasson's HMAC cipher. The
# Chris' JS implementation and this Python implementation can encrypt
# and decrypt the same data. Their inputs and outputs are compatible
# as of 2021-11-21.
# These are the algorithm parameters. They can be changed by
# command-line arguments. The reference implenmentation in JS only
# supports two rounds.
CONFIG = {}
CONFIG["algo"] = "sha256"
CONFIG["hex_input"] = "false"
CONFIG["hex_output"] = "false"
CONFIG["key"] = "Password"
CONFIG["rand_n"] = "64"
CONFIG["rounds"] = "2"
CONFIG["verbose"] = "false"
def log(*a):
if CONFIG["verbose"] != "true":
# Do not write logs if we are not in verbose mode
return
sys.stdout.buffer.write("".join(map(str, a)).encode("utf-8"))
sys.stdout.buffer.write(b"\n")
def get_hmac_function(algo):
functions = {}
functions["sha256"] = lambda x, y: hmac.digest(x, y, "sha256")
functions["sha512"] = lambda x, y: hmac.digest(x, y, "sha512")
return functions[algo]
def ct_hmac_round(buf, decrypt):
hm = get_hmac_function(CONFIG["algo"])
key = CONFIG["key"].encode("utf-8")
hmac_buf = bytes(key[::-1])
res = b""
p_i = 0
while p_i < len(buf):
digest = hm(key, hmac_buf)
log(" Digest: ", digest.hex())
for x in digest:
cip = buf[p_i] ^ x
res += bytes([cip])
if decrypt:
hmac_buf += bytes([cip, buf[p_i]])
else:
hmac_buf += bytes([buf[p_i], cip])
p_i += 1
if p_i == len(buf):
break
return res
def ct_hmac_cipher(buf, decrypt):
if not decrypt:
# We are encrypting a plaintext. Prepend a random buffer in
# front of the plaintext.
rand = os.urandom(int(CONFIG["rand_n"]))
buf = rand + buf
rounds = int(CONFIG["rounds"])
for rnd in range(rounds):
log(f"Round {rnd}")
log(" Before: ", buf.hex())
buf = ct_hmac_round(buf, decrypt)
if rnd != rounds - 1:
buf = buf[::-1] # Reverse the buffer
log(" After: ", buf.hex())
if decrypt:
buf = buf[int(CONFIG["rand_n"]) :]
return buf
def run_cipher(decrypt):
input_buf = sys.stdin.buffer.read()
if CONFIG["hex_input"] == "true":
hex_input = input_buf.replace(b"\n", b"")
hex_input = hex_input.decode("ascii")
input_buf = bytes.fromhex(hex_input)
log(f"Input buffer is {len(input_buf)} bytes")
out_buf = ct_hmac_cipher(input_buf, decrypt)
log(f"Output buffer is {len(out_buf)} bytes")
if CONFIG["hex_output"] == "true":
for i, c in enumerate(out_buf.hex()):
sys.stdout.buffer.write(c.encode("ascii"))
if i % 70 == 69:
sys.stdout.buffer.write(b"\n")
sys.stdout.buffer.write(b"\n")
else:
sys.stdout.buffer.write(out_buf)
def action_help():
print("Usage\n")
print(f"{NAME} encrypt key <key>")
print(" Encrypt stdin with the key <key>.")
print()
print(f"{NAME} decrypt key <key>")
print(" Decrypt stdin with the key <key>.")
print()
print("This is all the help you get, read the code for more")
def action_encrypt():
log("Running cipher in encryption mode")
run_cipher(False)
def action_decrypt():
log("Running cipher in decryption mode")
run_cipher(True)
def main():
globals()["NAME"] = sys.argv.pop(0)
try:
action = sys.argv.pop(0)
except:
action = "help"
while sys.argv:
name = sys.argv.pop(0)
val = sys.argv.pop(0)
CONFIG[name] = val
fname = f"action_{action}"
if fname in globals():
fn = globals()[f"action_{action}"]
fn()
else:
print(f"Unknown command: {action}")
if __name__ == "__main__":
main()
--
Leo