#!/usr/bin/env python3
import hmac
+import json
+import sys
from argparse import ArgumentParser
from getpass import getpass
from hashlib import sha1
self.password = password
self.indices = self.init_indices(self.get_hash(self.password, self.iv))
+ def pw_mac(self):
+ return self.get_hash(self.iv, self.password)
+
def init_indices(self, first_key) -> list[int]:
indices = list(range(20))
pick_accu = 0
opad_ba[i] ^= c
return sha1(ipad_ba), sha1(opad_ba)
- def scramble(self, data):
- bio = BytesIO()
+ def scramble(self, data, fh=None):
+ if fh is None:
+ fh = BytesIO()
round_key = self.iv
ipad_template, opad_template = self.prepare_ipad_opad()
for i in range(0, len(data), 10):
- #derived = self.get_hash(
- # self.password, round_key := self.advance_round_key(round_key)
- #)
round_key = self.advance_round_key(round_key)
ipad, opad = ipad_template.copy(), opad_template.copy()
ipad.update(round_key)
opad.update(ipad.digest())
block = data[i:i + 10]
- bio.write(bytes(a ^ b for a, b in zip(block, opad.digest()[:len(block)])))
- return bio.getvalue()
+ fh.write(bytes(a ^ b for a, b in zip(block, opad.digest()[:len(block)])))
+ return fh
-class HeaderReader:
+class HeaderCodec:
def __init__(self):
self.iv_bio = BytesIO()
self.pw_mac_bio = BytesIO()
if done:
self.current_bio = None
- def parse(self, fh):
+ @classmethod
+ def parse(cls, fh):
+ header = cls()
for line in fh:
line = line.strip()
- if self.current_bio is None:
- self.find_header(line)
+ if header.current_bio is None:
+ header.find_header(line)
else:
- self.add_bytes(line)
+ header.add_bytes(line)
+ return header
@property
def iv(self):
assert len(secret) > 0
return secret
-
-def cleanup_secret(infh):
- return infh.read()
- bio = BytesIO()
- for line in infh:
- line = line.strip()
- if not line:
- continue
- assert len(line) < 79 and b":" in line
- bio.write(line)
- bio.write(b"\n")
- return bio.getvalue()
-
-
-class HeaderWriter:
@staticmethod
def indent(i):
return " " * i
- def __init__(self, fh, password):
- self.fh = fh
- self.iv = token_bytes(20)
- self.password = password
-
- def print_as_const_sha_buf(self, varname, data):
- print(f"static const struct sha1_buf {varname} PROGMEM = {{", file=self.fh)
- print(f"{self.indent(4)}{{\n{self.indent(8)}", end="", file=self.fh)
+ @classmethod
+ def print_as_const_sha_buf(cls, fh, varname, data):
+ print(f"static const struct sha1_buf {varname} PROGMEM = {{", file=fh)
+ print(f"{cls.indent(4)}{{\n{cls.indent(8)}", end="", file=fh)
last = len(data) - 1
for i, b in enumerate(data):
- print(f"0x{b:02x}", end=",", file=self.fh)
+ print(f"0x{b:02x}", end=",", file=fh)
if i == last:
- print(f"\n{self.indent(4)}}}", file=self.fh)
+ print(f"\n{cls.indent(4)}}}", file=fh)
elif i % 12 == 11:
- print(f"\n{self.indent(8)}", end="", file=self.fh)
+ print(f"\n{cls.indent(8)}", end="", file=fh)
else:
- print(end=" ", file=self.fh)
- print("};\n", file=self.fh)
+ print(end=" ", file=fh)
+ print("};\n", file=fh)
- def print_as_const(self, varname, data):
- print(f"static const uint8_t {varname}[] PROGMEM = {{", file=self.fh)
- print(f"{self.indent(4)}", end="", file=self.fh)
+ @classmethod
+ def print_as_const(cls, fh, varname, data):
+ print(f"static const uint8_t {varname}[] PROGMEM = {{", file=fh)
+ print(f"{cls.indent(4)}", end="", file=fh)
last = len(data) - 1
for i, b in enumerate(data):
- print(f"0x{b:02x}", end=",", file=self.fh)
+ print(f"0x{b:02x}", end=",", file=fh)
if i == last:
- print(file=self.fh)
+ print(file=fh)
elif i % 12 == 11:
- print(f"\n{self.indent(4)}", end="", file=self.fh)
+ print(f"\n{cls.indent(4)}", end="", file=fh)
else:
- print(end=" ", file=self.fh)
- print("};\n", file=self.fh)
-
- def write(self, secret):
- print("\n// secrets.h\n", file=self.fh)
- print("#ifndef SECRETS_H", file=self.fh)
- print("#define SECRETS_H\n", file=self.fh)
- print("#include <stdint.h>", file=self.fh)
- print("#include <avr/pgmspace.h>\n", file=self.fh)
- print('#include "sha1.h"\n', file=self.fh)
- self.print_as_const_sha_buf("iv", self.iv)
- self.print_as_const_sha_buf(
- "pw_mac", CryptoManager.get_hash(self.iv, self.password)
- )
- self.print_as_const(
- "secret",
- CryptoManager(self.iv, self.password).scramble(secret),
- )
- print("#endif // SECRETS_H", file=self.fh)
+ print(end=" ", file=fh)
+ print("};\n", file=fh)
+
+ @classmethod
+ def write(cls, fh, iv, pw_mac, secret):
+ print("\n// secrets.h\n", file=fh)
+ print("#ifndef SECRETS_H", file=fh)
+ print("#define SECRETS_H\n", file=fh)
+ print("#include <stdint.h>", file=fh)
+ print("#include <avr/pgmspace.h>\n", file=fh)
+ print('#include "sha1.h"\n', file=fh)
+ cls.print_as_const_sha_buf(fh, "iv", iv)
+ cls.print_as_const_sha_buf(fh, "pw_mac", pw_mac)
+ cls.print_as_const(fh, "secret", secret)
+ print("#endif // SECRETS_H", file=fh)
+
+
+def cleanup_secret(infh):
+ bio = BytesIO()
+ for line in infh:
+ line = line.strip()
+ if not line:
+ continue
+ assert len(line) < 79 and b":" in line
+ bio.write(line)
+ bio.write(b"\n")
+ return bio.getvalue()
def encrypt_secrets(project_dir, infile, new_password):
password = getpass().encode()
secrets_h = project_dir / "secrets.h"
if not new_password and secrets_h.exists():
- header = HeaderReader()
with secrets_h.open("rt") as fh:
- header.parse(fh)
+ header = HeaderCodec.parse(fh)
assert CryptoManager.get_hash(header.iv, password) == header.pw_mac
else:
other = getpass("Confirm password: ").encode()
assert password == other
with Path(infile).open("rb") as infh, secrets_h.open("wt") as outfh:
- HeaderWriter(outfh, password).write(cleanup_secret(infh))
+ crypto_manager = CryptoManager(token_bytes(20), password)
+ HeaderCodec.write(
+ outfh,
+ crypto_manager.iv,
+ crypto_manager.pw_mac(),
+ crypto_manager.scramble(cleanup_secret(infh)).getvalue(),
+ )
def decrypt_secrets(project_dir, outfile):
password = getpass().encode()
secrets_h = project_dir / "secrets.h"
assert secrets_h.exists()
- header = HeaderReader()
with secrets_h.open("rt") as fh:
- header.parse(fh)
+ header = HeaderCodec.parse(fh)
assert CryptoManager.get_hash(header.iv, password) == header.pw_mac
- with Path(outfile).open("wb") as fh:
- fh.write(CryptoManager(header.iv, password).scramble(header.secret))
+ outfile_path = Path(outfile)
+ assert outfile_path.absolute() != secrets_h.absolute(), (
+ "Don't decrypt into secrets.h"
+ )
+ with outfile_path.open("wb") as fh:
+ CryptoManager(header.iv, password).scramble(header.secret, fh)
-def main():
+def from_dump(project_dir, outfile):
+ print("reading dump > ", end="")
+ sys.stdout.flush()
+ data = json.load(sys.stdin)
+ iv = bytes.fromhex(data["iv"])
+ pw_mac = bytes.fromhex(data["pw_mac"])
+ secret = bytes.fromhex(data["secret"])
+ print("done")
+ if outfile is None:
+ outfile = project_dir / "secrets.h"
+ else:
+ outfile = Path(outfile)
+ with outfile.open("wt") as fh:
+ HeaderCodec.write(fh, iv, pw_mac, secret)
+
+
+def parse_args():
ap = ArgumentParser()
sp = ap.add_subparsers(dest="action", required=True, help="Actions")
ep = sp.add_parser("encrypt")
ep.add_argument("--new-password", action="store_true")
dp = sp.add_parser("decrypt")
dp.add_argument("outfile")
- args = ap.parse_args()
+ mp = sp.add_parser("from_dump")
+ mp.add_argument("outfile", nargs="?", default=None)
+ return ap.parse_args()
+
+
+def main():
+ args = parse_args()
project_dir = Path(__file__).parent.absolute()
if args.action == "encrypt":
encrypt_secrets(project_dir, args.infile, args.new_password)
elif args.action == "decrypt":
decrypt_secrets(project_dir, args.outfile)
+ elif args.action == "from_dump":
+ from_dump(project_dir, args.outfile)
if __name__ == "__main__":