0
# Challenge-Response Operations
1
2
HMAC-SHA1 and Yubico challenge-response authentication for secure authentication workflows, supporting both variable and fixed-length responses with configurable user interaction requirements.
3
4
## Capabilities
5
6
### Challenge-Response Authentication
7
8
Perform challenge-response operations using pre-configured YubiKey slots.
9
10
```python { .api }
11
# Base YubiKey class method
12
def challenge(challenge, mode='HMAC', slot=1, variable=True, may_block=True):
13
"""
14
Issue challenge to YubiKey and return response (base class method).
15
16
Requires YubiKey 2.2+ with challenge-response configuration in specified slot.
17
18
Parameters:
19
- challenge (bytes): Challenge data to send to YubiKey
20
- HMAC mode: Up to 64 bytes
21
- OTP mode: Exactly 6 bytes
22
- mode (str): Challenge mode
23
- 'HMAC': HMAC-SHA1 challenge-response
24
- 'OTP': Yubico challenge-response
25
- slot (int): Configuration slot number (1 or 2)
26
- variable (bool): Variable length response (HMAC mode only)
27
- True: Response length matches challenge length
28
- False: Fixed 20-byte response
29
- may_block (bool): Allow operations requiring user interaction
30
- True: May wait for button press if configured
31
- False: Fail immediately if button press required
32
33
Returns:
34
bytes: Response data
35
- HMAC mode: 20 bytes (fixed) or variable length
36
- OTP mode: 16 bytes
37
38
Raises:
39
YubiKeyVersionError: Device doesn't support challenge-response
40
YubiKeyTimeout: Operation timed out waiting for user interaction
41
InputError: Challenge data is invalid for specified mode
42
YubiKeyError: Challenge-response operation failed
43
"""
44
45
# USB HID implementation method (same signature)
46
def challenge_response(challenge, mode='HMAC', slot=1, variable=True, may_block=True):
47
"""
48
Issue challenge to YubiKey and return response (USB HID implementation).
49
50
Same functionality as challenge() method but available on USB HID implementations.
51
Both methods can be used interchangeably.
52
53
Parameters: Same as challenge() method
54
Returns: Same as challenge() method
55
Raises: Same as challenge() method
56
"""
57
```
58
59
### HMAC-SHA1 Challenge-Response
60
61
HMAC-SHA1 provides cryptographically strong challenge-response authentication.
62
63
Usage example:
64
65
```python
66
import yubico
67
import hashlib
68
import hmac
69
import os
70
71
# Connect to YubiKey
72
yk = yubico.find_yubikey()
73
74
# Generate random challenge
75
challenge = os.urandom(32)
76
77
try:
78
# Perform HMAC challenge-response (works with both challenge() and challenge_response())
79
response = yk.challenge(
80
challenge=challenge,
81
mode='HMAC',
82
slot=1,
83
variable=True
84
)
85
86
print(f"Challenge: {challenge.hex()}")
87
print(f"Response: {response.hex()}")
88
print(f"Response length: {len(response)} bytes")
89
90
except yubico.yubikey_base.YubiKeyVersionError:
91
print("YubiKey doesn't support challenge-response")
92
except yubico.yubico_exception.InputError as e:
93
print(f"Invalid challenge data: {e.reason}")
94
```
95
96
### Yubico Challenge-Response
97
98
Yubico challenge-response uses a proprietary algorithm for compatibility with legacy systems.
99
100
Usage example:
101
102
```python
103
import yubico
104
105
yk = yubico.find_yubikey()
106
107
# Yubico challenge-response requires exactly 6 bytes
108
challenge = b"123456"
109
110
try:
111
response = yk.challenge(
112
challenge=challenge,
113
mode='OTP',
114
slot=2,
115
may_block=True
116
)
117
118
print(f"Challenge: {challenge.hex()}")
119
print(f"Response: {response.hex()}")
120
print(f"Response length: {len(response)} bytes") # Always 16 bytes
121
122
except yubico.yubikey_base.YubiKeyVersionError:
123
print("YubiKey doesn't support Yubico challenge-response")
124
except yubico.yubico_exception.InputError:
125
print("Challenge must be exactly 6 bytes for OTP mode")
126
```
127
128
### Variable vs Fixed Length Responses
129
130
HMAC mode supports both variable and fixed-length responses.
131
132
```python
133
import yubico
134
135
yk = yubico.find_yubikey()
136
challenge = b"test challenge data"
137
138
# Variable length response (matches challenge length)
139
var_response = yk.challenge_response(
140
challenge=challenge,
141
mode='HMAC',
142
slot=1,
143
variable=True
144
)
145
146
# Fixed length response (always 20 bytes)
147
fixed_response = yk.challenge_response(
148
challenge=challenge,
149
mode='HMAC',
150
slot=1,
151
variable=False
152
)
153
154
print(f"Challenge length: {len(challenge)}")
155
print(f"Variable response length: {len(var_response)}")
156
print(f"Fixed response length: {len(fixed_response)}")
157
```
158
159
### Button Press Requirements
160
161
Some YubiKey configurations require user button press for challenge-response.
162
163
```python
164
import yubico
165
166
yk = yubico.find_yubikey()
167
challenge = b"authentication challenge"
168
169
# Allow blocking for button press
170
try:
171
response = yk.challenge_response(
172
challenge=challenge,
173
mode='HMAC',
174
slot=1,
175
may_block=True
176
)
177
print("Challenge-response successful")
178
179
except yubico.yubikey_base.YubiKeyTimeout:
180
print("Timed out waiting for button press")
181
182
# Fail immediately if button press required
183
try:
184
response = yk.challenge_response(
185
challenge=challenge,
186
mode='HMAC',
187
slot=1,
188
may_block=False
189
)
190
print("Challenge-response successful (no button press required)")
191
192
except yubico.yubikey_base.YubiKeyTimeout:
193
print("Button press required but may_block=False")
194
```
195
196
### Multi-Slot Challenge-Response
197
198
YubiKeys can have different challenge-response configurations in each slot.
199
200
```python
201
import yubico
202
203
yk = yubico.find_yubikey()
204
challenge = b"test challenge"
205
206
# Test both slots
207
for slot in [1, 2]:
208
try:
209
response = yk.challenge_response(challenge, slot=slot)
210
print(f"Slot {slot} response: {response.hex()}")
211
except yubico.yubikey_base.YubiKeyError:
212
print(f"Slot {slot} not configured for challenge-response")
213
```
214
215
### Authentication Workflow Example
216
217
Complete authentication workflow using challenge-response.
218
219
```python
220
import yubico
221
import hashlib
222
import hmac
223
import os
224
import time
225
226
def yubikey_authenticate(user_id, expected_secret=None):
227
"""
228
Authenticate user using YubiKey challenge-response.
229
230
Parameters:
231
- user_id (str): User identifier
232
- expected_secret (bytes): Known secret for verification (demo only)
233
234
Returns:
235
bool: Authentication success
236
"""
237
try:
238
# Connect to YubiKey
239
yk = yubico.find_yubikey()
240
241
# Generate random challenge
242
challenge = os.urandom(20)
243
244
# Get YubiKey response
245
yk_response = yk.challenge_response(
246
challenge=challenge,
247
mode='HMAC',
248
slot=1,
249
variable=False # Fixed 20-byte response
250
)
251
252
# For demo: verify against known secret
253
if expected_secret:
254
expected_response = hmac.new(
255
expected_secret,
256
challenge,
257
hashlib.sha1
258
).digest()
259
260
if hmac.compare_digest(yk_response, expected_response):
261
print(f"Authentication successful for user: {user_id}")
262
return True
263
else:
264
print(f"Authentication failed for user: {user_id}")
265
return False
266
267
# In real applications, verify response against stored hash
268
print(f"Challenge-response completed for user: {user_id}")
269
print(f"Response: {yk_response.hex()}")
270
return True
271
272
except yubico.yubico_exception.YubicoError as e:
273
print(f"YubiKey authentication failed: {e.reason}")
274
return False
275
276
# Example usage
277
secret = os.urandom(20) # In practice, store this securely
278
success = yubikey_authenticate("alice", secret)
279
```
280
281
### Performance Considerations
282
283
Challenge-response operations have different performance characteristics.
284
285
```python
286
import yubico
287
import time
288
289
yk = yubico.find_yubikey()
290
challenge = b"performance test"
291
292
# Measure response time
293
start_time = time.time()
294
response = yk.challenge_response(challenge, mode='HMAC', slot=1)
295
end_time = time.time()
296
297
print(f"Challenge-response time: {(end_time - start_time)*1000:.2f} ms")
298
299
# Test multiple operations
300
times = []
301
for i in range(10):
302
start = time.time()
303
yk.challenge_response(f"test{i}".encode(), mode='HMAC', slot=1)
304
end = time.time()
305
times.append((end - start) * 1000)
306
307
avg_time = sum(times) / len(times)
308
print(f"Average response time over 10 operations: {avg_time:.2f} ms")
309
```
310
311
### Error Handling
312
313
Comprehensive error handling for challenge-response operations.
314
315
```python
316
import yubico
317
318
def safe_challenge_response(yk, challenge, **kwargs):
319
"""
320
Perform challenge-response with comprehensive error handling.
321
"""
322
try:
323
return yk.challenge_response(challenge, **kwargs)
324
325
except yubico.yubikey_base.YubiKeyVersionError:
326
print("ERROR: YubiKey doesn't support challenge-response")
327
print("Required: YubiKey 2.2+ with challenge-response configuration")
328
329
except yubico.yubikey_base.YubiKeyTimeout:
330
print("ERROR: Operation timed out")
331
print("This may indicate button press is required")
332
333
except yubico.yubico_exception.InputError as e:
334
print(f"ERROR: Invalid challenge data: {e.reason}")
335
print("HMAC mode: up to 64 bytes")
336
print("OTP mode: exactly 6 bytes")
337
338
except yubico.yubikey_base.YubiKeyError as e:
339
print(f"ERROR: Challenge-response failed: {e.reason}")
340
print("Check that the specified slot is configured for challenge-response")
341
342
return None
343
344
# Usage
345
yk = yubico.find_yubikey()
346
challenge = b"test challenge"
347
response = safe_challenge_response(yk, challenge, mode='HMAC', slot=1)
348
349
if response:
350
print(f"Success: {response.hex()}")
351
else:
352
print("Challenge-response failed")
353
```
354
355
## Configuration Requirements
356
357
Before using challenge-response, the YubiKey must be configured appropriately:
358
359
```python
360
import yubico
361
from yubico.yubikey_config import YubiKeyConfig
362
import os
363
364
# Configure YubiKey for HMAC challenge-response
365
yk = yubico.find_yubikey()
366
cfg = yk.init_config()
367
368
# Set secret key (20 bytes for HMAC)
369
secret = os.urandom(20)
370
cfg.mode_challenge_response(
371
secret=secret,
372
type='HMAC',
373
variable=True,
374
require_button=False # Set to True if button press required
375
)
376
377
# Write configuration to slot 1
378
yk.write_config(cfg, slot=1)
379
print("YubiKey configured for challenge-response")
380
381
# Test the configuration
382
test_challenge = b"configuration test"
383
response = yk.challenge_response(test_challenge, slot=1)
384
print(f"Test response: {response.hex()}")
385
```