CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-python-yubico

Python library for communicating with Yubico YubiKey hardware authentication tokens

Pending
Overview
Eval results
Files

utilities.mddocs/

Utility Functions

Helper functions for data processing, CRC validation, hex dumping, ModHex encoding/decoding, and cryptographic operations commonly needed in YubiKey applications.

Capabilities

CRC Validation

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'}")

Hex Dump Utilities

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")

ModHex Encoding

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}")

HOTP Operations

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}")

Python 2/3 Compatibility

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()}")

Usage Best Practices

Error Handling

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 False

Performance Considerations

Some 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 results

Install with Tessl CLI

npx tessl i tessl/pypi-python-yubico

docs

challenge-response.md

configuration.md

device-discovery.md

device-interface.md

exceptions.md

index.md

utilities.md

tile.json