Fix AES-GCM counter carry corrupting the keystream on dart2wasm - #29
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests.
|
The GCM block counter lives in a Uint8List view over a Uint32List buffer. dart2wasm does not apply the modulo-256 truncation when such a view is assigned an out-of-range value, so `counter[15]++` let the carry bleed into the adjacent byte: 0000FFFF incremented to 00010001 instead of 00010000. Past that first 32-bit carry, at ~1 MiB, the keystream desynchronised from every other platform, silently corrupting cross-platform ciphertext. Mask every stored counter byte explicitly, pin inc32 against raw AES-ECB at each byte-carry boundary, add an end-to-end check across the 1 MiB boundary, and run the dart2wasm suite on every push rather than only at release time. Fixes #28
A mis-carrying block counter still round-trips perfectly, so neither the size sweeps in correctness_test.dart nor a decrypt-of-encrypt check can see it. Assert instead that block j of a run started at counter C equals block 0 of a run started at C + j. The starting counter is an input, so this reaches the 32- and 64-bit carries that no achievable message length could walk to. Covers AES/Twofish/Blowfish CTR, ChaCha20 with both counter widths, XChaCha20, Salsa20 and XSalsa20, plus a 1 MiB run per cipher for large-index output addressing. Verified by mutation: breaking the carry in ctr.dart or chacha20.dart fails exactly the affected subjects.
|
@dipu-bd do you think that the benchmark table needs any update after that? |
Member
Author
|
Benchmark will not be affected in a major way, ±5 Mbps at most. I'll do it later. |
|
@dipu-bd, just confirming that it's working in production in a WASM-based PWA. Thanks for the fix. Best regards. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The GCM block counter lives in a Uint8List view over a Uint32List buffer. dart2wasm does not apply the modulo-256 truncation when such a view is assigned an out-of-range value, so
counter[15]++let the carry bleed into the adjacent byte: 0000FFFF incremented to 00010001 instead of 00010000. Past that first 32-bit carry, at ~1 MiB, the keystream desynchronised from every other platform, silently corrupting cross-platform ciphertext.Mask every stored counter byte explicitly, pin inc32 against raw AES-ECB at each byte-carry boundary, add an end-to-end check across the 1 MiB boundary, and run the dart2wasm suite on every push rather than only at release time.
Fixes #28