I'm working on a file encryption benchmark for large files and tested ChaCha20-Poly1305, but received an error on decryption part:
java.lang.RuntimeException: javax.crypto.ShortBufferException: Output buffer too small
at java.base/com.sun.crypto.provider.ChaCha20Cipher.engineDoFinal(ChaCha20Cipher.java:703)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2085)
at ChaCha20.ChaCha20Poly1305Jre.main(ChaCha20Poly1305Jre.java:73)
Caused by: javax.crypto.ShortBufferException: Output buffer too small
at java.base/com.sun.crypto.provider.ChaCha20Cipher$EngineAEADDec.doFinal(ChaCha20Cipher.java:1360)
at java.base/com.sun.crypto.provider.ChaCha20Cipher.engineDoFinal(ChaCha20Cipher.java:701)
This is not an error in my program but in OpenJava 11 I'm using and should get fixed (known since 2019, see https://bugs.openjdk.java.net/browse/JDK-8224997). Even with the newest "Early adopter"-version (OpenJDK11U-jdk_x64_windows_11.0.7_9_ea) the error still occurs. This test is run with java version: 11.0.6+8-b520.43.
My question is: is there any other way to perform file encryption with ChaCha20-Poly1305 with native JCE for large files ?
I do not want to use BouncyCastle (as I'm using BC allready for the counterpart benchmark) or reading the plainfile completly into memory (in my source the testfile is only 1.024 bytes large but the benchmark will test up to 1 GB files). As well I do not want to use ChaCha20 as it does not provide any authentication. You can find the sources for ChaCha20Poly1305Jce.java, ChaCha20Poly1305JceNoStream.java and ChaCha20Jce.java in my Github-Repo https://github.com/java-crypto/Stackoverflow/tree/master/ChaCha20Poly1305.
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
public class ChaCha20Poly1305Jce {
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {
System.out.println("File En-/Decryption with ChaCha20-Poly1305 JCE");
System.out.println("see: https://stackoverflow.com/questions/61520639/chacha20-poly1305-fails-with-shortbufferexception-output-buffer-too-small");
System.out.println("\njava version: " + Runtime.version());
String filenamePlain = "test1024.txt";
String filenameEnc = "test1024enc.txt";
String filenameDec = "test1024dec.txt";
Files.deleteIfExists(new File(filenamePlain).toPath());
generateRandomFile(filenamePlain, 1024);
// setup chacha20-poly1305-cipher
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] key = new byte[32]; // 32 for 256 bit key or 16 for 128 bit
byte[] nonce = new byte[12]; // nonce = 96 bit
sr.nextBytes(key);
sr.nextBytes(nonce);
// Get Cipher Instance
Cipher cipherE = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
// Create parameterSpec
AlgorithmParameterSpec algorithmParameterSpec = new IvParameterSpec(nonce);
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key, "ChaCha20");
System.out.println("keySpec: " + keySpec.getAlgorithm() + " " + keySpec.getFormat());
System.out.println("cipher algorithm: " + cipherE.getAlgorithm());
// initialize the cipher for encryption
cipherE.init(Cipher.ENCRYPT_MODE, keySpec, algorithmParameterSpec);
// encryption
System.out.println("start encryption");
byte inE[] = new byte[8192];
byte outE[] = new byte[8192];
try (InputStream is = new FileInputStream(new File(filenamePlain));
OutputStream os = new FileOutputStream(new File(filenameEnc))) {
int len = 0;
while (-1 != (len = is.read(inE))) {
cipherE.update(inE, 0, len, outE, 0);
os.write(outE, 0, len);
}
byte[] outEf = cipherE.doFinal();
os.write(outEf, 0, outEf.length);
} catch (Exception e) {
e.printStackTrace();
}
// decryption
System.out.println("start decryption");
Cipher cipherD = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
// initialize the cipher for decryption
cipherD.init(Cipher.DECRYPT_MODE, keySpec, algorithmParameterSpec);
byte inD[] = new byte[8192];
byte outD[] = new byte[8192];
try (InputStream is = new FileInputStream(new File(filenameEnc));
OutputStream os = new FileOutputStream(new File(filenameDec))) {
int len = 0;
while (-1 != (len = is.read(inD))) {
cipherD.update(inD, 0, len, outD, 0);
os.write(outD, 0, len);
}
byte[] outDf = cipherD.doFinal();
os.write(outDf, 0, outDf.length);
} catch (Exception e) {
e.printStackTrace();
}
// file compare
System.out.println("compare plain <-> dec: " + Arrays.equals(sha256(filenamePlain), sha256(filenameDec)));
}
public static void generateRandomFile(String filename, int size) throws IOException, NoSuchAlgorithmException {
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] data = new byte[size];
sr.nextBytes(data);
Files.write(Paths.get(filename), data, StandardOpenOption.CREATE);
}
public static byte[] sha256(String filenameString) throws IOException, NoSuchAlgorithmException {
byte[] buffer = new byte[8192];
int count;
MessageDigest md = MessageDigest.getInstance("SHA-256");
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filenameString));
while ((count = bis.read(buffer)) > 0) {
md.update(buffer, 0, count);
}
bis.close();
return md.digest();
}
}
Using the Early Access version of OpenJDK 11.0.8 (get it here: https://adoptopenjdk.net/upstream.html?variant=openjdk11&ga=ea) I was able to run the (edited) testprogram (now I'm using CipherInput/OutputStreams).
File En-/Decryption with ChaCha20-Poly1305 JCE
see: https://stackoverflow.com/questions/61520639/chacha20-poly1305-fails-with-shortbufferexception-output-buffer-too-small
java version: 11.0.8-ea+8
start encryption
keySpec: ChaCha20 RAW
cipher algorithm: ChaCha20-Poly1305/None/NoPadding
start decryption
compare plain <-> dec: true
Edit: I missed the final version (GA) for one hour but it's working as well:
java version: 11.0.8+10
start encryption
keySpec: ChaCha20 RAW
cipher algorithm: ChaCha20-Poly1305/None/NoPadding
start decryption
compare plain <-> dec: true
new code:
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.*;
import java.util.Arrays;
public class ChaCha20Poly1305JceCis {
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {
System.out.println("File En-/Decryption with ChaCha20-Poly1305 JCE");
System.out.println("see: https://stackoverflow.com/questions/61520639/chacha20-poly1305-fails-with-shortbufferexception-output-buffer-too-small");
System.out.println("\njava version: " + Runtime.version());
String filenamePlain = "test1024.txt";
String filenameEnc = "test1024enc.txt";
String filenameDec = "test1024dec.txt";
Files.deleteIfExists(new File(filenamePlain).toPath());
generateRandomFile(filenamePlain, 1024);
// setup chacha20-poly1305-cipher
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] key = new byte[32]; // 32 for 256 bit key or 16 for 128 bit
byte[] nonce = new byte[12]; // nonce = 96 bit
sr.nextBytes(key);
sr.nextBytes(nonce);
System.out.println("start encryption");
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
try (FileInputStream in = new FileInputStream(filenamePlain);
FileOutputStream out = new FileOutputStream(filenameEnc);
CipherOutputStream encryptedOutputStream = new CipherOutputStream(out, cipher);) {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "ChaCha20");
System.out.println("keySpec: " + secretKeySpec.getAlgorithm() + " " + secretKeySpec.getFormat());
System.out.println("cipher algorithm: " + cipher.getAlgorithm());
//AlgorithmParameterSpec algorithmParameterSpec = new IvParameterSpec(nonce);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(nonce));
byte[] buffer = new byte[8096];
int nread;
while ((nread = in.read(buffer)) > 0) {
encryptedOutputStream.write(buffer, 0, nread);
}
encryptedOutputStream.flush();
}
// decryption
System.out.println("start decryption");
Cipher cipherD = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
try (FileInputStream in = new FileInputStream(filenameEnc); // i don't care about the path as all is lokal
CipherInputStream cipherInputStream = new CipherInputStream(in, cipherD);
FileOutputStream out = new FileOutputStream(filenameDec)) // i don't care about the path as all is lokal
{
byte[] buffer = new byte[8192];
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "ChaCha20");
//AlgorithmParameterSpec algorithmParameterSpec = new IvParameterSpec(nonce);
cipherD.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(nonce));
int nread;
while ((nread = cipherInputStream.read(buffer)) > 0) {
out.write(buffer, 0, nread);
}
out.flush();
}
// file compare
System.out.println("compare plain <-> dec: " + Arrays.equals(sha256(filenamePlain), sha256(filenameDec)));
}
public static void generateRandomFile(String filename, int size) throws IOException, NoSuchAlgorithmException {
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] data = new byte[size];
sr.nextBytes(data);
Files.write(Paths.get(filename), data, StandardOpenOption.CREATE);
}
public static byte[] sha256(String filenameString) throws IOException, NoSuchAlgorithmException {
byte[] buffer = new byte[8192];
int count;
MessageDigest md = MessageDigest.getInstance("SHA-256");
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filenameString));
while ((count = bis.read(buffer)) > 0) {
md.update(buffer, 0, count);
}
bis.close();
return md.digest();
}
}
Edit July 23 2020: Seems to be that not all Java versions with version nr 11.0.8 got fixed. I tested the "original" Oracle Java 1.0.8 for Windows x64 and the error still persists. I reported this to Oracle Bug tracker and it was assigned as a Bug ID: JDK-8249844 (https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8249844).