Extracting OTR private keys from the Conversations Android app

I think Conversations is a great xmpp client for Android. But sometimes I want to use a desktop client, and want to use the same OTR private key for the desktop that I do in Conversations. So I wrote the following script to export private OTR keys. The extracted key is given in both raw hex and in (base64'd) libotr format, so it can be imported into virtually any xmpp client that supports OTR.

With a little cleverness, the script could be adapted to modify or add OTR keys to the Conversations database. That is left as an exercise for the reader.

There are versions of the script for both rooted and nonrooted phones. See the github repo.

The script is available at: https://github.com/rxcomm/convkey

#!/usr/bin/env python
"""Copyright 2016 by David R. Andersen <k0rx@RXcomm.net>.

This script is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 3 of the License, or
any later version.

This script is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this library.  If not, see <http://www.gnu.org/licenses/>.

This script will read a Conversations database from an Android phone
and output the OTR private keys for each account in the database.


1. You must have adb installed on your system (apt-get adb in
   Debian/Ubuntu). The script uses adb to get the database.

2. Plug your phone in and run this script.

import os
import sqlite3
import json
import binascii
#import pprint
from otrapps.util import fingerprint
from potr.compatcrypto.pycrypto import DSAKey
from subprocess import call, PIPE

def get_database():
    """Pull the Conversations database from an Android phone using adb"""

    # FIXME: do some error checking here...
    call(['adb', 'kill-server'], stdout=PIPE, stderr=PIPE)
    call(['adb', 'root'], stdout=PIPE, stderr=PIPE)
    call(['adb', 'pull',
          'history'], stdout=PIPE, stderr=PIPE)
    call(['adb', 'kill-server'], stdout=PIPE, stderr=PIPE)

def parse_database():
    """Parse the Conversations database for all private OTR keys"""

    keydict = {}
    database = sqlite3.connect('history')
    cur = database.cursor()
    cur.execute('select * from accounts')
    account_data = cur.fetchall()
    for account in account_data:
        userid = account[1] + '@' + account[2]
        print 80*'-'
        print 'Your XMPP ID is: ' + userid + '\n'
        k = json.loads(account[7])
        print 'Your OTR private key parts are:'
        print 'Y: %s' % k['otr_y'].upper()
        print 'G: %s' % k['otr_g'].upper()
        print 'P: %s' % k['otr_p'].upper()
        print 'Q: %s' % k['otr_q'].upper()
        print 'X: %s' % k['otr_x'].upper()
        k['otr_y'] = int(k['otr_y'], 16)
        k['otr_g'] = int(k['otr_g'], 16)
        k['otr_p'] = int(k['otr_p'], 16)
        k['otr_q'] = int(k['otr_q'], 16)
        k['otr_x'] = int(k['otr_x'], 16)

        newkey = DSAKey((k['otr_y'],
        fprint = fingerprint((k['otr_y'], k['otr_g'], k['otr_p'], k['otr_q']))
        print 'Fingerprint: ' + fprint.upper() + '\n'
        print 'Your base64-encoded OTR private key is [in libotr format]:\n%s' \
               % binascii.b2a_base64(newkey.serializePrivateKey())

        # keydict is compatible with keysync from The Guardian Project
        keydict[userid] = {'fingerprint': fprint,
                           'name': userid,
                           'protocol': 'XMPP',
                           'resource': 'Conversations',
                           'verification': None, # I'm not sure what this means
                           'p': k['otr_p'],
                           'q': k['otr_q'],
                           'g': k['otr_g'],
                           'x': k['otr_x'],
                           'y': k['otr_y'],

if __name__ == '__main__':
All code, text and images are Copyright © 2013-2017 by David R. Andersen. All rights reserved unless otherwise specified.
This site is produced using the Haggis static site generator.