0
# Utility Functions
1
2
Helper functions for data processing, CRC validation, hex dumping, ModHex encoding/decoding, and cryptographic operations commonly needed in YubiKey applications.
3
4
## Capabilities
5
6
### CRC Validation
7
8
Functions for calculating and validating ISO13239 CRC checksums used in YubiKey data integrity verification.
9
10
```python { .api }
11
def crc16(data):
12
"""
13
Calculate ISO13239 CRC checksum of input data.
14
15
Used internally by YubiKey protocol for data integrity verification.
16
Useful for custom protocol implementations and data validation.
17
18
Parameters:
19
- data (bytes): Input data to calculate checksum for
20
21
Returns:
22
int: 16-bit CRC checksum value
23
"""
24
25
def validate_crc16(data):
26
"""
27
Validate CRC16 checksum of data.
28
29
Verifies that the last 2 bytes of the input data contain a valid
30
CRC16 checksum for the preceding data.
31
32
Parameters:
33
- data (bytes): Data with CRC16 checksum appended
34
35
Returns:
36
bool: True if CRC checksum is valid, False otherwise
37
38
Raises:
39
InputError: If data is too short to contain CRC
40
"""
41
```
42
43
CRC validation example:
44
45
```python
46
import yubico.yubico_util as util
47
48
# Calculate CRC for data
49
data = b"Hello, YubiKey!"
50
crc_value = util.crc16(data)
51
print(f"CRC16 of '{data.decode()}': 0x{crc_value:04x}")
52
53
# Create data with CRC appended
54
data_with_crc = data + crc_value.to_bytes(2, 'little')
55
56
# Validate CRC
57
is_valid = util.validate_crc16(data_with_crc)
58
print(f"CRC validation: {'PASS' if is_valid else 'FAIL'}")
59
60
# Test with corrupted data
61
corrupted_data = data + b'\x00\x00' # Wrong CRC
62
is_valid = util.validate_crc16(corrupted_data)
63
print(f"Corrupted data validation: {'PASS' if is_valid else 'FAIL'}")
64
```
65
66
### Hex Dump Utilities
67
68
Functions for creating human-readable hex dumps of binary data for debugging and analysis.
69
70
```python { .api }
71
def hexdump(src, length=8, colorize=False):
72
"""
73
Create formatted hexadecimal dump of binary data.
74
75
Produces output similar to the Unix hexdump utility, showing both
76
hex values and ASCII representation for debugging and analysis.
77
78
Parameters:
79
- src (bytes): Source data to dump
80
- length (int): Number of bytes per line (default: 8)
81
- colorize (bool): Enable colored output (default: False)
82
83
Returns:
84
str: Formatted hex dump string
85
"""
86
```
87
88
Hex dump example:
89
90
```python
91
import yubico.yubico_util as util
92
93
# Create sample data
94
data = b"YubiKey challenge-response data with binary\x00\x01\x02\x03"
95
96
# Basic hex dump
97
print("Basic hex dump:")
98
print(util.hexdump(data))
99
100
# Custom width
101
print("\nWide hex dump (16 bytes per line):")
102
print(util.hexdump(data, length=16))
103
104
# Colorized output (if terminal supports it)
105
print("\nColorized hex dump:")
106
print(util.hexdump(data, colorize=True))
107
108
# Dump YubiKey response data
109
import yubico
110
try:
111
yk = yubico.find_yubikey()
112
response = yk.challenge_response(b"test challenge", slot=1)
113
print("\nYubiKey response hex dump:")
114
print(util.hexdump(response))
115
except yubico.yubico_exception.YubicoError:
116
print("YubiKey not available for response dump")
117
```
118
119
### ModHex Encoding
120
121
Functions for working with ModHex (Modified Hexadecimal) encoding used by YubiKey OTP output.
122
123
```python { .api }
124
def modhex_decode(data):
125
"""
126
Decode ModHex string to regular hexadecimal.
127
128
ModHex uses the characters 'cbdefghijklnrtuv' instead of '0123456789abcdef'
129
to avoid keyboard layout issues and ensure reliable character input.
130
131
Parameters:
132
- data (str): ModHex encoded string
133
134
Returns:
135
str: Regular hexadecimal string
136
137
Raises:
138
InputError: If input contains invalid ModHex characters
139
"""
140
```
141
142
ModHex example:
143
144
```python
145
import yubico.yubico_util as util
146
147
# ModHex characters: cbdefghijklnrtuv (instead of 0123456789abcdef)
148
modhex_data = "vvhhhbbbdvcv" # Example ModHex string
149
150
try:
151
hex_data = util.modhex_decode(modhex_data)
152
print(f"ModHex: {modhex_data}")
153
print(f"Hex: {hex_data}")
154
155
# Convert to bytes
156
binary_data = bytes.fromhex(hex_data)
157
print(f"Binary: {binary_data}")
158
159
except yubico.yubico_exception.InputError as e:
160
print(f"ModHex decode error: {e.reason}")
161
162
# Working with YubiKey OTP output
163
yubikey_otp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"
164
print(f"\nYubiKey OTP: {yubikey_otp}")
165
166
# First 12 characters are usually the public ID in ModHex
167
public_id_modhex = yubikey_otp[:12]
168
otp_modhex = yubikey_otp[12:]
169
170
try:
171
public_id_hex = util.modhex_decode(public_id_modhex)
172
otp_hex = util.modhex_decode(otp_modhex)
173
174
print(f"Public ID (ModHex): {public_id_modhex}")
175
print(f"Public ID (Hex): {public_id_hex}")
176
print(f"OTP (ModHex): {otp_modhex}")
177
print(f"OTP (Hex): {otp_hex}")
178
179
except yubico.yubico_exception.InputError as e:
180
print(f"OTP decode error: {e.reason}")
181
```
182
183
### HOTP Operations
184
185
Functions for HOTP (HMAC-based One-Time Password) truncation according to RFC 4226.
186
187
```python { .api }
188
def hotp_truncate(hmac_result, length=6):
189
"""
190
Perform HOTP truncation operation per RFC 4226.
191
192
Truncates HMAC-SHA1 result to generate numeric OTP codes.
193
Used in OATH-HOTP implementations and OTP validation.
194
195
Parameters:
196
- hmac_result (bytes): 20-byte HMAC-SHA1 result
197
- length (int): Desired OTP code length (typically 6 or 8)
198
199
Returns:
200
int: Truncated numeric OTP code
201
202
Raises:
203
InputError: If HMAC result length is invalid or length is out of range
204
"""
205
```
206
207
HOTP truncation example:
208
209
```python
210
import yubico.yubico_util as util
211
import hmac
212
import hashlib
213
214
# Example HOTP calculation
215
secret = b"12345678901234567890" # 20-byte secret
216
counter = 1234 # HOTP counter value
217
218
# Calculate HMAC-SHA1
219
counter_bytes = counter.to_bytes(8, 'big')
220
hmac_result = hmac.new(secret, counter_bytes, hashlib.sha1).digest()
221
222
print(f"Secret: {secret.hex()}")
223
print(f"Counter: {counter}")
224
print(f"HMAC-SHA1: {hmac_result.hex()}")
225
226
# Truncate to 6-digit OTP
227
try:
228
otp_6 = util.hotp_truncate(hmac_result, length=6)
229
print(f"6-digit OTP: {otp_6:06d}")
230
231
# Truncate to 8-digit OTP
232
otp_8 = util.hotp_truncate(hmac_result, length=8)
233
print(f"8-digit OTP: {otp_8:08d}")
234
235
except yubico.yubico_exception.InputError as e:
236
print(f"HOTP truncation error: {e.reason}")
237
238
# Generate sequence of HOTP codes
239
print("\nHOTP sequence:")
240
for i in range(5):
241
counter_bytes = (counter + i).to_bytes(8, 'big')
242
hmac_result = hmac.new(secret, counter_bytes, hashlib.sha1).digest()
243
otp = util.hotp_truncate(hmac_result, length=6)
244
print(f"Counter {counter + i}: {otp:06d}")
245
```
246
247
248
### Python 2/3 Compatibility
249
250
Utility functions for handling differences between Python 2 and Python 3 byte/string operations.
251
252
```python { .api }
253
def ord_byte(byte):
254
"""
255
Convert byte to integer value (Python 2/3 compatible).
256
257
Handles the difference between Python 2 (where bytes are strings)
258
and Python 3 (where bytes are integers when indexed).
259
260
Parameters:
261
- byte: Single byte (str in Python 2, int in Python 3)
262
263
Returns:
264
int: Integer value of the byte
265
"""
266
267
def chr_byte(number):
268
"""
269
Convert integer to single-byte string (Python 2/3 compatible).
270
271
Handles the difference between Python 2 chr() and Python 3 bytes()
272
for creating single-byte strings.
273
274
Parameters:
275
- number (int): Integer value (0-255)
276
277
Returns:
278
bytes: Single-byte string
279
"""
280
```
281
282
Compatibility function example:
283
284
```python
285
import yubico.yubico_util as util
286
287
# Working with binary data across Python versions
288
data = b"binary data example"
289
290
print("Processing bytes:")
291
for i, byte in enumerate(data):
292
# Use ord_byte for consistent behavior
293
byte_value = util.ord_byte(byte)
294
print(f" Byte {i}: 0x{byte_value:02x} ({byte_value})")
295
296
# Creating binary data
297
print("\nCreating bytes:")
298
binary_data = b""
299
for value in [0x48, 0x65, 0x6c, 0x6c, 0x6f]: # "Hello"
300
binary_data += util.chr_byte(value)
301
302
print(f"Created: {binary_data} ({binary_data.decode()})")
303
304
# Practical usage in YubiKey data processing
305
def process_yubikey_data(response_data):
306
"""Process YubiKey response data in a Python 2/3 compatible way."""
307
processed = []
308
309
for i in range(len(response_data)):
310
byte_val = util.ord_byte(response_data[i])
311
312
# Apply some processing (example: XOR with 0x5C)
313
processed_byte = byte_val ^ 0x5C
314
processed.append(util.chr_byte(processed_byte))
315
316
return b"".join(processed)
317
318
# Example usage
319
sample_response = b"YubiKey response data"
320
processed = process_yubikey_data(sample_response)
321
print(f"\nOriginal: {sample_response.hex()}")
322
print(f"Processed: {processed.hex()}")
323
```
324
325
326
## Usage Best Practices
327
328
### Error Handling
329
330
Always handle potential errors when using utility functions:
331
332
```python
333
import yubico.yubico_util as util
334
import yubico.yubico_exception
335
336
def safe_modhex_decode(modhex_str):
337
"""Safely decode ModHex with error handling."""
338
try:
339
return util.modhex_decode(modhex_str)
340
except yubico.yubico_exception.InputError as e:
341
print(f"ModHex decode failed: {e.reason}")
342
return None
343
344
def safe_crc_validate(data):
345
"""Safely validate CRC with error handling."""
346
try:
347
return util.validate_crc16(data)
348
except yubico.yubico_exception.InputError as e:
349
print(f"CRC validation failed: {e.reason}")
350
return False
351
```
352
353
### Performance Considerations
354
355
Some utility functions are computationally intensive:
356
357
```python
358
import yubico.yubico_util as util
359
import time
360
361
# CRC calculation performance
362
large_data = b"x" * 10000
363
start_time = time.time()
364
crc_result = util.crc16(large_data)
365
end_time = time.time()
366
367
print(f"CRC16 calculation time for {len(large_data)} bytes: {(end_time - start_time)*1000:.2f} ms")
368
369
# Optimize for repeated operations
370
def batch_crc_validation(data_list):
371
"""Efficiently validate CRC for multiple data blocks."""
372
results = []
373
for data in data_list:
374
try:
375
results.append(util.validate_crc16(data))
376
except yubico.yubico_exception.InputError:
377
results.append(False)
378
return results
379
```