Skip to content

Commit 7697c2e

Browse files
committed
#2483: ChaCha20Poly1305 now copies the ciphertext into its decryption buffer with System.arraycopy instead of one byte at a time
1 parent 6d7d611 commit 7697c2e

3 files changed

Lines changed: 84 additions & 3 deletions

File tree

‎core/src/main/java/org/bouncycastle/crypto/modes/ChaCha20Poly1305.java‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,14 @@ public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) t
346346
{
347347
case State.DEC_DATA:
348348
{
349-
for (int i = 0; i < len; ++i)
349+
while (len > 0)
350350
{
351-
buf[bufPos] = in[inOff + i];
352-
if (++bufPos == buf.length)
351+
int n = Math.min(len, buf.length - bufPos);
352+
System.arraycopy(in, inOff, buf, bufPos, n);
353+
inOff += n;
354+
len -= n;
355+
bufPos += n;
356+
if (bufPos == buf.length)
353357
{
354358
poly1305.update(buf, 0, BUF_SIZE);
355359
processData(buf, 0, BUF_SIZE, out, outOff + resultLen);

‎core/src/test/java/org/bouncycastle/crypto/test/ChaCha20Poly1305Test.java‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,84 @@ public void performTest() throws Exception
5959

6060
outputSizeTests();
6161
randomTests();
62+
testPiecewiseDecryption();
6263
testExceptions();
6364
}
6465

66+
/*
67+
* Decryption holds back the last MAC_SIZE bytes it has seen, since they may be the tag, so how the ciphertext is
68+
* split across calls decides what sits in the buffer when the next call arrives. Every split must decrypt as
69+
* one call does.
70+
*/
71+
private void testPiecewiseDecryption()
72+
throws InvalidCipherTextException
73+
{
74+
SecureRandom random = new SecureRandom();
75+
byte[] K = new byte[32];
76+
random.nextBytes(K);
77+
byte[] nonce = new byte[12];
78+
random.nextBytes(nonce);
79+
AEADParameters parameters = new AEADParameters(new KeyParameter(K), 16 * 8, nonce);
80+
81+
int[] lengths = { 0, 1, 15, 16, 17, 48, 63, 64, 65, 79, 80, 81, 127, 128, 129, 143, 144, 145, 300, 1000 };
82+
for (int i = 0; i < lengths.length; ++i)
83+
{
84+
byte[] P = new byte[lengths[i]];
85+
random.nextBytes(P);
86+
87+
ChaCha20Poly1305 cipher = initCipher(true, parameters);
88+
byte[] C = new byte[cipher.getOutputSize(P.length)];
89+
int len = cipher.processBytes(P, 0, P.length, C, 0);
90+
cipher.doFinal(C, len);
91+
92+
// pieces of one size, then pieces of random sizes with single bytes through processByte
93+
for (int piece = 1; piece <= 2 * (64 + 16) + 1; ++piece)
94+
{
95+
checkPiecewiseDecryption(parameters, P, C, random, piece);
96+
}
97+
for (int j = 0; j < 50; ++j)
98+
{
99+
checkPiecewiseDecryption(parameters, P, C, random, 0);
100+
}
101+
}
102+
}
103+
104+
private void checkPiecewiseDecryption(AEADParameters parameters, byte[] P, byte[] C, SecureRandom random,
105+
int piece)
106+
throws InvalidCipherTextException
107+
{
108+
ChaCha20Poly1305 cipher = initCipher(false, parameters);
109+
byte[] decP = new byte[cipher.getOutputSize(C.length)];
110+
111+
int len = 0;
112+
for (int pos = 0; pos < C.length; )
113+
{
114+
int n = Math.min(C.length - pos, piece > 0 ? piece : random.nextInt(3 * 64));
115+
int predicted = cipher.getUpdateOutputSize(n);
116+
int written;
117+
if (n == 1 && random.nextBoolean())
118+
{
119+
written = cipher.processByte(C[pos], decP, len);
120+
}
121+
else
122+
{
123+
written = cipher.processBytes(C, pos, n, decP, len);
124+
}
125+
if (written != predicted)
126+
{
127+
fail("piecewise decryption reported incorrect update length");
128+
}
129+
pos += n;
130+
len += written;
131+
}
132+
len += cipher.doFinal(decP, len);
133+
134+
if (len != P.length || !areEqual(P, decP))
135+
{
136+
fail("incorrect piecewise decrypt");
137+
}
138+
}
139+
65140
private void checkTestCase(
66141
ChaCha20Poly1305 encCipher,
67142
ChaCha20Poly1305 decCipher,

‎docs/releasenotes.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ Date: 2026, TBD
8181

8282
- The BCJSSE provider adds an org.bouncycastle.jsse.BCSSLContext interface exposing extended functionality of its SSLContext, obtained with org.bouncycastle.jsse.util.ContextUtil.getBCSSLContext() by way of the new BCSSLSessionContext interface the context's session contexts implement. Its getDefaultParameters(boolean) and getSupportedParameters(boolean) return the context's default and supported parameters as a BCSSLParameters for either client or server mode, including the BC-specific properties, where SSLContext.getDefaultSSLParameters() and getSupportedSSLParameters() report client mode only and cannot carry those properties. A BCSSLContext describes the initialization of the SSLContext it was obtained from, and is not updated if the SSLContext is re-initialized.
8383

84+
- ChaCha20Poly1305 - and with it XChaCha20Poly1305, the provider's ChaCha20-Poly1305 and XChaCha20-Poly1305 ciphers, HPKE and MLS - now copies the ciphertext into its decryption buffer with System.arraycopy, as much at a time as the buffer takes, where every byte used to be copied on its own. In a JMH comparison on an x86-64 machine decryption ran about 1.1 times as fast for 64 bytes and 1.2 times as fast for 1 KB to 16 KB on JDK 21 and 25, and 1.35 to 1.45 times as fast on JDK 17. The output is unchanged.
85+
8486
### 2.1.4 Additional Notes
8587

8688
- The sources and javadoc jars of the Ant-built distributions (jdk14, jdk15to18 and jdk13) no longer carry test material. Each module's javadoc target copies the package documentation it needs - org/bouncycastle/<area>/**/*.html - back into the module source directory that has already been compiled from, and zip-src zips that directory afterwards, so every test package's package.html arrived in the sources jar by that route; javadoc-util additionally copied org/bouncycastle/asn1/isismtt/**/*.java, which put test classes into the bcutil javadoc as generated pages, and javadoc-pg deliberately copied the gpg and bcpg test sources in order to document them. Separately the source copies excluded test material only one directory deep and only for *.java, because Ant reads ** as an any-depth wildcard just where it is a whole path segment, so anything nested further or with another extension - the PEM certificate fixtures under org/bouncycastle/est/test/san corrected in 1.86, and an ICAO master list under org/bouncycastle/asn1/icao/test - went through. The source and javadoc copies of every module now exclude test directories at any depth, and javadoc-pg no longer documents the test packages. org.bouncycastle.util.test is unaffected and still ships in the bcprov binary, sources and javadoc jars, as it does from the Gradle build: it is the SimpleTest framework the light-weight API's own test classes are written against, not test material of the distribution. No binary changes - the classes and resources of every Ant-built jar are identical to those of the 1.86 release - and the Gradle-built jdk18on artifacts never carried any of this.

0 commit comments

Comments
 (0)