A Python port of the Invisible Internet Project (I2P)
1# Crypto Implementation Audit Checklist
2
3_Last audited: 2026-03-23_
4_Auditor: Claude (automated) + manual review_
5
6---
7
8## 1. Constant-Time Comparisons
9
10**Target files**: `i2p_crypto/chacha20.py`, `i2p_crypto/aes_cbc.py`, `i2p_crypto/hmac_generator.py`, `i2p_crypto/eddsa.py`, `i2p_crypto/dsa.py`, `i2p_crypto/noise.py`, `i2p_crypto/garlic_crypto.py`, `i2p_data/i2np_codec.py`
11
12**Check**: Every MAC/tag/signature/hash verification uses `hmac.compare_digest()`.
13
14| Location | Pattern | Status |
15|----------|---------|--------|
16| `hmac_generator.py:63` | `hmac.compare_digest()` | PASS |
17| `password.py:31` | `secrets.compare_digest()` | PASS |
18| `console/auth.py:28` | `secrets.compare_digest()` | PASS |
19| `garlic_crypto.py:257` | `hmac.compare_digest()` on SHA-256 hash | PASS (fixed 2026-03-23) |
20| `i2np_codec.py:80` | `hmac.compare_digest()` on checksum byte | PASS (fixed 2026-03-23) |
21| `dsa.py`, `eddsa.py` | Delegated to `cryptography` library verify() | PASS |
22| `noise.py` CipherState | Delegated to `ChaCha20Poly1305.decrypt()` | PASS |
23
24**Result**: **PASS** -- All comparisons on security-sensitive data use constant-time functions.
25
26---
27
28## 2. Key Zeroization
29
30**Target files**: `i2p_crypto/x25519.py`, `i2p_crypto/noise.py`, `i2p_crypto/hkdf.py`, `i2p_data/session_key_manager.py`
31
32**Check**: Ephemeral keys stored in `bytearray`, explicitly zeroed after use.
33
34| Location | Key Type | Storage | Zeroed? | Status |
35|----------|----------|---------|---------|--------|
36| `noise.py:32` | CipherState key | `bytes` (immutable) | No | KNOWN LIMITATION |
37| `noise.py:162` | Ephemeral private key | `bytes` tuple | No | KNOWN LIMITATION |
38| `noise.py:360` | `_x25519_private` | `bytes` | No | KNOWN LIMITATION |
39| `hkdf.py:20-21` | Output buffers | `bytearray` | No explicit zeroing | ADVISORY |
40| `session_key_manager.py:103` | Session keys | `bytes` | No | KNOWN LIMITATION |
41
42**Result**: **ADVISORY** -- Python's immutable `bytes` type makes key zeroization impossible without switching to `bytearray`. This is a language-level limitation. The `cryptography` library handles its own key zeroization in C. For pure-Python key material, use `bytearray` with explicit zeroing in `finally` blocks.
43
44**Note**: Key zeroization in Python is best-effort. The garbage collector may retain copies, and `bytearray` zeroing can be optimized away. For production deployments, rely on the `cryptography` library's C-level key handling.
45
46---
47
48## 3. CSPRNG Usage
49
50**Target files**: `i2p_crypto/`, `i2p_transport/`
51
52**Check**: All randomness sourced from `os.urandom()` or `secrets.*`.
53
54| Location | Function | Source | Status |
55|----------|----------|--------|--------|
56| `elgamal.py:38-41` | Key generation | `os.urandom()` | PASS |
57| `garlic_crypto.py:19-20` | Padding/tags | `os.urandom()` | PASS |
58| `ssu2_handshake.py` | Connection IDs | `os.urandom(8)` | PASS |
59| `ssu2_server.py` | Nonces | `os.urandom(4)` | PASS |
60| `ntcp2_real_handshake.py` | Padding | `os.urandom()` | PASS |
61| `ssu2_connection.py:194` | Challenge data | `os.urandom(8)` | PASS |
62
63**Result**: **PASS** -- Zero uses of `random.*` in crypto or transport modules.
64
65---
66
67## 4. No Secrets in Logs
68
69**Target**: All files in `i2p_crypto/`, `i2p_transport/`, `i2p_data/`
70
71**Check**: No `logging.*`, `print()`, `repr()` calls that include key material.
72
73| Location | Log Content | Status |
74|----------|-------------|--------|
75| `ssu2_server.py` | Addresses, packet sizes | PASS |
76| `manager.py` | Peer hash prefix (first 8 bytes only) | PASS |
77| `upnp.py` | URLs only | PASS |
78
79**Result**: **PASS** -- No key material found in any logging statements.
80
81---
82
83## 5. Nonce Management
84
85**Target files**: NTCP2 and SSU2 frame encryption in `i2p_transport/`
86
87**Check**: Nonce counters increment monotonically, overflow detection, separate nonce spaces.
88
89| Check | Location | Status |
90|-------|----------|--------|
91| Monotonic increment (Noise) | `noise.py:51,60` | PASS -- `_n += 1` after each op |
92| Overflow detection (Noise) | `noise.py:48,57` | PASS (fixed 2026-03-23) -- checks `_n >= MAX_NONCE` |
93| Overflow detection (SSU2) | `ssu2_connection.py:119` | PASS (fixed 2026-03-23) -- checks `>= 0xFFFFFFFF` |
94| Separate nonce spaces | `ssu2_connection.py:65-67` | PASS -- send/recv use separate CipherState |
95| Nonce construction (SSU2) | `ssu2_connection.py:105-107` | PASS -- 12-byte nonce from packet number |
96
97**Result**: **PASS** -- All nonce management issues addressed.
98
99---
100
101## 6. AEAD Correctness
102
103**Target files**: `i2p_crypto/chacha20.py`, NTCP2/SSU2 frame processing
104
105**Check**: AD parameter includes header bytes; decryption returns no partial plaintext on auth failure.
106
107| Check | Location | Status |
108|-------|----------|--------|
109| AD binding (SSU2) | `ssu2_connection.py:127,170` | PASS -- header bound as AD |
110| AD binding (NTCP2) | `ntcp2_real_connection.py:81,103` | PASS -- empty AD (per NTCP2 data phase spec) |
111| Atomic decryption failure | `cryptography` library | PASS -- `InvalidTag` raised, no partial plaintext |
112| Nonce increment on failure | `noise.py:60` | PASS -- `_n += 1` only reached after successful decrypt |
113
114**Result**: **PASS** -- AEAD semantics correct.
115
116---
117
118## 7. HKDF Usage
119
120**Target**: `i2p_crypto/hkdf.py`, `i2p_crypto/noise.py`
121
122**Check**: Two-phase (Extract + Expand) usage; info parameters are protocol-specific strings.
123
124| Check | Location | Status |
125|-------|----------|--------|
126| Extract + Expand | `hkdf.py` | PASS -- separate `extract()` and `expand()` functions |
127| Protocol-specific info | `noise.py` MixKey/MixHash | PASS -- chaining key derived per Noise spec |
128| Output length check | `hkdf.py` | PASS -- `expand()` output <= 255 * HashLen |
129
130**Result**: **PASS**
131
132---
133
134## 8. Integer Overflow Masking
135
136**Target**: `i2p_crypto/siphash.py`, nonce counters
137
138**Check**: All arithmetic ported from Java applies explicit masks.
139
140| Check | Location | Status |
141|-------|----------|--------|
142| 64-bit mask in SipHash | `siphash.py:9,14,66,75,79` | PASS -- `& MASK64` on all operations |
143| IV masking | `siphash.py:98` | PASS -- `iv & MASK64` on init |
144| SipHash return | `siphash.py:61` | PASS -- `(v0 ^ v1 ^ v2 ^ v3) & MASK64` |
145
146**Result**: **PASS** -- All Java overflow semantics correctly ported with explicit masks.
147
148---
149
150## 9. String Encoding
151
152**Target**: RouterInfo properties, address book, SU3 metadata
153
154**Check**: All string-to-bytes conversions use explicit `.encode('utf-8')`.
155
156| Check | Status |
157|-------|--------|
158| RouterInfo encoding | PASS -- explicit UTF-8 in data serialization |
159| Password hashing | PASS -- `password.encode("utf-8")` |
160| Console auth | PASS -- explicit UTF-8 encoding |
161
162**Result**: **PASS**
163
164---
165
166## Summary
167
168| Section | Result | Fixes Applied |
169|---------|--------|---------------|
170| 1. Constant-time comparisons | PASS | 2 fixes (garlic_crypto, i2np_codec) |
171| 2. Key zeroization | ADVISORY | Language limitation documented |
172| 3. CSPRNG usage | PASS | None needed |
173| 4. No secrets in logs | PASS | None needed |
174| 5. Nonce management | PASS | 2 fixes (noise overflow, SSU2 overflow) |
175| 6. AEAD correctness | PASS | None needed |
176| 7. HKDF usage | PASS | None needed |
177| 8. Integer overflow masking | PASS | None needed |
178| 9. String encoding | PASS | None needed |
179
180**Overall**: 8/9 PASS, 1 ADVISORY (key zeroization -- Python language limitation)
181**Fixes applied**: 4 security hardening changes in this audit cycle