Authy 迁移到 2FAS
date
Jun 17, 2025
outer_link
slug
migrate-authy-to-2fas
status
Published
tags
product-dev
tech
summary
type
Post
用 Authy 大概有两三年了,用它的原因不为别的:就是因为它当时提供了桌面版,并且可以使用 Raycast 插件。但它后续移除了桌面版的支持,并且也无法在 Mac 运行 iPad 版本,这导致我无法再使用 Raycast 插件与其进行联动。它糟糕的 UI 界面以及与手机账号绑定的方式让我似乎找不到理由再继续使用它。唯一的问题是:Authy 官方并没有提供迁移到别的 Two-factor Authenticator 的方式。但经过了一番搜索,终于找到了解决方法,做一下简单的记录。
第一步:获取 Authy 保存的 authenticator_tokens
这个操作比较简单,在 iOS 设备上使用代理抓包 App,重新安装 Authy 并登录。然后在抓包记录中搜索携带 “authenticator_tokens“ 这一个 url 的响应内容即可,响应的格式如下:
{ "message": "success", "authenticator_tokens": [ { "account_type": "authenticator", "digits": 6, "encrypted_seed": " ", "issuer": " ", "key_derivation_iterations": 100000, "logo": " ", "name": " ", "original_name": " ", "password_timestamp": " ", "salt": " ", "unique_id": " ", "unique_iv": " " } ], "deleted": [], "success": true }
第二步:解码
复制下面 Github 页面中的代码,将刚获取到的 json 进行解码。注意,需要输入 Authy 的 Backup Password。
# Author: AlexTech01@Github 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)
第三步:生成二维码
上一步运行结束之后,会得到一个
decrypted_tokens.json
文件,打开这个网址,然后加载文件,即可生成二维码,使用 2FAS App 进行扫描添加即可。题外话:我很不喜欢在用电脑的时候还需要切换到手机进行操作(虽然现在可以用 iPhone Mirroring,但体验不佳),我对安全的要求并不是很高,更倾向于便捷性,所以在 Mac 上,我依旧使用 Raycast + One Time Password 这个插件来实现类似于之前 Authy + Raycast 的操作。弊端是安全性降低,并且需要手动添加 OTP,但对于我来说并不是一个很大的问题。