import json import base64 import binascii # For base16 decoding from getpass import getpass # For hidden password input from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend def decrypt_token(kdf_rounds, encrypted_seed_b64, salt, iv, passphrase): try: # Decode the base64-encoded encrypted seed encrypted_seed = base64.b64decode(encrypted_seed_b64) # Derive the encryption key using PBKDF2 with SHA-1 kdf = PBKDF2HMAC( algorithm=hashes.SHA1(), length=32, # AES-256 requires a 32-byte key salt=salt.encode(), iterations=kdf_rounds, backend=default_backend() ) key = kdf.derive(passphrase.encode()) # AES with CBC mode # Some versions of Authy used an IV, while others used a null IV. We account for both cases here. if not iv: iv = bytes([0] * 16) else: iv = binascii.unhexlify(iv) cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) decryptor = cipher.decryptor() # Decrypt the ciphertext decrypted_data = decryptor.update(encrypted_seed) + decryptor.finalize() # Remove PKCS7 padding padding_len = decrypted_data[-1] padding_start = len(decrypted_data) - padding_len # Validate padding if padding_len > 16 or padding_start < 0: raise ValueError("Invalid padding length") if not all(pad == padding_len for pad in decrypted_data[padding_start:]): raise ValueError("Invalid padding bytes") return decrypted_data[:padding_start].decode('utf-8') except Exception as e: return f"Decryption failed: {str(e)}" def process_authenticator_data(input_file, output_file, backup_password): with open(input_file, "r") as json_file: data = json.load(json_file) decrypted_tokens = [] for token in data['authenticator_tokens']: decrypted_seed = decrypt_token( kdf_rounds=token['key_derivation_iterations'], encrypted_seed_b64=token['encrypted_seed'], salt=token['salt'], iv=token['unique_iv'], passphrase=backup_password ) decrypted_token = { "account_type": token["account_type"], "name": token["name"], "issuer": token["issuer"], "decrypted_seed": decrypted_seed, # Store as UTF-8 string "digits": token["digits"], "logo": token["logo"], "unique_id": token["unique_id"] } decrypted_tokens.append(decrypted_token) output_data = { "message": "success", "decrypted_authenticator_tokens": decrypted_tokens, "success": True } with open(output_file, "w") as output_json_file: json.dump(output_data, output_json_file, indent=4) print(f"Decryption completed. Decrypted data saved to '{output_file}'.") # User configuration input_file = "authenticator_tokens.json" # Replace with your input file output_file = "decrypted_tokens.json" # Replace with your desired output file # Prompt for the backup password at runtime (hidden input) backup_password = getpass("Enter the backup password: ").strip() # Process the file process_authenticator_data(input_file, output_file, backup_password)