-
-
Save smowtion/547f195ab66c6223979420bae1748ad4 to your computer and use it in GitHub Desktop.
How to Export Your Authy Tokens
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import urllib.parse | |
# Load source content from file | |
with open("decrypted_tokens.json", "r") as f: | |
authy_data = json.load(f) | |
# Convert to Bitwarden format | |
bitwarden_data = {"items": []} | |
for account in authy_data["decrypted_authenticator_tokens"]: | |
# Ensure all fields are strings or provide defaults | |
name = str(account.get("name", "")) | |
issuer = account.get("issuer") # May be None | |
secret = str(account.get("decrypted_seed", "")) | |
digits = str(account.get("digits", "6")) | |
# Construct TOTP URI | |
totp_uri = f"otpauth://totp/{urllib.parse.quote(issuer or '')}:{urllib.parse.quote(name)}?" | |
totp_uri += f"secret={urllib.parse.quote(secret)}&digits={digits}" | |
if issuer: # Only add issuer parameter if it exists | |
totp_uri += f"&issuer={urllib.parse.quote(issuer)}" | |
# Add to Bitwarden items | |
bitwarden_data["items"].append({ | |
"name": name, | |
"type": 1, # Type 1 is for login items | |
"login": { | |
"username": name, | |
"totp": totp_uri | |
} | |
}) | |
# Save to JSON file | |
with open("vaultwarden_import.json", "w") as f: | |
json.dump(bitwarden_data, f, indent=4) | |
print("Vaultwarden import file created: vaultwarden_import.json") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, 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, zero IV | |
iv = bytes([0] * 16) # Zero IV (16 bytes for AES block size) | |
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") | |
# Extract the decrypted seed, base16 decode, and interpret as UTF-8 string | |
decrypted_seed_hex = decrypted_data[:padding_start].hex() | |
return binascii.unhexlify(decrypted_seed_hex).decode('utf-8') # Decode base16 and interpret as 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'], | |
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment