Python library for communicating with Yubico YubiKey hardware authentication tokens
—
Helper functions for data processing, CRC validation, hex dumping, ModHex encoding/decoding, and cryptographic operations commonly needed in YubiKey applications.
Functions for calculating and validating ISO13239 CRC checksums used in YubiKey data integrity verification.
def crc16(data):
"""
Calculate ISO13239 CRC checksum of input data.
Used internally by YubiKey protocol for data integrity verification.
Useful for custom protocol implementations and data validation.
Parameters:
- data (bytes): Input data to calculate checksum for
Returns:
int: 16-bit CRC checksum value
"""
def validate_crc16(data):
"""
Validate CRC16 checksum of data.
Verifies that the last 2 bytes of the input data contain a valid
CRC16 checksum for the preceding data.
Parameters:
- data (bytes): Data with CRC16 checksum appended
Returns:
bool: True if CRC checksum is valid, False otherwise
Raises:
InputError: If data is too short to contain CRC
"""CRC validation example:
import yubico.yubico_util as util
# Calculate CRC for data
data = b"Hello, YubiKey!"
crc_value = util.crc16(data)
print(f"CRC16 of '{data.decode()}': 0x{crc_value:04x}")
# Create data with CRC appended
data_with_crc = data + crc_value.to_bytes(2, 'little')
# Validate CRC
is_valid = util.validate_crc16(data_with_crc)
print(f"CRC validation: {'PASS' if is_valid else 'FAIL'}")
# Test with corrupted data
corrupted_data = data + b'\x00\x00' # Wrong CRC
is_valid = util.validate_crc16(corrupted_data)
print(f"Corrupted data validation: {'PASS' if is_valid else 'FAIL'}")Functions for creating human-readable hex dumps of binary data for debugging and analysis.
def hexdump(src, length=8, colorize=False):
"""
Create formatted hexadecimal dump of binary data.
Produces output similar to the Unix hexdump utility, showing both
hex values and ASCII representation for debugging and analysis.
Parameters:
- src (bytes): Source data to dump
- length (int): Number of bytes per line (default: 8)
- colorize (bool): Enable colored output (default: False)
Returns:
str: Formatted hex dump string
"""Hex dump example:
import yubico.yubico_util as util
# Create sample data
data = b"YubiKey challenge-response data with binary\x00\x01\x02\x03"
# Basic hex dump
print("Basic hex dump:")
print(util.hexdump(data))
# Custom width
print("\nWide hex dump (16 bytes per line):")
print(util.hexdump(data, length=16))
# Colorized output (if terminal supports it)
print("\nColorized hex dump:")
print(util.hexdump(data, colorize=True))
# Dump YubiKey response data
import yubico
try:
yk = yubico.find_yubikey()
response = yk.challenge_response(b"test challenge", slot=1)
print("\nYubiKey response hex dump:")
print(util.hexdump(response))
except yubico.yubico_exception.YubicoError:
print("YubiKey not available for response dump")Functions for working with ModHex (Modified Hexadecimal) encoding used by YubiKey OTP output.
def modhex_decode(data):
"""
Decode ModHex string to regular hexadecimal.
ModHex uses the characters 'cbdefghijklnrtuv' instead of '0123456789abcdef'
to avoid keyboard layout issues and ensure reliable character input.
Parameters:
- data (str): ModHex encoded string
Returns:
str: Regular hexadecimal string
Raises:
InputError: If input contains invalid ModHex characters
"""ModHex example:
import yubico.yubico_util as util
# ModHex characters: cbdefghijklnrtuv (instead of 0123456789abcdef)
modhex_data = "vvhhhbbbdvcv" # Example ModHex string
try:
hex_data = util.modhex_decode(modhex_data)
print(f"ModHex: {modhex_data}")
print(f"Hex: {hex_data}")
# Convert to bytes
binary_data = bytes.fromhex(hex_data)
print(f"Binary: {binary_data}")
except yubico.yubico_exception.InputError as e:
print(f"ModHex decode error: {e.reason}")
# Working with YubiKey OTP output
yubikey_otp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"
print(f"\nYubiKey OTP: {yubikey_otp}")
# First 12 characters are usually the public ID in ModHex
public_id_modhex = yubikey_otp[:12]
otp_modhex = yubikey_otp[12:]
try:
public_id_hex = util.modhex_decode(public_id_modhex)
otp_hex = util.modhex_decode(otp_modhex)
print(f"Public ID (ModHex): {public_id_modhex}")
print(f"Public ID (Hex): {public_id_hex}")
print(f"OTP (ModHex): {otp_modhex}")
print(f"OTP (Hex): {otp_hex}")
except yubico.yubico_exception.InputError as e:
print(f"OTP decode error: {e.reason}")Functions for HOTP (HMAC-based One-Time Password) truncation according to RFC 4226.
def hotp_truncate(hmac_result, length=6):
"""
Perform HOTP truncation operation per RFC 4226.
Truncates HMAC-SHA1 result to generate numeric OTP codes.
Used in OATH-HOTP implementations and OTP validation.
Parameters:
- hmac_result (bytes): 20-byte HMAC-SHA1 result
- length (int): Desired OTP code length (typically 6 or 8)
Returns:
int: Truncated numeric OTP code
Raises:
InputError: If HMAC result length is invalid or length is out of range
"""HOTP truncation example:
import yubico.yubico_util as util
import hmac
import hashlib
# Example HOTP calculation
secret = b"12345678901234567890" # 20-byte secret
counter = 1234 # HOTP counter value
# Calculate HMAC-SHA1
counter_bytes = counter.to_bytes(8, 'big')
hmac_result = hmac.new(secret, counter_bytes, hashlib.sha1).digest()
print(f"Secret: {secret.hex()}")
print(f"Counter: {counter}")
print(f"HMAC-SHA1: {hmac_result.hex()}")
# Truncate to 6-digit OTP
try:
otp_6 = util.hotp_truncate(hmac_result, length=6)
print(f"6-digit OTP: {otp_6:06d}")
# Truncate to 8-digit OTP
otp_8 = util.hotp_truncate(hmac_result, length=8)
print(f"8-digit OTP: {otp_8:08d}")
except yubico.yubico_exception.InputError as e:
print(f"HOTP truncation error: {e.reason}")
# Generate sequence of HOTP codes
print("\nHOTP sequence:")
for i in range(5):
counter_bytes = (counter + i).to_bytes(8, 'big')
hmac_result = hmac.new(secret, counter_bytes, hashlib.sha1).digest()
otp = util.hotp_truncate(hmac_result, length=6)
print(f"Counter {counter + i}: {otp:06d}")Utility functions for handling differences between Python 2 and Python 3 byte/string operations.
def ord_byte(byte):
"""
Convert byte to integer value (Python 2/3 compatible).
Handles the difference between Python 2 (where bytes are strings)
and Python 3 (where bytes are integers when indexed).
Parameters:
- byte: Single byte (str in Python 2, int in Python 3)
Returns:
int: Integer value of the byte
"""
def chr_byte(number):
"""
Convert integer to single-byte string (Python 2/3 compatible).
Handles the difference between Python 2 chr() and Python 3 bytes()
for creating single-byte strings.
Parameters:
- number (int): Integer value (0-255)
Returns:
bytes: Single-byte string
"""Compatibility function example:
import yubico.yubico_util as util
# Working with binary data across Python versions
data = b"binary data example"
print("Processing bytes:")
for i, byte in enumerate(data):
# Use ord_byte for consistent behavior
byte_value = util.ord_byte(byte)
print(f" Byte {i}: 0x{byte_value:02x} ({byte_value})")
# Creating binary data
print("\nCreating bytes:")
binary_data = b""
for value in [0x48, 0x65, 0x6c, 0x6c, 0x6f]: # "Hello"
binary_data += util.chr_byte(value)
print(f"Created: {binary_data} ({binary_data.decode()})")
# Practical usage in YubiKey data processing
def process_yubikey_data(response_data):
"""Process YubiKey response data in a Python 2/3 compatible way."""
processed = []
for i in range(len(response_data)):
byte_val = util.ord_byte(response_data[i])
# Apply some processing (example: XOR with 0x5C)
processed_byte = byte_val ^ 0x5C
processed.append(util.chr_byte(processed_byte))
return b"".join(processed)
# Example usage
sample_response = b"YubiKey response data"
processed = process_yubikey_data(sample_response)
print(f"\nOriginal: {sample_response.hex()}")
print(f"Processed: {processed.hex()}")Always handle potential errors when using utility functions:
import yubico.yubico_util as util
import yubico.yubico_exception
def safe_modhex_decode(modhex_str):
"""Safely decode ModHex with error handling."""
try:
return util.modhex_decode(modhex_str)
except yubico.yubico_exception.InputError as e:
print(f"ModHex decode failed: {e.reason}")
return None
def safe_crc_validate(data):
"""Safely validate CRC with error handling."""
try:
return util.validate_crc16(data)
except yubico.yubico_exception.InputError as e:
print(f"CRC validation failed: {e.reason}")
return FalseSome utility functions are computationally intensive:
import yubico.yubico_util as util
import time
# CRC calculation performance
large_data = b"x" * 10000
start_time = time.time()
crc_result = util.crc16(large_data)
end_time = time.time()
print(f"CRC16 calculation time for {len(large_data)} bytes: {(end_time - start_time)*1000:.2f} ms")
# Optimize for repeated operations
def batch_crc_validation(data_list):
"""Efficiently validate CRC for multiple data blocks."""
results = []
for data in data_list:
try:
results.append(util.validate_crc16(data))
except yubico.yubico_exception.InputError:
results.append(False)
return resultsInstall with Tessl CLI
npx tessl i tessl/pypi-python-yubico