0
# SRP Authentication Protocol
1
2
Low-level Secure Remote Password (SRP) protocol implementation for AWS Cognito User Pools. SRP provides cryptographically secure authentication without transmitting passwords over the network, forming the foundation for secure user authentication in pycognito.
3
4
## Capabilities
5
6
### AWSSRP Class
7
8
The core SRP implementation that handles the complete SRP authentication flow with AWS Cognito.
9
10
```python { .api }
11
class AWSSRP:
12
"""AWS SRP authentication protocol implementation."""
13
14
# Challenge type constants
15
SMS_MFA_CHALLENGE = "SMS_MFA"
16
SOFTWARE_TOKEN_MFA_CHALLENGE = "SOFTWARE_TOKEN_MFA"
17
NEW_PASSWORD_REQUIRED_CHALLENGE = "NEW_PASSWORD_REQUIRED"
18
PASSWORD_VERIFIER_CHALLENGE = "PASSWORD_VERIFIER"
19
DEVICE_SRP_CHALLENGE = "DEVICE_SRP_AUTH"
20
DEVICE_PASSWORD_VERIFIER_CHALLENGE = "DEVICE_PASSWORD_VERIFIER"
21
22
def __init__(self, username: str, password: str, pool_id: str, client_id: str,
23
pool_region: str = None, client=None, client_secret: str = None,
24
device_key: str = None, device_group_key: str = None,
25
device_password: str = None):
26
"""
27
Initialize AWSSRP authentication client.
28
29
Args:
30
username (str): User's username
31
password (str): User's password
32
pool_id (str): Cognito User Pool ID
33
client_id (str): Cognito Client ID
34
pool_region (str, optional): AWS region (extracted from pool_id if not provided)
35
client (boto3.client, optional): Pre-configured boto3 cognito-idp client
36
client_secret (str, optional): Client secret if app client requires it
37
device_key (str, optional): Device key for device authentication
38
device_group_key (str, optional): Device group key for device authentication
39
device_password (str, optional): Device password for device authentication
40
41
Raises:
42
ValueError: If pool_region and client are both specified, or if device parameters are partially provided
43
44
Note:
45
Device authentication requires all three device parameters or none at all.
46
"""
47
```
48
49
**Usage Example:**
50
51
```python
52
from pycognito.aws_srp import AWSSRP
53
54
# Basic SRP authentication
55
aws_srp = AWSSRP(
56
username='user@example.com',
57
password='user-password',
58
pool_id='us-east-1_example123',
59
client_id='your-client-id'
60
)
61
62
# With client secret
63
aws_srp = AWSSRP(
64
username='user@example.com',
65
password='user-password',
66
pool_id='us-east-1_example123',
67
client_id='your-client-id',
68
client_secret='your-client-secret'
69
)
70
71
# With device authentication
72
aws_srp = AWSSRP(
73
username='user@example.com',
74
password='user-password',
75
pool_id='us-east-1_example123',
76
client_id='your-client-id',
77
device_key='device-key-123',
78
device_group_key='device-group-456',
79
device_password='device-password-789'
80
)
81
```
82
83
### SRP Authentication Flow
84
85
Execute the complete SRP authentication flow with challenge handling.
86
87
```python { .api }
88
def authenticate_user(self, client=None, client_metadata: dict = None) -> dict:
89
"""
90
Authenticate user using SRP protocol.
91
92
Args:
93
client (boto3.client, optional): Override the default boto3 client
94
client_metadata (dict, optional): Custom workflow metadata
95
96
Returns:
97
dict: Authentication result with tokens
98
{
99
'AuthenticationResult': {
100
'AccessToken': str,
101
'IdToken': str,
102
'RefreshToken': str,
103
'TokenType': str,
104
'ExpiresIn': int
105
},
106
'ChallengeName': str, # If additional challenges required
107
'Session': str # Challenge session token
108
}
109
110
Raises:
111
ForceChangePasswordException: When password change is required
112
SoftwareTokenMFAChallengeException: When TOTP MFA is required
113
SMSMFAChallengeException: When SMS MFA is required
114
NotImplementedError: For unsupported challenge types
115
116
Flow:
117
1. Initiate SRP authentication with public key
118
2. Process password verifier challenge
119
3. Handle device challenges if device keys provided
120
4. Return tokens or raise MFA/password challenges
121
"""
122
```
123
124
**Usage Example:**
125
126
```python
127
from pycognito.exceptions import (
128
ForceChangePasswordException,
129
SoftwareTokenMFAChallengeException,
130
SMSMFAChallengeException
131
)
132
133
try:
134
# Perform SRP authentication
135
tokens = aws_srp.authenticate_user()
136
137
# Success - tokens contain access, ID, and refresh tokens
138
print("Authentication successful!")
139
print(f"Access Token: {tokens['AuthenticationResult']['AccessToken']}")
140
141
except ForceChangePasswordException:
142
print("Password change required")
143
# Handle new password challenge
144
145
except SoftwareTokenMFAChallengeException as e:
146
print("TOTP MFA required")
147
# Handle software token MFA
148
mfa_tokens = e.get_tokens()
149
150
except SMSMFAChallengeException as e:
151
print("SMS MFA required")
152
# Handle SMS MFA
153
mfa_tokens = e.get_tokens()
154
```
155
156
### New Password Challenge
157
158
Handle the new password required challenge when users must set a new password.
159
160
```python { .api }
161
def set_new_password_challenge(self, new_password: str, client=None) -> dict:
162
"""
163
Handle new password required challenge.
164
165
Args:
166
new_password (str): New password to set
167
client (boto3.client, optional): Override the default boto3 client
168
169
Returns:
170
dict: Authentication result after password is set
171
172
Use Cases:
173
- User logging in with temporary password (admin created users)
174
- Password expiration policies requiring new password
175
- Security policies forcing password updates
176
177
Flow:
178
1. Initiate SRP authentication
179
2. Process password verifier challenge
180
3. Handle NEW_PASSWORD_REQUIRED challenge with new password
181
4. Return final authentication tokens
182
"""
183
```
184
185
**Usage Example:**
186
187
```python
188
# Handle new password requirement
189
try:
190
tokens = aws_srp.authenticate_user()
191
except ForceChangePasswordException:
192
print("Setting new password...")
193
194
new_password = input("Enter new password: ")
195
tokens = aws_srp.set_new_password_challenge(new_password)
196
197
print("New password set successfully!")
198
print(f"Access Token: {tokens['AuthenticationResult']['AccessToken']}")
199
```
200
201
### Authentication Parameters
202
203
Generate authentication parameters for SRP initiation.
204
205
```python { .api }
206
def get_auth_params(self) -> dict:
207
"""
208
Get authentication parameters for SRP initiation.
209
210
Returns:
211
dict: Parameters for initiate_auth call
212
{
213
'USERNAME': str,
214
'SRP_A': str, # Public key for SRP
215
'SECRET_HASH': str, # If client secret is configured
216
'DEVICE_KEY': str # If device authentication enabled
217
}
218
219
Note:
220
These parameters are used internally but can be useful for
221
custom authentication flows or debugging.
222
"""
223
```
224
225
### Challenge Processing
226
227
Process SRP authentication challenges with password verification.
228
229
```python { .api }
230
def process_challenge(self, challenge_parameters: dict, request_parameters: dict) -> dict:
231
"""
232
Process password verifier challenge.
233
234
Args:
235
challenge_parameters (dict): Challenge data from AWS Cognito
236
request_parameters (dict): Original request parameters
237
238
Returns:
239
dict: Challenge response parameters
240
{
241
'TIMESTAMP': str,
242
'USERNAME': str,
243
'PASSWORD_CLAIM_SECRET_BLOCK': str,
244
'PASSWORD_CLAIM_SIGNATURE': str,
245
'SECRET_HASH': str, # If client secret configured
246
'DEVICE_KEY': str # If device authentication enabled
247
}
248
249
Internal Flow:
250
1. Extract challenge parameters (USER_ID_FOR_SRP, SALT, SRP_B, SECRET_BLOCK)
251
2. Compute password authentication key using SRP protocol
252
3. Generate HMAC signature with secret block and timestamp
253
4. Return challenge response for AWS verification
254
"""
255
256
def process_device_challenge(self, challenge_parameters: dict) -> dict:
257
"""
258
Process device password verifier challenge.
259
260
Args:
261
challenge_parameters (dict): Device challenge data from AWS Cognito
262
263
Returns:
264
dict: Device challenge response parameters
265
266
Note:
267
Similar to process_challenge but uses device-specific credentials
268
and authentication keys for device verification.
269
"""
270
```
271
272
### Cryptographic Utilities
273
274
Static methods for SRP cryptographic operations.
275
276
```python { .api }
277
@staticmethod
278
def get_secret_hash(username: str, client_id: str, client_secret: str) -> str:
279
"""
280
Calculate secret hash for client secret authentication.
281
282
Args:
283
username (str): Username
284
client_id (str): Cognito client ID
285
client_secret (str): Cognito client secret
286
287
Returns:
288
str: Base64-encoded HMAC-SHA256 hash
289
290
Formula:
291
HMAC-SHA256(client_secret, username + client_id)
292
"""
293
294
@staticmethod
295
def get_cognito_formatted_timestamp(input_datetime: datetime) -> str:
296
"""
297
Format datetime for Cognito authentication.
298
299
Args:
300
input_datetime (datetime): Datetime to format
301
302
Returns:
303
str: Formatted timestamp string (e.g., "Mon Jan 2 15:04:05 UTC 2006")
304
305
Note:
306
Uses specific format required by AWS Cognito SRP protocol.
307
"""
308
```
309
310
**Usage Example:**
311
312
```python
313
from datetime import datetime
314
315
# Calculate secret hash
316
secret_hash = AWSSRP.get_secret_hash(
317
username='user@example.com',
318
client_id='your-client-id',
319
client_secret='your-client-secret'
320
)
321
322
# Format timestamp for Cognito
323
timestamp = AWSSRP.get_cognito_formatted_timestamp(datetime.utcnow())
324
print(f"Cognito timestamp: {timestamp}")
325
```
326
327
## SRP Cryptographic Functions
328
329
Low-level cryptographic functions that implement the SRP protocol mathematics.
330
331
```python { .api }
332
def hash_sha256(buf: bytes) -> str:
333
"""Hash buffer using SHA256 and return hex digest."""
334
335
def hex_hash(hex_string: str) -> str:
336
"""Hash hex string using SHA256."""
337
338
def hex_to_long(hex_string: str) -> int:
339
"""Convert hex string to long integer."""
340
341
def long_to_hex(long_num: int) -> str:
342
"""Convert long integer to hex string."""
343
344
def get_random(nbytes: int) -> int:
345
"""Generate random long integer from n bytes."""
346
347
def pad_hex(long_int: int | str) -> str:
348
"""Pad hex string for consistent hashing."""
349
350
def compute_hkdf(ikm: bytes, salt: bytes) -> bytes:
351
"""Compute HKDF (HMAC-based Key Derivation Function)."""
352
353
def calculate_u(big_a: int, big_b: int) -> int:
354
"""Calculate SRP U value from client and server public keys."""
355
356
def generate_hash_device(device_group_key: str, device_key: str) -> tuple:
357
"""Generate device hash and verifier for device authentication."""
358
```
359
360
## Usage Patterns
361
362
### Direct SRP Authentication
363
364
```python
365
def direct_srp_authentication(username, password, pool_id, client_id):
366
"""Direct SRP authentication without Cognito wrapper."""
367
368
# Initialize SRP client
369
aws_srp = AWSSRP(
370
username=username,
371
password=password,
372
pool_id=pool_id,
373
client_id=client_id
374
)
375
376
try:
377
# Perform authentication
378
tokens = aws_srp.authenticate_user()
379
380
# Extract tokens
381
auth_result = tokens['AuthenticationResult']
382
access_token = auth_result['AccessToken']
383
id_token = auth_result['IdToken']
384
refresh_token = auth_result.get('RefreshToken')
385
386
print("SRP Authentication successful!")
387
return {
388
'access_token': access_token,
389
'id_token': id_token,
390
'refresh_token': refresh_token
391
}
392
393
except Exception as e:
394
print(f"SRP Authentication failed: {e}")
395
return None
396
397
# Usage
398
tokens = direct_srp_authentication(
399
'user@example.com',
400
'password',
401
'us-east-1_example123',
402
'your-client-id'
403
)
404
```
405
406
### Custom SRP Flow with Challenge Handling
407
408
```python
409
def custom_srp_flow_with_challenges(username, password, pool_id, client_id):
410
"""Custom SRP flow with manual challenge handling."""
411
412
aws_srp = AWSSRP(username, password, pool_id, client_id)
413
414
# Step 1: Get authentication parameters
415
auth_params = aws_srp.get_auth_params()
416
print(f"SRP_A (public key): {auth_params['SRP_A']}")
417
418
# Step 2: Initiate auth with AWS
419
response = aws_srp.client.initiate_auth(
420
AuthFlow='USER_SRP_AUTH',
421
AuthParameters=auth_params,
422
ClientId=client_id
423
)
424
425
# Step 3: Handle password verifier challenge
426
if response['ChallengeName'] == 'PASSWORD_VERIFIER':
427
print("Processing password verifier challenge...")
428
429
# Process challenge
430
challenge_response = aws_srp.process_challenge(
431
response['ChallengeParameters'],
432
auth_params
433
)
434
435
# Respond to challenge
436
tokens = aws_srp.client.respond_to_auth_challenge(
437
ClientId=client_id,
438
ChallengeName='PASSWORD_VERIFIER',
439
ChallengeResponses=challenge_response
440
)
441
442
# Check for additional challenges
443
if 'ChallengeName' in tokens:
444
print(f"Additional challenge required: {tokens['ChallengeName']}")
445
return tokens # Return for further processing
446
else:
447
print("Authentication completed!")
448
return tokens['AuthenticationResult']
449
450
else:
451
print(f"Unexpected challenge: {response['ChallengeName']}")
452
return None
453
454
# Usage
455
result = custom_srp_flow_with_challenges(
456
'user@example.com',
457
'password',
458
'us-east-1_example123',
459
'your-client-id'
460
)
461
```
462
463
### SRP with Custom Boto3 Configuration
464
465
```python
466
def srp_with_custom_boto3():
467
"""SRP authentication with custom boto3 configuration."""
468
469
import boto3
470
from botocore.config import Config
471
472
# Custom boto3 configuration
473
config = Config(
474
region_name='us-east-1',
475
retries={'max_attempts': 3},
476
read_timeout=30,
477
connect_timeout=10
478
)
479
480
# Create custom cognito client
481
cognito_client = boto3.client('cognito-idp', config=config)
482
483
# Initialize SRP with custom client
484
aws_srp = AWSSRP(
485
username='user@example.com',
486
password='password',
487
pool_id='us-east-1_example123',
488
client_id='your-client-id',
489
client=cognito_client # Use custom client
490
)
491
492
# Authenticate
493
tokens = aws_srp.authenticate_user()
494
return tokens
495
496
# Usage
497
tokens = srp_with_custom_boto3()
498
```
499
500
### SRP Protocol Mathematics Example
501
502
```python
503
def demonstrate_srp_mathematics():
504
"""Demonstrate the mathematical operations in SRP protocol."""
505
506
from pycognito.aws_srp import (
507
hex_to_long, long_to_hex, pad_hex,
508
calculate_u, compute_hkdf, N_HEX, G_HEX
509
)
510
511
# SRP protocol constants
512
N = hex_to_long(N_HEX) # Large prime number
513
g = hex_to_long(G_HEX) # Generator (2)
514
515
print(f"SRP Prime N: {N}")
516
print(f"SRP Generator g: {g}")
517
518
# Simulate SRP key generation
519
a = 12345 # Client private key (normally random)
520
A = pow(g, a, N) # Client public key: g^a mod N
521
522
print(f"Client private key a: {a}")
523
print(f"Client public key A: {A}")
524
print(f"A (hex): {long_to_hex(A)}")
525
print(f"A (padded): {pad_hex(A)}")
526
527
# Simulate server public key
528
B = 67890 # Server public key (normally calculated)
529
530
# Calculate U value (hash of A and B)
531
u = calculate_u(A, B)
532
print(f"U value: {u}")
533
534
# Demonstrate HKDF
535
ikm = b"input key material"
536
salt = b"salt value"
537
derived_key = compute_hkdf(ikm, salt)
538
print(f"HKDF result: {derived_key.hex()}")
539
540
# Usage
541
demonstrate_srp_mathematics()
542
```
543
544
### Production SRP Wrapper
545
546
```python
547
class ProductionSRPClient:
548
"""Production-ready SRP client with error handling and logging."""
549
550
def __init__(self, pool_id, client_id, client_secret=None):
551
self.pool_id = pool_id
552
self.client_id = client_id
553
self.client_secret = client_secret
554
555
def authenticate(self, username, password, retry_count=3):
556
"""Authenticate with retry logic and comprehensive error handling."""
557
558
for attempt in range(retry_count):
559
try:
560
print(f"Authentication attempt {attempt + 1}")
561
562
aws_srp = AWSSRP(
563
username=username,
564
password=password,
565
pool_id=self.pool_id,
566
client_id=self.client_id,
567
client_secret=self.client_secret
568
)
569
570
tokens = aws_srp.authenticate_user()
571
572
print("Authentication successful!")
573
return {
574
'success': True,
575
'tokens': tokens,
576
'attempt': attempt + 1
577
}
578
579
except ForceChangePasswordException:
580
return {
581
'success': False,
582
'error': 'PASSWORD_CHANGE_REQUIRED',
583
'message': 'User must change password'
584
}
585
586
except SoftwareTokenMFAChallengeException as e:
587
return {
588
'success': False,
589
'error': 'MFA_REQUIRED_TOTP',
590
'mfa_tokens': e.get_tokens()
591
}
592
593
except SMSMFAChallengeException as e:
594
return {
595
'success': False,
596
'error': 'MFA_REQUIRED_SMS',
597
'mfa_tokens': e.get_tokens()
598
}
599
600
except Exception as e:
601
print(f"Attempt {attempt + 1} failed: {e}")
602
if attempt == retry_count - 1:
603
return {
604
'success': False,
605
'error': 'AUTHENTICATION_FAILED',
606
'message': str(e)
607
}
608
609
return {
610
'success': False,
611
'error': 'MAX_RETRIES_EXCEEDED'
612
}
613
614
# Usage
615
srp_client = ProductionSRPClient(
616
pool_id='us-east-1_example123',
617
client_id='your-client-id',
618
client_secret='your-client-secret'
619
)
620
621
result = srp_client.authenticate('user@example.com', 'password')
622
623
if result['success']:
624
print("Authentication successful!")
625
tokens = result['tokens']
626
else:
627
print(f"Authentication failed: {result['error']}")
628
if 'mfa_tokens' in result:
629
print("MFA required - handle accordingly")
630
```