AES GCM从Python到C#移植问题:GHASH计算与Wireshark不匹配
Hey there, let's dig into this GHASH issue you're facing with your AES-GCM port from Python to C#. Since you've confirmed that GF_mult and standalone GHASH calculations match between the two languages, but the final auth_tag doesn't align with Wireshark (and your TLS handshake fails), here are targeted areas to investigate:
1. Validate GHASH Input Padding & Formatting
AES-GCM strictly defines the input to GHASH as:
- Padded AAD (auth_data): zero-padded to the next 128-bit (16-byte) boundary
- Padded ciphertext: zero-padded to the next 128-bit boundary
- A 128-bit block containing the big-endian 64-bit length of AAD (in bits) concatenated with the big-endian 64-bit length of ciphertext (in bits)
Double-check your C# code for these common mistakes:
- Are you using zero-padding (not another padding scheme) for AAD and ciphertext?
- Are the length fields encoded as big-endian? C# uses little-endian by default for primitives, so you'll need to reverse bytes if
BitConverter.IsLittleEndianis true. - Are you including all parts of the input (no truncated blocks or extra added bytes)?
2. Verify GHASH Initialization Vector (X₀)
The initial GHASH state X₀ is derived from the GCM nonce (standard 96-bit nonce in TLS):X₀ = nonce || 0x00000000 || 0x00000001 (all in big-endian)
Check these details in your code:
- Is
X₀constructed correctly, especially the trailing 64-bit value (0x0000000000000001as big-endian bytes)? - Are you treating the nonce as big-endian when building
X₀(no byte reversal errors)?
3. Confirm Block Processing Order & Logic
GHASH processes each 128-bit block sequentially with this formula:Xᵢ = (Xᵢ₋₁ XOR blockᵢ) * H (where * is GF(2¹²⁸) multiplication via your GF_mult function)
Even if GF_mult works, verify:
- Are you processing AAD blocks first, then ciphertext blocks, then the final length block (in that exact order)?
- Are you applying the XOR with the previous state before multiplying by H (not the reverse)?
- Are you skipping any blocks in the sequence?
4. Check Final Auth Tag Derivation
The auth tag is calculated as:auth_tag = Xₙ XOR X₀ (where Xₙ is the final GHASH state after all blocks are processed)
Ensure:
- You're XORing the final GHASH result with the original
X₀(not a modified version) - You're truncating the tag to the correct length (TLS typically uses 16-byte tags; avoid accidental shorter lengths)
- There's no byte order mismatch in the final XOR step
5. Enforce Endianness Consistency
Endianness is a frequent culprit in crypto porting:
- Always explicitly handle big-endian encoding when converting between byte arrays and numeric values (like length fields or GHASH state blocks)
- For example, when converting a 64-bit length to bytes, use
BitConverter.GetBytes(length).Reverse()if running on a little-endian system
6. Test with NIST Standard Vectors
Use official AES-GCM test vectors from NIST SP 800-38D to validate your full pipeline:
- Pick a vector with known nonce, AAD, plaintext, key, expected ciphertext, and auth tag
- Run your C# code against it: if the tag matches, the issue is TLS-specific (e.g., incorrect AAD formatting for TLS records). If not, compare intermediate GHASH states with your Python code to pinpoint the failing step
Quick Sanity Check
Since your standalone GHASH tests pass, have you tested with the exact input bytes Wireshark shows (including padded AAD, ciphertext, and length block)? It's possible your test case doesn't replicate the real TLS handshake input.
内容的提问来源于stack exchange,提问作者frenzy man

