I’m trying to write an encrypter/decrypter class that encrypts and decrypts files. My server receives the file as an InputStream
, and I want to store it at a particular path, so those are the inputs to my encrypt()
method. For testing purposes, encrypt()
generates a new key for each file and stores it in a map with the file’s path. My decrypt()
method just takes the file name and attempts to print the file contents as a string. encrypt()
seems to work fine. The problem I’m running into is that when I call decrypt()
with the file name, I get a ShortBufferException. I’ve tried changing the buffer size in my BufferedReader, but to no avail. I think I’m missing something fundamental about how CipherInputStream
or InputStream
s in general work. Any thoughts would be much appreciated! Here’s my FileEncrypterDecrypter
class:
public class FileEncrypterDecrypter {
private static final int ALGORITHM_NONCE_SIZE = 12;
private static final String ALGORITHM_NAME = "AES/GCM/NoPadding";
private final static int ALGORITHM_TAG_SIZE = 128;
private final static HashMap<String, SecretKey> fileToKey = new HashMap<>();
FileEncrypterDecrypter() {}
public static void encrypt(InputStream plaintext, String filePath) throws IOException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException {
// Generate a new SecretKey for this file
SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();
fileToKey.put(filePath, secretKey);
// Generate a 96-bit nonce using a CSPRNG.
SecureRandom rand = new SecureRandom();
byte[] nonce = new byte[ALGORITHM_NONCE_SIZE];
rand.nextBytes(nonce);
System.out.println(Arrays.toString(nonce));
// Create the cipher instance and initialize.
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(ALGORITHM_TAG_SIZE, nonce));
// Write to the file using a CipherOutputStream
FileOutputStream fileOut = new FileOutputStream(filePath);
CipherOutputStream cipherOut = new CipherOutputStream(fileOut, cipher);
fileOut.write(nonce);
plaintext.transferTo(cipherOut);
}
public static String decrypt(String filePath) throws InvalidKeyException, IOException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
// Get the SecretKey for this file
SecretKey secretKey = fileToKey.get(filePath);
// Read nonce from the file
FileInputStream fileIn = new FileInputStream(filePath);
byte[] nonce = new byte[ALGORITHM_NONCE_SIZE];
fileIn.read(nonce);
System.out.println(Arrays.toString(nonce));
fileIn = new FileInputStream(filePath);
// Create the cipher instance and initialize
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, nonce));
CipherInputStream cipherIn = new CipherInputStream(fileIn, cipher);
InputStreamReader inputReader = new InputStreamReader(cipherIn);
BufferedReader reader = new BufferedReader(inputReader);
// Decrypt and return result.
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
}
And my error output:
Exception in thread "main" java.io.IOException: javax.crypto.ShortBufferException: Output buffer invalid
at java.base/javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:148)
at java.base/javax.crypto.CipherInputStream.read(CipherInputStream.java:261)
at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:270)
at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:313)
at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:188)
at java.base/java.io.InputStreamReader.read(InputStreamReader.java:177)
at java.base/java.io.BufferedReader.fill(BufferedReader.java:162)
at java.base/java.io.BufferedReader.readLine(BufferedReader.java:329)
at java.base/java.io.BufferedReader.readLine(BufferedReader.java:396)
at FileEncrypterDecrypter.decrypt(FileEncrypterDecrypter.java:64)
at Main.main(Main.java:22)
Caused by: javax.crypto.ShortBufferException: Output buffer invalid
at java.base/com.sun.crypto.provider.GaloisCounterMode$GCMDecrypt.doFinal(GaloisCounterMode.java:1366)
at java.base/com.sun.crypto.provider.GaloisCounterMode.engineDoFinal(GaloisCounterMode.java:432)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2152)
at java.base/javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:145)
... 10 more
Here’s my main method:
public static void main (String[] args) throws BadPaddingException, IllegalBlockSizeException, IOException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException {
InputStream is = new ByteArrayInputStream(Charset.forName("UTF-8").encode("HELLO WORLD").array());
FileEncrypterDecrypter encrypterDecrypter = new FileEncrypterDecrypter();
encrypterDecrypter.encrypt(is, "./message");
encrypterDecrypter.decrypt("./message");
}
I’ve found similar problems on Stack Overflow, but none which applies exactly to my situation.