Skip to content

Comments

fix: prevent cipher reuse after final() and reset is_finalized in all init() overrides#948

Merged
boorad merged 2 commits intomainfrom
fix/cipher-reuse-after-final
Feb 17, 2026
Merged

fix: prevent cipher reuse after final() and reset is_finalized in all init() overrides#948
boorad merged 2 commits intomainfrom
fix/cipher-reuse-after-final

Conversation

@boorad
Copy link
Collaborator

@boorad boorad commented Feb 17, 2026

Summary

Fixes #945 — cipher behavior change from 0.7+ to 1+ where Buffer concat vs string concat produced different results.

Changes

Cipher reuse prevention (commit 1)

  • Added is_finalized flag to base HybridCipher class
  • Added checkNotFinalized() guard to update() and final() across all cipher subclasses
  • Removed duplicate final_called_/final_called flags from ChaCha20Poly1305Cipher, XChaCha20Poly1305Cipher, and XSalsa20Poly1305Cipher
  • Calling update() or final() after final() now throws "Unsupported state or unable to authenticate data", matching Node.js behavior
  • Added encoding roundtrip tests (Buffer concat vs string concat, hex encoding)
  • Added state violation tests (update-after-final, double-final)

Init reset fix (commit 2)

  • Added is_finalized = false to XSalsa20Cipher::init(), XChaCha20Poly1305Cipher::init(), and ChaCha20Poly1305Cipher::init() — these override init() without calling HybridCipher::init(), so the flag was never reset on re-initialization
  • Moved is_finalized = true inside the #else BLSALLOC_SODIUM block in XSalsa20Cipher::final() so state isn't corrupted on the error path
  • Added re-init after final test

Root Cause

The original issue report used the same cipher instance for both Buffer concat and string concat approaches. In 0.x this silently produced wrong results (different ciphertexts). The correct usage requires separate cipher instances, which now produce identical output.

- Add is_finalized flag to HybridCipher base class
- Guard update() and final() in all cipher subclasses
- Consolidate duplicate final_called/final_called_ flags into base class
- Reset is_finalized on init() for proper re-initialization
- Add tests for Buffer concat vs string concat equivalence
- Add tests for hex/base64 encoding roundtrips
- Add tests for cipher state violations (update after final, double final)
Add is_finalized = false to XSalsa20Cipher, XChaCha20Poly1305Cipher,
and ChaCha20Poly1305Cipher init() methods that bypass HybridCipher::init().
Move is_finalized = true inside #else block in XSalsa20Cipher::final()
so state isn't corrupted on the error path. Add re-init after final test.
@boorad boorad self-assigned this Feb 17, 2026
@github-actions
Copy link
Contributor

🤖 End-to-End Test Results - Android

Status: ✅ Passed
Platform: Android
Run: 22105980760

📸 Final Test Screenshot

Maestro Test Results - android

Screenshot automatically captured from End-to-End tests and will expire in 30 days


This comment is automatically updated on each test run.

@github-actions
Copy link
Contributor

🤖 End-to-End Test Results - iOS

Status: ✅ Passed
Platform: iOS
Run: 22105980805

📸 Final Test Screenshot

Maestro Test Results - ios

Screenshot automatically captured from End-to-End tests and will expire in 30 days


This comment is automatically updated on each test run.

@boorad boorad merged commit 2f1bdb2 into main Feb 17, 2026
7 checks passed
@boorad boorad deleted the fix/cipher-reuse-after-final branch February 17, 2026 16:34
boorad added a commit that referenced this pull request Feb 24, 2026
New pages: PQC (ML-DSA/ML-KEM), Argon2, KMAC, Certificate (SPKAC),
and Utilities (one-shot hash, timingSafeEqual, primes, introspection).

Updated 8 existing pages with missing API sections: SubtleCrypto
(deriveBits, deriveKey, wrapKey/unwrapKey, encapsulation), Keys
(KeyObject.from, equals, toCryptoKey), Signing (standalone sign/verify),
DiffieHellman (diffieHellman function), Ed25519 (Ed448/X448), Hash
(crypto.hash one-shot, SHA3), ECDH (convertKey), and reorganized the
API index into Core/Key Exchange/Key Derivation/Advanced sections.

Annotated 7 pages with behavioral notes from recent fix PRs (#929,
#930, #932, #933, #939, #948, #949, #951, #954, #955): cipher
single-use warning, generateKeys preservation, PBKDF2 validation,
OAEP hash default, randomFill view correctness, RSA-* aliases, and
flexible curve names.

Added llms.txt index route and fixed llms-full.txt JSX stripping in
source.ts to produce clean LLM-friendly output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Cipher behaviour change from 0.7+ to 1+

1 participant