or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

aead-ciphers.mdasymmetric-cryptography.mdhash-functions.mdindex.mdkey-derivation.mdkey-serialization.mdmessage-authentication.mdsymmetric-ciphers.mdsymmetric-encryption.mdtwo-factor-auth.mdutilities.mdx509-certificates.md

two-factor-auth.mddocs/

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