0
# Two-Factor Authentication
1
2
Time-based and HMAC-based one-time password (OTP) generation and verification for implementing two-factor authentication systems. Compatible with standard authenticator apps.
3
4
## Core Imports
5
6
```python
7
from cryptography.hazmat.primitives.twofactor.totp import TOTP
8
from cryptography.hazmat.primitives.twofactor.hotp import HOTP
9
from cryptography.hazmat.primitives import hashes
10
from cryptography.exceptions import InvalidToken
11
```
12
13
## Capabilities
14
15
### TOTP (Time-based One-Time Password)
16
17
```python { .api }
18
class TOTP:
19
def __init__(self, key: bytes, length: int, algorithm, time_step: int, backend=None):
20
"""
21
Initialize TOTP generator.
22
23
Args:
24
key (bytes): Shared secret key (base32 decoded)
25
length (int): OTP length (6 or 8 digits)
26
algorithm: Hash algorithm (hashes.SHA1(), hashes.SHA256(), etc.)
27
time_step (int): Time step in seconds (usually 30)
28
backend: Cryptographic backend
29
"""
30
31
def generate(self, time: int) -> bytes:
32
"""
33
Generate TOTP for specific time.
34
35
Args:
36
time (int): Unix timestamp
37
38
Returns:
39
bytes: OTP as bytes (e.g., b'123456')
40
"""
41
42
def verify(self, totp: bytes, time: int) -> None:
43
"""
44
Verify TOTP for specific time.
45
46
Args:
47
totp (bytes): OTP to verify
48
time (int): Unix timestamp
49
50
Raises:
51
InvalidToken: If TOTP is invalid for given time
52
"""
53
```
54
55
### HOTP (HMAC-based One-Time Password)
56
57
```python { .api }
58
class HOTP:
59
def __init__(self, key: bytes, length: int, algorithm, backend=None):
60
"""
61
Initialize HOTP generator.
62
63
Args:
64
key (bytes): Shared secret key
65
length (int): OTP length (6 or 8 digits)
66
algorithm: Hash algorithm (hashes.SHA1() most common)
67
backend: Cryptographic backend
68
"""
69
70
def generate(self, counter: int) -> bytes:
71
"""
72
Generate HOTP for specific counter value.
73
74
Args:
75
counter (int): Counter value
76
77
Returns:
78
bytes: OTP as bytes
79
"""
80
81
def verify(self, hotp: bytes, counter: int) -> None:
82
"""
83
Verify HOTP for specific counter.
84
85
Args:
86
hotp (bytes): OTP to verify
87
counter (int): Counter value
88
89
Raises:
90
InvalidToken: If HOTP is invalid for given counter
91
"""
92
```
93
94
## Usage Examples
95
96
### Basic TOTP Implementation
97
98
```python
99
from cryptography.hazmat.primitives.twofactor.totp import TOTP
100
from cryptography.hazmat.primitives import hashes
101
from cryptography.exceptions import InvalidToken
102
import time
103
import base64
104
import secrets
105
106
# Generate random secret key
107
secret_key = secrets.token_bytes(20) # 160-bit key
108
print(f"Secret key (base32): {base64.b32encode(secret_key).decode()}")
109
110
# Create TOTP instance (standard 6-digit, 30-second window)
111
totp = TOTP(secret_key, 6, hashes.SHA1(), 30)
112
113
# Generate TOTP for current time
114
current_time = int(time.time())
115
token = totp.generate(current_time)
116
print(f"Current TOTP: {token.decode()}")
117
118
# Verify token
119
try:
120
totp.verify(token, current_time)
121
print("TOTP verification: SUCCESS")
122
except InvalidToken:
123
print("TOTP verification: FAILED")
124
125
# Test with slightly different time (within window)
126
totp.verify(token, current_time + 15) # Still valid within 30-second window
127
print("TOTP still valid within time window")
128
```
129
130
### TOTP QR Code Generation for Authenticator Apps
131
132
```python
133
from cryptography.hazmat.primitives.twofactor.totp import TOTP
134
from cryptography.hazmat.primitives import hashes
135
import base64
136
import secrets
137
import urllib.parse
138
139
class TOTPSetup:
140
def __init__(self, issuer: str = "MyApp"):
141
self.issuer = issuer
142
143
def generate_secret(self) -> str:
144
"""Generate base32-encoded secret for user"""
145
secret_bytes = secrets.token_bytes(20)
146
return base64.b32encode(secret_bytes).decode()
147
148
def generate_qr_url(self, secret: str, account_name: str) -> str:
149
"""Generate URL for QR code compatible with authenticator apps"""
150
# Standard format: otpauth://totp/issuer:account?secret=SECRET&issuer=ISSUER
151
secret_clean = secret.replace(' ', '') # Remove spaces
152
153
params = {
154
'secret': secret_clean,
155
'issuer': self.issuer,
156
'algorithm': 'SHA1',
157
'digits': '6',
158
'period': '30'
159
}
160
161
query_string = urllib.parse.urlencode(params)
162
label = f"{self.issuer}:{account_name}"
163
url = f"otpauth://totp/{urllib.parse.quote(label)}?{query_string}"
164
165
return url
166
167
def create_totp(self, secret: str) -> TOTP:
168
"""Create TOTP instance from base32 secret"""
169
secret_bytes = base64.b32decode(secret.upper())
170
return TOTP(secret_bytes, 6, hashes.SHA1(), 30)
171
172
# Usage
173
setup = TOTPSetup("MySecureApp")
174
175
# Generate secret for new user
176
user_secret = setup.generate_secret()
177
print(f"User secret: {user_secret}")
178
179
# Generate QR code URL
180
qr_url = setup.generate_qr_url(user_secret, "alice@example.com")
181
print(f"QR Code URL: {qr_url}")
182
183
# Create TOTP instance for verification
184
user_totp = setup.create_totp(user_secret)
185
186
# Server-side verification
187
import time
188
current_token = user_totp.generate(int(time.time()))
189
print(f"Expected token: {current_token.decode()}")
190
```
191
192
### HOTP Counter-based Authentication
193
194
```python
195
from cryptography.hazmat.primitives.twofactor.hotp import HOTP
196
from cryptography.hazmat.primitives import hashes
197
from cryptography.exceptions import InvalidToken
198
import secrets
199
200
class HOTPAuthenticator:
201
def __init__(self, key: bytes, initial_counter: int = 0):
202
self.hotp = HOTP(key, 6, hashes.SHA1())
203
self.counter = initial_counter
204
205
def generate_next_token(self) -> str:
206
"""Generate next HOTP token and increment counter"""
207
token = self.hotp.generate(self.counter)
208
self.counter += 1
209
return token.decode()
210
211
def verify_token(self, token: str, window: int = 3) -> bool:
212
"""
213
Verify HOTP token within a counter window.
214
215
Args:
216
token: Token to verify
217
window: Look-ahead window for counter synchronization
218
219
Returns:
220
bool: True if token is valid
221
"""
222
token_bytes = token.encode()
223
224
# Try current counter and look-ahead window
225
for i in range(window + 1):
226
try:
227
self.hotp.verify(token_bytes, self.counter + i)
228
# Token valid, update counter
229
self.counter = self.counter + i + 1
230
return True
231
except InvalidToken:
232
continue
233
234
return False
235
236
# Usage
237
secret_key = secrets.token_bytes(20)
238
authenticator = HOTPAuthenticator(secret_key)
239
240
print("Generated HOTP tokens:")
241
for i in range(5):
242
token = authenticator.generate_next_token()
243
print(f"Token {i+1}: {token}")
244
245
# Simulate client/server verification
246
client_auth = HOTPAuthenticator(secret_key, 0) # Reset counter
247
server_auth = HOTPAuthenticator(secret_key, 0) # Reset counter
248
249
# Client generates token
250
client_token = client_auth.generate_next_token()
251
print(f"\nClient generated: {client_token}")
252
253
# Server verifies token
254
is_valid = server_auth.verify_token(client_token)
255
print(f"Server verification: {'SUCCESS' if is_valid else 'FAILED'}")
256
```
257
258
### Complete 2FA Authentication System
259
260
```python
261
from cryptography.hazmat.primitives.twofactor.totp import TOTP
262
from cryptography.hazmat.primitives import hashes
263
from cryptography.exceptions import InvalidToken
264
import time
265
import base64
266
import secrets
267
import json
268
269
class TwoFactorAuth:
270
def __init__(self):
271
self.users = {} # In production, use proper database
272
273
def enroll_user(self, username: str) -> dict:
274
"""Enroll user for 2FA"""
275
# Generate secret
276
secret_bytes = secrets.token_bytes(20)
277
secret_b32 = base64.b32encode(secret_bytes).decode()
278
279
# Store user data
280
self.users[username] = {
281
'secret': secret_b32,
282
'secret_bytes': secret_bytes,
283
'enrolled': True,
284
'backup_codes': self._generate_backup_codes()
285
}
286
287
# Return enrollment data
288
return {
289
'secret': secret_b32,
290
'qr_url': self._generate_qr_url(secret_b32, username),
291
'backup_codes': self.users[username]['backup_codes']
292
}
293
294
def verify_setup_token(self, username: str, token: str) -> bool:
295
"""Verify initial setup token"""
296
if username not in self.users:
297
return False
298
299
return self._verify_totp_token(username, token)
300
301
def authenticate(self, username: str, token: str) -> dict:
302
"""Authenticate user with 2FA token"""
303
if username not in self.users or not self.users[username]['enrolled']:
304
return {'success': False, 'reason': 'User not enrolled'}
305
306
# Check if it's a backup code
307
if token in self.users[username]['backup_codes']:
308
# Remove used backup code
309
self.users[username]['backup_codes'].remove(token)
310
return {'success': True, 'method': 'backup_code'}
311
312
# Verify TOTP token with time window tolerance
313
if self._verify_totp_token_with_window(username, token):
314
return {'success': True, 'method': 'totp'}
315
316
return {'success': False, 'reason': 'Invalid token'}
317
318
def _verify_totp_token(self, username: str, token: str) -> bool:
319
"""Verify TOTP token for exact current time"""
320
try:
321
secret_bytes = self.users[username]['secret_bytes']
322
totp = TOTP(secret_bytes, 6, hashes.SHA1(), 30)
323
324
current_time = int(time.time())
325
totp.verify(token.encode(), current_time)
326
return True
327
except (InvalidToken, KeyError):
328
return False
329
330
def _verify_totp_token_with_window(self, username: str, token: str, window: int = 1) -> bool:
331
"""Verify TOTP token with time window tolerance"""
332
try:
333
secret_bytes = self.users[username]['secret_bytes']
334
totp = TOTP(secret_bytes, 6, hashes.SHA1(), 30)
335
336
current_time = int(time.time())
337
338
# Try current time and adjacent time windows
339
for offset in range(-window, window + 1):
340
try:
341
test_time = current_time + (offset * 30)
342
totp.verify(token.encode(), test_time)
343
return True
344
except InvalidToken:
345
continue
346
347
return False
348
except KeyError:
349
return False
350
351
def _generate_backup_codes(self, count: int = 10) -> list:
352
"""Generate backup codes for recovery"""
353
codes = []
354
for _ in range(count):
355
code = secrets.token_hex(4).upper() # 8-character hex codes
356
codes.append(code)
357
return codes
358
359
def _generate_qr_url(self, secret: str, username: str) -> str:
360
"""Generate QR code URL"""
361
import urllib.parse
362
363
params = {
364
'secret': secret,
365
'issuer': 'MyApp',
366
'algorithm': 'SHA1',
367
'digits': '6',
368
'period': '30'
369
}
370
371
query_string = urllib.parse.urlencode(params)
372
label = f"MyApp:{username}"
373
return f"otpauth://totp/{urllib.parse.quote(label)}?{query_string}"
374
375
# Usage Example
376
auth_system = TwoFactorAuth()
377
378
# Enroll user
379
enrollment_data = auth_system.enroll_user("alice@example.com")
380
print("Enrollment data:")
381
print(f"Secret: {enrollment_data['secret']}")
382
print(f"QR URL: {enrollment_data['qr_url']}")
383
print(f"Backup codes: {enrollment_data['backup_codes']}")
384
385
# Simulate user setting up authenticator app and generating token
386
# For testing, we'll generate the expected token
387
secret_bytes = base64.b32decode(enrollment_data['secret'])
388
totp = TOTP(secret_bytes, 6, hashes.SHA1(), 30)
389
current_token = totp.generate(int(time.time())).decode()
390
391
print(f"\nCurrent expected token: {current_token}")
392
393
# Verify setup
394
setup_verified = auth_system.verify_setup_token("alice@example.com", current_token)
395
print(f"Setup verification: {'SUCCESS' if setup_verified else 'FAILED'}")
396
397
# Test authentication
398
auth_result = auth_system.authenticate("alice@example.com", current_token)
399
print(f"Authentication result: {auth_result}")
400
401
# Test backup code
402
backup_code = enrollment_data['backup_codes'][0]
403
backup_result = auth_system.authenticate("alice@example.com", backup_code)
404
print(f"Backup code result: {backup_result}")
405
406
# Test backup code reuse (should fail)
407
backup_reuse = auth_system.authenticate("alice@example.com", backup_code)
408
print(f"Backup code reuse: {backup_reuse}")
409
```
410
411
## Security Considerations
412
413
- **Secret Storage**: Store TOTP secrets securely (encrypted in database)
414
- **Time Synchronization**: Ensure server time is accurate (NTP)
415
- **Window Tolerance**: Allow small time window (±30 seconds) for clock skew
416
- **Rate Limiting**: Implement rate limiting for OTP verification attempts
417
- **Backup Codes**: Provide backup codes for account recovery
418
- **Secret Generation**: Use cryptographically secure random number generator
419
- **Algorithm Choice**: SHA-1 most compatible, SHA-256 for higher security
420
- **Counter Management**: For HOTP, implement proper counter synchronization
421
- **Replay Prevention**: Track recently used tokens to prevent replay attacks