After finding out the vendor lock in of this tool, I wanted to get rid of it to move my TOTP generation to Bitwarden. But for that I would either need to setup TOTP again for 20 separate services, or somehow extract the seed from the current generator.

The backup from this tool is a .encrypt file, which encrypts with the password totpauthenticator by default, You can set it under Settings->Encryption Key without knowing the previous password, if you’d like to change it.

Then it’s off to decrypting it! I won’t explain the .apk decompilation and Java scouting done to find this method, but I will give you the tools to decrypt it yourself.

The process is quite simple, and explained in the code snippet below. To run it, you need to pip install pycryptodome.

import hashlib
import base64
import json
from getpass import getpass

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# Place the file as `backup.encrypt` next to this script.
# It's a base64 encoded binary.
with open("backup.encrypt", "rb") as f:
    base64_binary = f.read()
    binary = base64.b64decode(base64_binary)

password = getpass()

# We need a SHA256 hash of the password.
password_hash = hashlib.sha256(password.encode('utf-8')).digest()

# The IV is set in an imported library, which didn't bother with it.
iv = b'\x00'*16

# Now we can decrypt the AES/CBC/PKCS7 encrypted binary.
def decrypt_with_AES(cipher_text, secret_key):
    cipher = AES.new(password_hash, AES.MODE_CBC, iv)
    plain_bytes = unpad(cipher.decrypt(cipher_text), AES.block_size)
    return plain_bytes.decode()

decrypted = decrypt_with_AES(binary, password_hash)

# The content is a key value pair, of which the key contains a
# list of key-value blocks with our data.
entries = json.loads(list(json.loads(decrypted))[0])

# Finally, write the resulting json to a file.
with open("backup.json", "w") as f:
    json.dump(entries, f)

# We find out the seeds have been encoded, so we will decode
# these as well. Quite simple luckily: hex encoded byte arrays from
# which we retrieve the base32 seed.
readable = ""

for entry in entries:
    encoded = entry["key"]
    decoded = bytes.fromhex(encoded)
    recoded = base64.b32encode(decoded).decode()
    readable += f"{entry['issuer']} / {entry['name']}: {recoded}\n"

# Don't use any padding characters (=) when setting the seed
with open("backup.txt", "w") as f:
    f.write(readable)

Using it

For Windows users. If you’re a Linux user, you don’t need this walkthrough 😚

  1. Download and install Python. You can get it from the Microsoft Store
  2. In TOTP Authenticator:
    1. Set a (simple) password as decribed above, and remember it
    2. Create a backup file
    3. Transfer this file to your computer into some folder
    4. Ensure it is named backup.encrypt
  3. Create a new Text Document in that same folder, and copy the code above into it
  4. Rename that file to decrypt.py, making sure you also replace the .txt extension
  5. Open a terminal in this folder. Several ways to do it, but a simple one is:
    • When in the Windows File Explorer with your new file visible, press F4 to select the address bar, type cmd, and press Enter
  6. In that terminal, type and press Enter for each of these steps:
    1. pip install pycryptodome
    2. python3 decrypt.py
    3. Enter the password you set before
  7. That should have a backup.txt appear with your TOTP seed values

With many thanks to my good friend Eddy.