How Kode stores your codes
Where Kode keeps your 2FA keys, how they sync, and how to get them out again.
Only in your Keychain
Every account – including the secret key the codes are made from – is stored as one item in the iPhone's Keychain, available after first unlock. The key is never stored in the app's database, in settings, in log files, in analytics or in crash reports.
iCloud Keychain
With sync on (the default) the item is marked synchronizable, so iCloud Keychain carries it to your other Apple devices and to a new iPhone. iCloud Keychain is end-to-end encrypted. Turn sync off and the keys move to this iPhone only (“this device only”) and don't move to another phone.
No server
Kode makes no network calls. Codes are calculated with the TOTP standard (RFC 6238) using HMAC-SHA1/256/512 – the same codes as Google Authenticator.
Lock
Face ID or passcode when the app opens, and again after a minute in the background. While the app isn't active a lock screen covers everything, so the app switcher never shows a code. A copied code expires from the clipboard after 60 seconds and isn't shared through Universal Clipboard.
The backup file format
The encrypted backup is a JSON file. The key is derived from your password with PBKDF2-HMAC-SHA256 (600,000 rounds, 16-byte salt), and the contents are encrypted with AES-256-GCM. ciphertext is the encrypted text followed by the 16-byte GCM tag. All binary fields are Base64.
{
"format": "kode-backup",
"version": 1,
"kdf": { "name": "PBKDF2-HMAC-SHA256", "iterations": 600000, "salt": "…" },
"cipher": { "name": "AES-256-GCM", "nonce": "…" },
"ciphertext": "…"
}
Decrypted, the contents are a list of accounts. Each account also has a ready-made otpauth:// address that most apps can import.
{
"created": "2026-09-24T09:41:00Z",
"accounts": [
{
"issuer": "GitHub", "name": "anna", "secret": "JBSWY3DPEHPK3PXP",
"algorithm": "SHA1", "digits": 6, "period": 30,
"uri": "otpauth://totp/GitHub:anna?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&algorithm=SHA1&digits=6&period=30"
}
]
}
To decrypt the file without Kode (Python with the cryptography package):
import base64, getpass, hashlib, json, sys
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
backup = json.load(open(sys.argv[1]))
b64 = base64.b64decode
key = hashlib.pbkdf2_hmac("sha256", getpass.getpass().encode(),
b64(backup["kdf"]["salt"]), backup["kdf"]["iterations"], 32)
plain = AESGCM(key).decrypt(b64(backup["cipher"]["nonce"]), b64(backup["ciphertext"]), None)
for account in json.loads(plain)["accounts"]:
print(account["uri"])