diff --git a/keygen.py b/keygen.py index 2d09bbb..4855883 100644 --- a/keygen.py +++ b/keygen.py @@ -2,35 +2,22 @@ """ SSH key generator with BIP39 mnemonic backup. -generate — creates a new Ed25519 SSH key pair and prints 24 recovery words + timestamp -recover — re-creates the exact same key pair from those 24 words + timestamp +generate — creates a new Ed25519 SSH key pair and prints 24 recovery words +recover — re-creates the exact same key pair from those 24 words """ import argparse import getpass import os import sys -from datetime import datetime, timezone from pathlib import Path -from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey -from cryptography.hazmat.primitives.kdf.hkdf import HKDF from mnemonic import Mnemonic ENTROPY_BITS = 256 # → 24 BIP39 words -TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S UTC" - - -def derive_seed(entropy: bytes, timestamp: str) -> bytes: - """Derive 32-byte Ed25519 seed from mnemonic entropy + timestamp.""" - return HKDF( - algorithm=hashes.SHA256(), - length=32, - salt=timestamp.encode(), - info=b"ssh-seed-keygen v1", - ).derive(entropy) def save_keypair( @@ -113,25 +100,11 @@ def ask_passphrase(confirm: bool = True) -> bytes | None: return passphrase.encode() -def ask_timestamp() -> str: - print(f"Generation timestamp (format: YYYY-MM-DD HH:MM:SS UTC):") - raw = input("Timestamp: ").strip() - try: - datetime.strptime(raw, TIMESTAMP_FORMAT) - except ValueError: - print(f"Error: timestamp must match format '{TIMESTAMP_FORMAT}'", file=sys.stderr) - sys.exit(1) - return raw - - def cmd_generate(args: argparse.Namespace) -> None: mnemo = Mnemonic("english") entropy = os.urandom(ENTROPY_BITS // 8) words = mnemo.to_mnemonic(entropy) - timestamp = datetime.now(timezone.utc).strftime(TIMESTAMP_FORMAT) - - seed = derive_seed(entropy, timestamp) - private_key = Ed25519PrivateKey.from_private_bytes(seed) + private_key = Ed25519PrivateKey.from_private_bytes(entropy) passphrase = ask_passphrase(confirm=True) if args.passphrase else None @@ -144,10 +117,9 @@ def cmd_generate(args: argparse.Namespace) -> None: if passphrase: print(" Passphrase : set") - print(f"\nWrite ALL of the following down — you need both to recover your key:\n") - print(f" Timestamp : {timestamp}\n") + print(f"\nYour 24-word recovery mnemonic — write these down and store offline:\n") print(format_mnemonic(words)) - print("\nKeep these secret. Both the words and the timestamp are required to recover.") + print("\nThese words reconstruct your exact private key. Keep them secret.") def cmd_recover(args: argparse.Namespace) -> None: @@ -165,11 +137,8 @@ def cmd_recover(args: argparse.Namespace) -> None: print("Error: invalid mnemonic — check spelling or word count.", file=sys.stderr) sys.exit(1) - timestamp = ask_timestamp() - entropy = bytes(mnemo.to_entropy(words)) - seed = derive_seed(entropy, timestamp) - private_key = Ed25519PrivateKey.from_private_bytes(seed) + private_key = Ed25519PrivateKey.from_private_bytes(entropy) passphrase = ask_passphrase(confirm=True) if args.passphrase else None @@ -210,7 +179,7 @@ def build_parser() -> argparse.ArgumentParser: "generate", aliases=["gen"], parents=[shared], - help="generate a new SSH key pair and print its 24-word mnemonic + timestamp", + help="generate a new SSH key pair and print its 24-word mnemonic", ) sub.add_parser( "recover",