Metadata-less Encrypted Chat Using Axolotl+Tor

Metadata-less axotor

axotor is a command-line chat utility that uses the Axolotl (aka double ratchet) protocol for forward-secret, future-secret, deniable communication. All communication is carried out over the tor network, so there is absolutely no metadata to be surveilled either! Further, all hidden service parameters (keys, onion addresses, etc.) as well as the axolotl key database are stored only in ram. None of this information is ever written to disk! When you quit the chat client and shut down your computer, all of that information disappears forever. This post is an attempt to illustrate axotor's use.

Key exchange between server and client instances is extremely simple. Both both users (we'll call them bob and doug) need to securely agree on a master key via some out-of-band method. The key needs to be of the form NNN-xxxx where NNN is an integer, and xxxx is a complex alphanumeric string. For example, 93-xyz1zy would serve as a key (don't use this one). The integer and the string are separated by a hyphen.

axotor uses this master key to: 1) exchange various credentials such as the address of the server's onion, the onion's authentication cookie, and a ratchet key, and 2) to create the original Axolotl key databases. Credential exchange takes place using password-authenticated key exchange (PAKE). The python magic-wormhole module supplies PAKE functionality. The exchange of credentials takes place over the tor network as well.

There is a nice video (15MB in size) of axotor usage.

Installation is straightforward (although if you want to skip the installation steps and just try it out, go here). Axotor requires the pyaxo, pysocks, txtorcon, stem, and magic-wormhole python modules to be installed. These are available on pypi and can be installed with the command:

 sudo pip install pyaxo pysocks txtorcon stem magic-wormhole can be found in the /usr/share/pyaxo/examples subdirectory. To link axotor and its supporting modules in the /usr/local/bin directory, execute the following commands:

 sudo ln -s /usr/share/pyaxo/examples/ /usr/local/bin/axotor
 sudo ln -s /usr/share/pyaxo/examples/ /usr/local/bin/
 sudo ln -s /usr/share/pyaxo/examples/ /usr/local/bin/

After you have installed axotor, usage proceeds as follows (click the thumbnails on the terminal images for a larger version of each image):

The first step is for both users to start axotor. One user starts as the server using axotor -s (left-side images) and the second user starts as the client axotor -c (right-side images).

Each user selects and enters a nickname.

alt text alt text

Each user then enters the nickname for their communication partner.

alt text alt text

Finally, each user enters the previously agreed-upon master key.

alt text alt text

The server then proceeds to create a tor hidden service. axotor will wait until the hidden service descriptor is published before returning (this usually takes about 30 seconds or so).

alt text alt text

axotor will then ask if either user wants to go through a socialist millionaire's protocol authentication process. In the example below, bob chose not to, but doug wanted to. As a result, axotor will require SMP authentication. If neither user wanted to go through the SMP authentication step, axotor will skip it and go directly to the chat window. Obviously, if both users request SMP, axotor will execute the protocol as well

alt text alt text

axotor then asks for the authentication secret. This should be an additional secret agreed to out-of-band by the two users.

alt text alt text

axotor notifies each user of the success or failure of the SMP step.

alt text alt text

Finally, the chat window opens. Green text signifies that either the SMP step was successful, or that the users chose to skip that step. If the the SMP step is not successful, the users will be prompted to exit. If they choose not to, the text will appear red to remind them that the SMP step was unsuccessful.

alt text alt text

At this point, axotor acts like every other chat client. The users can exchange messages as they wish. Each message is encrypted with a different key, with what is widely recognized as the most secure messaging protocol available (Axolotl).

alt text alt text

Finally, when the chat is over, either user can type .quit to exit the chat. In the case shown below, doug chose to quit.

alt text alt text

axotor then notifies bob that doug has quit the chat and shuts down the chat window.

alt text alt text

End Notes

The axotor server and client instances use different ports for their tor servers, so it is very easy to try axotor out. You can run the server and client side by side in two different terminal windows on the same machine.

If you want to try axotor out without going through all the installation steps, there is a docker image I created to do that. Execute the script from the unmessage-client repository and this will download and run the docker image. At the docker container's bash prompt, proceed with the commands axotor -s or axotor -c as appropriate.

We also want to emphasize that the code has not been audited. Please do not use axotor for any activity that your life depends upon!


The file-transfer branch of the pyaxo github repository has the capability for encrypted file transfer as well.

Edit: encrypted file transfer is now enabled in master branch.

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:

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

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 <>.

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__':

Axotor - A metadata-less Axolotl standalone chat client

A standalone, forward-secret, metadataless chat application is a useful thing in today's world. I've created one using the pyaxo Axolotl library. The axolotl protocol provides the forward secrecy, and tor eliminates the metadata.

The client is based on a client-server model, where one party to the chat establishes a server, and the other party connects to the server as a client. The interesting thing about this application is that the connection is made through tor. The server creates a hidden service, and the client connects to that hidden service, so all communication is hidden (even from the tor exit nodes).

The client is terminal-based, and uses ncurses. can be found in the examples subdirectory of the pyaxo github repo. pyaxo is available in the python package manager and can be installed via pip. When pyaxo is installed via pip, is located in the /usr/share/pyaxo/examples directory.

Here are a couple of images of the startup process:

Here are the resulting chat windows:

axochat - a standalone ncurses instant messaging script using AES256 encryption with Axolotl ratchet for key management.

Axochat is a python ncurses-based standalone instant messaging chat application that uses AES256 encryption (GPG) with the Axolotl ratchet for key management. The Axolotl ratchet provides for excellent forward and future secrecy, as well as authentication and deniability.

The Axolotl ratchet was developed by Trevor Perrin and Moxie Marlinspike.

The script runs using a client-server model. One user starts the server, giving it his/her Axolotl credentials and the second user starts a client, giving it his/her Axolotl credentials. The two connect on a user-chosen tcp port and encrypted instant messages can then be exchanged. By virtue of the Axolotl ratchet, each message is encrypted with a different ephemeral key. That key is deleted upon decryption of the message so it can never be decrypted again.

The script is curses-based, and so should run in any modern terminal emulator. Prior to exchanging encrypted messages, each user needs to generate their own Axolotl credentials and enter the other user's credentials as well. Authentication is provided by the out-of-band exchange of key fingerprints between the users. The script also provides the capability to generate the required credentials.

If you prefer to run axochat in client-only mode, here is a simple echo server for use with axochat. The script will accept connections from axochat clients, and echo any messages to all other connected clients. Of course, only the addressee will be able to decrypt the messages.

#!/usr/bin/env python

A simple echo server for axochat

import socket
import threading
import sys

HOST = ''
PORT = 50000
SIZE = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
client_list = []
address_list = []

def receiveData(client, address):
    global client_list
    global address_list
    while True:
        data = client.recv(SIZE)
        if not data:
            print str(address) + ' disconnected'
            for item in client_list:
                if item != client:

while True:
    client, address = s.accept()
    client_list = client_list + [client]
    address_list = address_list + [address]
    print 'added new client', address

If you want to connect the client to a server via tor (either axochat or echo server), you can use socat. The following socat command:

 socat TCP-LISTEN:12345,fork SOCKS4A:localhost:<server.ip.addr>:50000,socksport=9050 &

nd connect your axochat client to localhost port 12345. This will proxy the connection through tor to port 50000 on the server node.

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.