- Make sure adb can see your phone: adb devices
- Get a backup of the FreeOTP data: adb backup -f freeotp.bak org.fedorahosted.freeotp
- Grab the FreeOTP recovery tarball: https://drive.google.com/file/d/0B20nsfvgGNDgSlp2am9sWkNlalE/view?usp=sharing
This tarball has a built version of android-backup-extractor, and a virtualenv with the python3 dependencies needed and a copy of freeotp-redisplay.py - Install the JCE Unlimited libraries
- Install Python3 if not already installed.
- Install qrencode (brew install qrencode on OSX)
- In the recovery tarball there's https://github.com/nelenkov/android-backup-extractor patched to remove the version check in AndroidBackup.java
- java -jar abe-all.jar unpack freeotp.bak freeotp.tar backup_password
- tar xvf freeotp.tar
- . venv/bin/activate
- python3 freeotp-redisplay.py apps/org.fedorahosted.freeotp/sp/tokens.xml
Image 1: 0123456789ABCDEF - SomeSite:SomeDetails
The Image # is the PNG of a QR code that can be used to move the token to another device.
The Hex code can be passed to oathtool to get a one time token (it assumes SHA1 as the digest algorithm):
- Install oathtool ( brew install oath-toolkit on OSX)
- Make sure the current time is correct.
- oathtool --totp=sha1 0123456789ABCDEF
------
Copy of freeotp-redisplay.py:
#!/usr/bin/env python
from __future__ import print_function
import base64
import ctypes
import json
import subprocess
import sys
import xml.etree.ElementTree as ET
from urllib.parse import urlencode
def main():
tree = ET.parse(sys.argv[1])
root = tree.getroot()
assert root.tag == 'map'
count = 0
for child in root:
assert child.tag == 'string'
name = child.attrib['name']
if name == 'tokenOrder':
continue
if ':' in name:
service, user = name.split(':', 1)
else:
user = name
service = 'Unknown'
info = json.loads(child.text)
#print("Info for %s: %r" % (name, info))
# Ensure that we only have unsigned values, then get secret
secret_bytes = [ctypes.c_ubyte(x).value for x in info['secret']]
secret_hex = ''.join('%02X' % (x,) for x in secret_bytes)
secret = bytes.fromhex(secret_hex)
secret_b32 = base64.b32encode(secret)
# Make fancy URL.
params = {
'secret': secret_b32,
'issuer': service,
'counter': info['counter'],
'digits': info['digits'],
'period': info['period'],
'algorithm': info['algo'],
}
tmpl = 'otpauth://{type}/{service}:{user}?{params}'
url = tmpl.format(
type=info['type'].lower(),
service=service,
user=user,
params=urlencode(params),
)
count=count+1
print("Image {0}: {2} - {1}".format(count, name, secret_hex))
process_qrencode = subprocess.Popen(['qrencode', '-o', "qr-{0}.png".format(count), url])
if __name__ == "__main__":
main()