File encryption and decryption using AES
public long copyStreamsLong(InputStream in, OutputStream out, long sizeLimit) throws IOException {
long byteCount = 0;
IOException error = null;
long totalBytesRead = 0;
try {
String key = "C4F9EA21977047D6"; // user value (16/24/32 bytes)
// byte[] keyBytes = DatatypeConverter.parseHexBinary(aesKey);
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
System.out.println(secretKey.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] buffer = new byte[CustomLimitedStreamCopier.BYTE_BUFFER_SIZE];
// in.read(buffer);
int bytesRead = -1;
while ((bytesRead = in.read(buffer)) != -1) {
// We are able to abort the copy immediately upon limit violation.
totalBytesRead += bytesRead;
if (sizeLimit > 0 && totalBytesRead > sizeLimit) {
StringBuilder msg = new StringBuilder();
msg.append("Content size violation, limit = ").append(sizeLimit);
throw new ContentLimitViolationException(msg.toString());
}
byte[] obuf = cipher.update(buffer, 0, bytesRead);
if (obuf != null) {
out.write(obuf);
}
byteCount += bytesRead;
}
byte[] obuf = cipher.doFinal();
if (obuf != null) {
out.write(obuf);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
error = e;
CustomLimitedStreamCopier.logger.error("Failed to close input stream: " + this, e);
}
try {
out.close();
} catch (IOException e) {
error = e;
CustomLimitedStreamCopier.logger.error("Failed to close output stream: " + this, e);
}
}
if (error != null)
throw error;
return byteCount;
}
public InputStream getContentInputStream() throws ContentIOException {
ReadableByteChannel channel = getReadableChannel();
InputStream is = Channels.newInputStream(channel);
try {
final String ALGORITHM = "AES";
final String TRANSFORMATION = "AES";
String key = "C4F9EA21977047D6";
Key secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] buffer = ByteStreams.toByteArray(is);
System.out.println("in read" + buffer.length);
is.read(buffer);
byte[] outputBytes = cipher.doFinal(buffer);
is = new ByteArrayInputStream(outputBytes);
}
When I try to encrypt the input stream, I get the increased byte array, after that, when I decrypt that input stream while decrypting, it takes the original byte size. So when I try to open the file it will give the wrong premature end tag, so I want the file to be the same size after encryption and this method is part of the java class.
What is the solution to this problem?
The cipher is initialized with TRANSFORMATION=" AES " , which means your Java implementation will choose the default AES mode, usually " AES/ECB/PKCS5Padding ". Since ECB mode is not secure and should no longer be used for plaintext/streams longer than 16 bytes, the padding will add extra bytes up to a maximum block length of 16.
So run my applet and you'll see that in your implementation, an inputLen of "10" (meaning a 10-byte long plain text/stream) will result in an outputSize of "16".
To avoid this, you do need another AES mode, and the output has the same length as the input on the encryption side. You can use AES CTR mode for this - you just need one additional parameter (initialization vector, 16 bytes long). It's very important that you never use the same iv for more than 1 encryption, so it should be generated as a random value. In order to decrypt, the receiver of the message ("decryptor") needs to know what initialization vector was used in terms of encryption.
cipher algorithm: AES inputLen 10 outputSize 16
cipher algorithm: AES/CTR/NOPADDING inputLen 10 outputSize 10
EDIT (Security Warning): As President James K. Polk reminded, " CTR mode makes it trivial to modify a single byte of an attacker's choice, and therefore needs to be used with an authentication tag. ". It depends on the type of data that will be encrypted that is being authenticated (eg if you are encrypting music files any modification will result in "piep" or "krk", changes in financial data will be disastrous).
code:
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class Main {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
System.out.println("");
int inputLen = 10;
final String ALGORITHM = "AES";
// aes ecb mode
final String TRANSFORMATION = "AES";
//final String TRANSFORMATION = "AES/ECB/PKCS5PADDING";
String key = "C4F9EA21977047D6";
Key secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
System.out.format("cipher algorithm: %-20s inputLen %3d outputSize %3d%n", cipher.getAlgorithm(), inputLen, cipher.getOutputSize(inputLen));
// aes ctr mode
String TRANSFORMATION2 = "AES/CTR/NOPADDING";
// you need an unique (random) iv for each encryption
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher2 = Cipher.getInstance(TRANSFORMATION2);
cipher2.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
System.out.format("cipher algorithm: %-20s inputLen %3d outputSize %3d%n", cipher2.getAlgorithm(), inputLen, cipher2.getOutputSize(inputLen));
}
}
A running implementation of AES CTR mode can be found here : https://stackoverflow.com/a/62662276/8166854