Skip to content

Instantly share code, notes, and snippets.

@namazso
Last active March 15, 2025 04:36
Show Gist options
  • Save namazso/29edc56a0095ea2b9e9d0e27e4d52d7d to your computer and use it in GitHub Desktop.
Save namazso/29edc56a0095ea2b9e9d0e27e4d52d7d to your computer and use it in GitHub Desktop.
Export Tofu TOTP tokens

1. Exporting the secrets

Make an iTunes encrypted backup which contains the Keychain, where Tofu stores all the tokens.

2. Decrypting Keychain

You'll need irestore for decrypting the keychain. Use it like this:

irestore iPhone dumpkeys dump.txt

This will ask for your encryption password, then get you a dump.txt containing some json.

3. Filter and convert the secrets to readable format

Run this "oneliner":

cat dump.txt \
  | jq -r '.General[] | select(.agrp == "QG8TM5XJ84.com.calleerlandsson.Tofu") | .v_Data' \
  | while IFS= read -r line; do \
    echo "$line" \
    | base64 --decode \
    | dd bs=1 skip=1 status=none \
    | plistutil -i - -f xml \
    | xmllint --noout --xpath '/plist/dict' - ; done \
  | awk 'BEGIN{print "<totps>"} {print} END{print "</totps>"}' >totps.xml

This produces an XML of all your saved TOTP secrets.

4. Convert to URI

I didn't bother implementing a proper parser, but here's a PowerShell script that can convert the default type TOTP:

Function Invoke-Base32Encode {
    param(
        [Parameter(Mandatory = $true)][byte[]] $ByteArray
    )

    $byteArrayAsBinaryString = -join $ByteArray.ForEach{
        [Convert]::ToString($_, 2).PadLeft(8, '0')
    }

    # Pad the binary string to a multiple of 5
    $remainder = $byteArrayAsBinaryString.Length % 5
    if ($remainder -ne 0) {
        $padding = 5 - $remainder
        $byteArrayAsBinaryString += ('0' * $padding)
    }
    
    $Base32String = [regex]::Replace($byteArrayAsBinaryString, '.{5}', {
        param($Match)
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'[[Convert]::ToInt32($Match.Value, 2)]
    })

    Return $Base32String
}

([xml](Get-Content totp.xml)).totps.dict | ForEach-Object {
    $a = $_.array;
    $label = $a.string[1];
    $issuer = $a.string[2];
    $secretb64 = $a.data.Trim();
    $secret = [System.Convert]::FromBase64String($secretb64);
    $secretb32 = Invoke-Base32Encode $secret;
    "otpauth://totp/${issuer}:${label}?secret=$secretb32&issuer=$issuer";
}

5. Display them as QR code for scanning

cat totp.txt | xargs -I{} qrencode -t UTF8 "{}"
@eutampieri
Copy link

s/powershell/python/:

import xmltodict
import base64

o = xmltodict.parse(open("totps.xml").read())

for totp in o["totps"]["dict"]:
    array = totp['array']
    label = array['string'][1]
    issuer = array['string'][2]  
    try:
        secret_b64 = array['data']
    except:
        secret_b64 = array['dict'][0]['data']
    secret = base64.b64decode(secret_b64)
    secret_b32 = base64.b32encode(secret).decode('utf-8')
    print(f"otpauth://totp/{issuer}:{label}?secret={secret_b32}&issuer={issuer}")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment