Last active
September 9, 2015 13:42
-
-
Save jojonas/af8d29be87aa0f95f123 to your computer and use it in GitHub Desktop.
One-Time-Token generator (RFC 6238)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import time | |
import base64 | |
import hmac | |
import hashlib | |
# pads the string to a multiple of 8 bytes using "=" | |
def pad_base32(string): | |
return string + "=" * (8 - ((len(string)-1) % 8) - 1) | |
# calculates the authentification key for the specified secret and timestamp | |
# more info: https://en.wikipedia.org/wiki/Google_Authenticator | |
def calckey(secret, timestamp): | |
# pad secret to multiple of 8 characters | |
secret = pad_base32(secret) | |
# decode base32 to byte string (ignoring case) | |
key = base64.b32decode(secret, casefold=True) | |
# interpret Unix timestamp / 30sec as 8 byte unsigned long long | |
message = int(timestamp/30).to_bytes(8, byteorder="big", signed=False) | |
# combine message (time) and key (secret) into one authentification code using sha1 | |
code = hmac.new(key, message, hashlib.sha1).digest() | |
# extract offset (last 4 bits of HMAC) | |
offset = code[-1] & 0x0f | |
# extract 4 bytes starting at offset, interpret as 4 byte unsigned int | |
truncatedHash = int.from_bytes(code[offset:offset+4], byteorder="big", signed=False) | |
# unset the first bit | |
truncatedHash &= ~(1 << (8*4-1)) | |
# restrict code to 6 digits and convert to string | |
code = truncatedHash % 1000000 | |
codeStr = "%06d" % code | |
return codeStr | |
# to pull the database from Android, see https://nucleussystems.com/blog/android-backup-google-authenticator/ | |
# general steps: | |
# 1. have root access | |
# 2. use adb shell to pull /data/data/com.google.android.apps.authenticator2/databases | |
def from_database(dbfile, enable_plot=False): | |
import sqlite3 | |
now = int(time.time()) | |
remaining = 30 - (now % 30) | |
with sqlite3.connect(dbfile) as connection: | |
cursor = connection.cursor() | |
cursor.execute("SELECT email, secret FROM accounts") | |
# output code is a little ugly :/ | |
width = 50 | |
print("="*width) | |
print("Two-Step-Token generator (RFC 6238)".center(width)) | |
print("="*width) | |
print("Loaded from databases file '{:s}'.".format(dbfile).center(width)) | |
print("Remaining time: {:d} sec\n".format(remaining).center(width)) | |
print("{:>25} {}".format("Account", "Key")) | |
print("-"*width) | |
for name, secret in cursor: | |
key = calckey(secret, now) | |
print("{:>25} {}".format(name, key)) | |
if enable_plot: | |
plot(name, secret) | |
print("-"*width) | |
# plots the QR-code using the matplotlib library: | |
# the QR code contains a URI, but is encoded in the QR-code "text" mode | |
# this can currently not be fixed, it works with the Google Authentificator App though | |
def plot(name, secret): | |
import qrcode | |
import matplotlib.pyplot as plt | |
qr = qrcode.QRCode( | |
version=None, | |
error_correction=qrcode.constants.ERROR_CORRECT_L, | |
box_size=1, | |
border=1, | |
) | |
url = "otpauth://totp/{:s}?secret={:s}".format(name, secret) | |
qr.add_data(url) | |
qr.make(fit=True) | |
img = qr.make_image() | |
plt.clf() | |
plt.imshow(img, cmap="Greys", interpolation="nearest") | |
plt.axis('off') | |
plt.title(name) | |
plt.show() | |
if __name__=="__main__": | |
parser = argparse.ArgumentParser(description="Two-Step-Token generator (RFC 6238)") | |
parser.add_argument("-p", "--plot", action="store_true", help="plot the secrets via the qrcode module and Matplotlib (for transfering to a new device)") | |
parser.add_argument("database", type=str, help="sqlite database (dumped from Android)") | |
args = parser.parse_args() | |
from_database(args.database, args.plot) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment