Using adb, create a backup of the app using the following command:
adb backup -f freeotp-backup.ab -apk org.fedorahosted.freeotp
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.
The backups are some form of encrypted tar file. Android Backup Extractor can decrypt them. It's available on the AUR as android-backup-extractor-git.
Use it like so (this command will ask you for the password you just set to decrypt it):
abe unpack freeotp-backup.ab freeotp-backup.tar
Then extract the generated tar file:
$ 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
.
The tokens.xml
file is the preference file of FreeOTP. Each <string>...</string>
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 version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<!-- ... -->
<string name="Discord:[email protected]">{"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":"[email protected]","period":30,"secret":[122,-15,11,51,-100,-109,21,89,-30,-35],"type":"TOTP"}</string>
</map>
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:
>>> 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":"[email protected]","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:
>>> 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":"[email protected]","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:
>>> 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:
>>> 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:
>>> 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.
Which adb version does not bother with the android:allowBackup="false" setting in the AndroidManifest.xml?
allowBackup was set to false since 2016 but apparently people were able to backup their FreeOTP tokens, so there must be something going on on newer adb verions? (Android 11 btw)
neither way from above did yield any backup, usually the file is 47 bytes long and contains ANDROID BACKUP.. none .. as ascii.
using a password is much more bloated up but the extracted data is just 0s.
and a 'full' backup does not include the freeotp data.
update
So I remembered it correctly that I was once trying out the adb backup method. Found a older backup and by timestamp also the matching adb platform tools in the downloads folder of the laptop.
platform-tools_r31.0.3-windows.zip
was the one that worked back then, and... to my surprise... it also worked now.
tried these versions:
platform-tools_r33.0.2-windows.zip (latest as for now)
platform-tools_r31.0.0-windows.zip
platform-tools_r29.0.0-windows.zip
platform-tools_r28.0.0-windows.zip
platform-tools_r27.0.0-windows.zip
platform-tools_r26.0.0-windows.zip
without any luck. did use the platform tools from August 2021 (v31.0.3), same command, but a correct backup file. :)
With the notes by sfan5 the generated json is imported without any issues into FreeOTP+
I cannot tell why those other versions didn't work, but it might help someone who does struggle creating a backup on a non rooted device.
maybe v31.0.3 was bugged and didn't bother about the allowBackup=false manifest, but go figure, time to get rid of FreeOTP and move to FreeOTP+