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