Skip to content

Instantly share code, notes, and snippets.

@naufraghi
Last active July 10, 2020 20:11
Show Gist options
  • Save naufraghi/ba8c35bf4fc9b69c2a61 to your computer and use it in GitHub Desktop.
Save naufraghi/ba8c35bf4fc9b69c2a61 to your computer and use it in GitHub Desktop.
Script to backup Google Authenticator secrets as QR-codes
#!/usr/bin/env python3
# Copyright 2014 Matteo Bertini matteo@naufraghi.net
# Released as Public Domain
#
# Latest version:
# wget http://slug.it/backup-google-authenticator.py
#
# Requirements
# ============
#
# A rooted phone and a google authenticator full of secrets,
# connected with an USB cable to a computer with:
# - python3
# - adb (Android SDK)
# - qrencode
# - ImageMagick (+ ghostview for the labels if using brew)
#
# Usage
# =====
#
# Connect the phone, run, save or print or acquire the codes.
import os
import sys
import shutil
import sqlite3
import subprocess
import tempfile
import urllib.parse
import uu
import hashlib
import time
def get_databases(dest):
with open(dest, "w+b") as db:
cmd = "adb shell \"su -c 'uuencode /data/data/com.google.android.apps.authenticator2/databases/databases -'\""
dump = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
uu.decode(dump.stdout, db)
return dest
def get_accounts(db):
conn = sqlite3.connect(db)
cursor = conn.cursor()
for email, secret in cursor.execute("SELECT email, secret FROM accounts"):
yield (email, secret)
def create_qrcode(msg, filename):
subprocess.check_call(["qrencode", "-s", "4", "-o", filename, msg])
return filename
def set_image_comment(image, comment):
subprocess.check_call(["mogrify", "-comment", comment, image])
return image
def create_contact_sheet(filename):
subprocess.check_call(["montage", "-geometry", "150x150+50+50", "-tile", "1x", "-label", r"%c", "*.png", filename])
return filename
def backup_google_authenticator_secrets(dest_dir, dest_filename):
orig_dir = os.getcwd()
temp_dir = tempfile.mkdtemp()
os.chdir(temp_dir)
for account, secret in get_accounts(get_databases("databases.sqlite")):
account_ = urllib.parse.quote_plus(account)
qrsecret = "otpauth://totp/{account_}?secret={secret}".format(**locals())
filename = hashlib.md5(account.encode('utf-8')).hexdigest()+".png"
create_qrcode(qrsecret, filename)
set_image_comment(filename, account)
shutil.copyfile(os.path.join(temp_dir, create_contact_sheet(dest_filename)),
os.path.join(dest_dir, dest_filename))
shutil.rmtree(temp_dir)
def checked_dir(adir):
if os.path.isdir(adir):
return adir
else:
raise argparse.ArgumentTypeError("Invalid dir %r" % adir)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser("Backup Google Authenticator secrets to QR-codes")
parser.add_argument("dest", metavar="DIR", nargs="?", type=checked_dir,
help="Save the final image 'auth-codes.png' in %(default)s", default='.')
args = parser.parse_args()
backup_google_authenticator_secrets(args.dest, "auth-codes.png")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment