Repository navigation
CCM, KCCM, KGCM, EAX, OCB: refuse a second encryption without a new init - #2562
AlexLaroche wants to merge 1 commit into
Conversation
… same key and nonce without a new init, as GCM and ChaCha20-Poly1305 already did, rather than reusing the keystream or nonce.
|
Please include a statement saying you reviewed the code. See CONTRIBUTING.md for details. |
Contribution statementUse of generative AI. I used Claude Code (Anthropic's coding agent, model Claude Opus) to help with this pull request. It helped find and reproduce the issue, write the change and its regression test, run the existing tests, and write this description. I chose what to submit and I am responsible for it. Review. I have reviewed every line of this change, including the tests and the release note, and I understand what it does and why. I checked that the new test fails on Representations under CONTRIBUTING.md. By submitting this pull request I make the representations in the project's contributing guidelines, including the following:
|
Summary
After an encryption completes,
CCMBlockCipher,KCCMBlockCipher,KGCMBlockCipher,EAXBlockCipherandOCBBlockCipherencrypt a further message on the same instance under the same key and nonce if they are used again withoutinit, either directly or afterreset().The init-time guards ("cannot reuse nonce for ... encryption") don't cover this, because no new
inithappens.GCMBlockCipherandChaCha20Poly1305already refuse it ("... cannot be reused for encryption").Reproduced on
main(a1bb4ad): CCM, KCCM, KGCM and EAX give the same keystream for the second message, and OCB accepts it. bcprov 1.86 behaves the same for CCM.Change
Each of the five records when an encryption has used the current key and nonce, and only
initclears that record. A further encryption throwsIllegalStateException("<mode> cipher cannot be reused for encryption"), the same message pattern as GCM.processPacket/doFinal. The nonce counts as used once an encryption gets past its output-length check, so a too-small buffer can still be retried.reset()on its own does not use the nonce.processByte(s). The nonce is used by an encryptiondoFinal, or by areset()after message data has been processed. In OCB'sdoFinalthis happens on entry, since it writes the final block before checking room for the tag. Areset()before any message data still allows encryption.EAXBlockCiphergets the same change.Behaviour changes to note
Cipher.doFinal()resets the cipher for reuse with the same key and IV. ForAES/CCM,AES/EAX,AES/OCBand the DSTU 7624 KCCM/KGCM ciphers, a second encryption withoutinitnow throwsIllegalStateException. That is what GCM already does in BC and SunJCE.GMacover GCM,KGMacnow needs a freshinitbetween messages rather thanreset(). Reusing a GMAC nonce for a different message weakens the MAC anyway.initwith a fresh nonce per chunk, record or message, so they are not affected.Tests changed
Several existing tests encrypted twice under one key and nonce. They now expect the refusal and use a fresh
initor instance for the repeat:AEADTestUtil.testReset: the encryption half asserts the refusal; the decryption half is unchanged.AEADTestUtil.testBufferSizeChecks: the encryption half usesinitwith the existingvaryNoncehelper instead ofreset().CCMTest:bufferClearingTestand the twoprocessPacketoverloads use a fresh instance for the repeat.EAXTest: the repeat runs use fresh encryptors.OCBTest: the "(reused)" run now reuses only the decryptor.DSTU7624Test:doFinalTestnow expects the repeat to be refused.KGMacPartialBlockTestsre-initialises with a fresh IV, keeping its check that the result doesn't depend on what the instance processed before.KGCMNonceReuseTestsandKCCMNonceReuseTestspreviously asserted that "reset()-based reuse must still work". That is the reuse removed here, so they now assert the refusal. Please check this matches your intent.AEADTestexpects the refusal.BlockCipherTestalready took a freshCipherfor GCM before repeating an encryption. It now does the same for EAX, OCB and CCM through a smallnewEncryptorhelper that mirrors the originalinit, including the RC2/RC5 specs.Release note added to
docs/releasenotes.md(1.87).Tests
New
AEADEncryptionReuseTest(SimpleTest, registered incrypto.test.RegressionTest) covers CCM, KCCM, KGCM, EAX and OCB. It checks that:reset(), and through CCMprocessPacket;initwith a new nonce encrypts and decrypts correctly;reset()before any data still allows encryption;reset()after streamed data uses the nonce (EAX, OCB);It fails on
mainand passes with the change.Core tests pass:
CCMTest,EAXTest,OCBTest,DSTU7624Test,GCMTest,ChaCha20Poly1305Test,GCMSIVTest.Provider tests pass:
AEADTest,BlockCipherTest,OCBTest,DSTU7624Test,AESTest,ARIATest,CipherStreamTest2,AlgorithmParametersTest,ECIESTest. Before the test updates,AEADTestandBlockCipherTestfailed only on the EAX reuse described above; all of these pass onmaintoo.I ran these with javac and the test classes' runners (JDK 25), not through Gradle.
The same issue in bc-csharp is fixed in bcgit/bc-csharp#754.