Create a gist now

Instantly share code, notes, and snippets.

@otms61 /Yubico.py
Last active Dec 30, 2016

What would you like to do?
from Crypto.Cipher import AES
from binascii import hexlify, unhexlify
d = {'b': '1', 'c': '0', 'd': '2', 'e': '3', 'f': '4', 'g': '5', 'h': '6', 'i': '7', 'j': '8', 'k': '9', 'l': 'a', 'n': 'b', 'r': 'c', 't': 'd', 'u': 'e', 'v': 'f'}
rd = {'0': 'c', '1': 'b', '2': 'd', '3': 'e', '4': 'f', '5': 'g', '6': 'h', '7': 'i', '8': 'j', '9': 'k', 'a': 'l', 'b': 'n', 'c': 'r', 'd': 't', 'e': 'u', 'f': 'v'}
def modhex2hex(x):
return ''.join([d[i] for i in x])
def hex2modhex(x):
return ''.join([rd[i] for i in x])
def update_crc(crc, x):
crc ^= x
for _ in range(8):
flag = crc & 1
crc >>= 1
if flag != 0:
crc ^= 0x8408
return crc & 0xffff
def get_crc(s):
assert len(s) == 14, "Invalid length"
crc = 0x5af0
for i in s:
crc = update_crc(crc, i)
return crc
def verify_crc(s):
assert len(s) == 16, "Invalid length"
crc = 0xffff
for i in s:
crc = update_crc(crc, i)
return crc == 0xf0b8
class Yubico(object):
def __init__(self, key):
self.key = key
self.otp = 0
self.public_id = 0
self.plain = b''
self.uid = 0
self.useCtr = 0
self.tstp = 0
self.sessionCtr = 0
self.rnd = 0
self.crc = 0
def __repr__(self):
return f'< public_id:{self.public_id:012x} private_id:{self.uid:012x} useCtr:{self.useCtr:04x} tstp:{self.tstp:06x} sessionCtr:{self.sessionCtr:02x} rnd:{self.rnd:04x} crc:{self.crc:04x} >'
def calc_crc(self):
r = \
self.uid.to_bytes(6, 'big') + \
self.useCtr.to_bytes(2, 'little') + \
self.tstp.to_bytes(3, 'big') + \
self.sessionCtr.to_bytes(1, 'big') + \
self.rnd.to_bytes(2, 'big')
return get_crc(r)
@classmethod
def parse_otp(cls, key, otp):
yubico = cls(key)
yubico.key = key
yubico.otp = otp
otp_hex = modhex2hex(yubico.otp)
yubico.public_id = int(otp_hex[:12], 16)
crypted = unhexlify(otp_hex[12:])
yubico.plain = AES.new(key).decrypt(crypted)
yubico.uid = int.from_bytes(yubico.plain[:6], 'big')
yubico.useCtr = int.from_bytes(yubico.plain[6:8], 'little')
yubico.tstp = int.from_bytes(yubico.plain[8:11], 'big')
yubico.sessionCtr = int.from_bytes(yubico.plain[11:12], 'big')
yubico.rnd = int.from_bytes(yubico.plain[12:14], 'big')
yubico.crc = int.from_bytes(yubico.plain[14:16], 'little')
return yubico
@classmethod
def calc_otp(cls, key, public_id, private_id, useCtr, sessionCtr, tstp=0, rnd=0):
yubico = cls(key)
yubico.public_id = public_id
yubico.uid = private_id
yubico.useCtr = useCtr
yubico.sessionCtr = sessionCtr
yubico.tstp = tstp
yubico.rnd = rnd
yubico.crc = yubico.calc_crc()
plain = \
yubico.uid.to_bytes(6, 'big') + \
yubico.useCtr.to_bytes(2, 'little') + \
yubico.tstp.to_bytes(3, 'big') + \
yubico.sessionCtr.to_bytes(1, 'big') + \
yubico.rnd.to_bytes(2, 'big') + \
yubico.crc.to_bytes(2, 'little')
crypted = AES.new(key).encrypt(plain)
otp = yubico.public_id.to_bytes(6, 'big') + crypted
yubico.otp = hex2modhex(otp.hex())
return yubico
key = b"\xa9\xe2\x29\x33\x2e\x87\x0f\x26\x1e\xa5\x5a\x2a\xbd\xef\xda\xe0"
s = '''vvntibfekfkkuvrvubtictldndbenurgrgbukhkutild
vvntibfekfkkcgfeljervjjcejvjkvttthndftrtbdrf
vvntibfekfkkbnkhcdiuhbbbflbuitdnecbkbnlkchgv
vvntibfekfkkbevrttebkucvbdrntikdicluudifdgil
vvntibfekfkkjfvttcrfvdkrrrvdidrrrdlcdefvhege'''
for i in s.split('\n'):
y = Yubico.parse_otp(key, i)
print(y)
print()
y1 = Yubico.calc_otp(key, 0xffbd71439499, 0x8a00555dd7db, 0x01, 0x0, 0xf011a4, 0xadfd)
print(y1)
print(y1.otp)
y2 = Yubico.parse_otp(key, y1.otp)
print(y2)
print(y2.otp)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment