Exporting secrets from FreeOTP to Pass

2018/09/21

I have broken my phone screen. Not a big deal but I use it for 2FA. While replacement is on the way I will extract credentials from FreeOTP and import them in password store. Be aware that it is completeley against the idea of 2FA, because now both password and otp are on the same device.

So, first I need to find where FreeOTP stores OTP secrets. Android apps usually use sqlite db for that, but not in this case. FreeOTP uses shared_prefs.

Pull prefs from the phone (root needed).

adb root
adb pull /data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml

Here is what they look like.

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="Amazon Web Services:sarg@organization">{&quot;algo&quot;:&quot;SHA1&quot;,&quot;counter&quot;:0,&quot;digits&quot;:6,&quot;issuerExt&quot;:&quot;Amazon Web Services&quot;,&quot;issuerInt&quot;:&quot;Amazon Web Services&quot;,&quot;label&quot;:&quot;sarg@organization&quot;,&quot;period&quot;:30,&quot;secret&quot;:[52,19,105,64,67,-120,-45],&quot;type&quot;:&quot;TOTP&quot;}</string>
    <string name="tokenOrder">[&quot;Amazon Web Services:sarg@organization&quot;,&quot;Google:sarg@sarg.org.ru&quot;,&quot;GitHub:sarg&quot;]</string>
</map>

Storing JSON in XML - that’s a beaty. If you decode the value, it looks better:

{
    "type": "TOTP",
    "secret": [52, 19, 105, 64, 67, -120, -45],
    "period": 30,
    "label": "sarg@organization",
    "issuerInt": "Amazon Web Services",
    "issuerExt": "Amazon Web Services",
    "digits": 6,
    "counter": 0,
    "algo": "SHA1"
}

Now I need to:

URI format is described in google-authenticator wiki.

Provision a TOTP key for user alice@google.com, to use with a service provided
by Example, Inc:

otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example

For one-time scripts I prefer using python. There is a handy library for dealing with html/xml - BeautifulSoup. And I will consult with FreeOTP sources to get things right.

#!/usr/bin/env python3

import urllib.parse
import base64
import json
from bs4 import BeautifulSoup

inp = open('tokens.xml')
soup = BeautifulSoup(inp, 'lxml')

for tag in soup.find_all('string'):
    # it's a special setting, skip it
    if tag['name'] == 'tokenOrder':
        continue

    token = json.loads(tag.text)

    # secret is stored as signed byte array
    # so first make it unsigned by adding 256 modulo 256
    # and then convert to python's bytearray
    secret = bytearray(map(lambda x: (x+256)%256, token['secret']))

    issuer = token.get('issuerExt', '')
    print('otpauth://{type}/{issuer}?{args}'.format(
        type=token['type'].lower(),

        # issuer should be URL encoded
        issuer=urllib.parse.quote(
            ':'.join(filter(None, (issuer, token['label'])))),

        args=urllib.parse.urlencode({
            'secret': base64.b32encode(secret).decode('utf-8'),
            'algo  ': token['algo'],
            'digits': token['digits'],
            'period': token['period']
        })
    ))

inp.close()

Now what’s left is just to import everything into pass with pass otp insert -e. Or you can manually put otpauth:// string in an appropriate pass file. I did the latter way, because my already existing entries have had another names. Then check that everything went fine - pass otp 'Amazon Web Services/sarg@organization'