# Backing up and recovering 2FA tokens from FreeOTP ## Backing up FreeOTP Using [adb](https://developer.android.com/studio/command-line/adb.html), [create a backup](https://androidquest.wordpress.com/2014/09/18/backup-applications-on-android-phone-with-adb/) of the app using the following command: ```sh adb backup -f freeotp-backup.ab -apk org.fedorahosted.freeotp ``` [org.fedorahosted.freeotp](https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp) is the app ID for FreeOTP. This will ask, on the phone, for a password to encrypt the backup. Proceed with a password. ## Manually extracting the backup The backups are some form of encrypted tar file. [Android Backup Extractor](https://github.com/nelenkov/android-backup-extractor) can decrypt them. It's available on the AUR as [android-backup-extractor-git](https://aur.archlinux.org/packages/android-backup-extractor-git/). Use it like so (this command will ask you for the password you just set to decrypt it): ```sh abe unpack freeotp-backup.ab freeotp-backup.tar ``` Then extract the generated tar file: ```shell $ tar xvf freeotp-backup.tar apps/org.fedorahosted.freeotp/_manifest apps/org.fedorahosted.freeotp/sp/tokens.xml ``` We don't care about the manifest file, so let's look at `apps/org.fedorahosted.freeotp/sp/tokens.xml`. ### Reading tokens.xml The `tokens.xml` file is the preference file of FreeOTP. Each `...` is a token (except the one with the name `tokenOrder`). The token is a JSON blob. Let's take a look at an example token (which is no longer valid!): ```xml {"algo":"SHA1","counter":0,"digits":6,"imageAlt":"content://com.google.android.apps.photos.contentprovider/-1/1/content%3A%2F%2Fmedia%2Fexternal%2Ffile%2F3195/ORIGINAL/NONE/741876674","issuerExt":"Discord","issuerInt":"Discord","label":"me@example.org","period":30,"secret":[122,-15,11,51,-100,-109,21,89,-30,-35],"type":"TOTP"} ``` Let's open a python shell and get the inner text of the XML into a Python 3 shell. We'll need `base64`, `json` and `html` in a moment: ```py >>> import base64, json, html >>> s = """{"algo":"SHA1","counter":0,"digits":6,"imageAlt":"content://com.google.android.apps.photos.contentprovider/-1/1/content%3A%2F%2Fmedia%2Fexternal%2Ffile%2F3195/ORIGINAL/NONE/741876674","issuerExt":"Discord","issuerInt":"Discord","label":"me@example.org","period":30,"secret":[122,-15,11,51,-100,-109,21,89,-30,-35],"type":"TOTP"}""" ``` We decode all those HTML entities from the XML encoding: ```py >>> s = html.unescape(s); print(s) {"algo":"SHA1","counter":0,"digits":6,"imageAlt":"content://com.google.android.apps.photos.contentprovider/-1/1/content%3A%2F%2Fmedia%2Fexternal%2Ffile%2F3195/ORIGINAL/NONE/741876674","issuerExt":"Discord","issuerInt":"Discord","label":"me@example.org","period":30,"secret":[122,-15,11,51,-100,-109,21,89,-30,-35],"type":"TOTP"} ``` What we specifically need from this is the secret. It's a signed byte array from Java... Let's grab it: ```py >>> token = json.loads(s); print(token["secret"]) [122, -15, 11, 51, -100, -109, 21, 89, -30, -35] ``` Now we have to turn this into a Python bytestring. For that, these bytes need to be turned back into unsigned bytes. Let's go: ```py >>> secret = bytes((x + 256) & 255 for x in token["secret"]); print(secret) b'z\xf1\x0b3\x9c\x93\x15Y\xe2\xdd' ``` Finally, the TOTP standard uses base32 strings for TOTP secrets, so we'll need to turn those bytes into a base32 string: ```py >>> code = base64.b32encode(secret); print(code.decode()) PLYQWM44SMKVTYW5 ``` There we go. `PLYQWM44SMKVTYW5` is our secret in a format we can manually input into FreeOTP or Keepass. ## In a nutshell Install abe (choose your preferred aur installer): yaourt -S android-backup-extractor-git Get your token: ``` adb backup -f freeotp-backup.ab -apk org.fedorahosted.freeotp abe unpack freeotp-backup.ab freeotp-backup.tar tar xvf freeotp-backup.tar ./get-token.py secret name: token name token secret base64: PLYQWM44SMKVTYW5 ``` There's a verbose flag on python script if you want all the details