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).
Related
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 m trying to encrypt input stream i will get increased byte array and after that when i will decrypt that input stream at that time in decryption it will take original byte size .so,when i tried to open that file it will give error that premature end tag so i want same file size after encryption ,and this method is part of java class.
What will be the solution for this problem?
You initialize the cipher with the TRANSFORMATION = "AES" and that means that your Java implementation will choose a default AES mode, usually it is "AES/ECB/PKCS5Padding". Beneath the fact that ECB-mode is unsecure and should not any longer used for plaintext/streams longer than 16 bytes the padding will add additional bytes up to a (multiple) blocklength of 16.
So running my small program you see that in your implementation an inputLen of "10" (means a plaintext/stream of 10 byte length) will result in an outputSize of "16".
To avoid this you do need another AES mode and that the output is of the same length as the input on encryption side. You can use AES CTR mode for this - you just need an additional parameter (initialization vector, 16 byte long). It is very important that you never ever use the same iv for more than 1 encryption so it should get generated as random value. For decryption the recipient of the message ("the decryptor") needs to know what initvector was used on encryption side.
cipher algorithm: AES inputLen 10 outputSize 16
cipher algorithm: AES/CTR/NOPADDING inputLen 10 outputSize 10
Edit (security warning): As reminded by President James K. Polk "CTR mode makes it trivial to modify individual bytes of the attacker's choosing, so it needs to be coupled with an authentication tag.". It depends on the kind of data that are going to get encrypted if they need a authentication (e.g. if you are encrypting music files then any modifying will result in a "piep" or "krk", changing of financial data will end catastrophical).
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 the AES CTR-mode can be found here: https://stackoverflow.com/a/62662276/8166854
I try to test this code for encrypting and decrypting. By using dst = new String(baos.toByteArray()); return dst;
I can't decrypt the cipher text. However when I use byte[] encryptedBytes = DatatypeConverter.parseHexBinary(src);
I didn't manage to run the program. How can I fix this?
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class FileEncryption {
//Initial Vector
public static final byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
//EncryptAndDecrypt String -> Input : PlainText + Return : CipherText+DecipherText
public static String encryptString(String src) throws Exception
{
String dst="";
//Not Input!
if(src == null || src.length()==0)
return "";
//Encryption Setting
byte[] k="Multimediaproces".getBytes();
SecretKeySpec Key = new SecretKeySpec(k,"AES");
IvParameterSpec ivspec = new IvParameterSpec(iv);
Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE,Key,ivspec);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
CipherOutputStream cout = new CipherOutputStream(baos,encryptCipher);
cout.write(src.getBytes());
cout.flush(); //ByteOutputStream -> Write Encryption Text
cout.close();
dst = DatatypeConverter.printHexBinary(baos.toByteArray());
return dst;
}
//String src -> EncryptedData
public static String decryptString(String src) throws Exception
{
//src value is Encrypted Value!
//So, src value -> Not Byte!
String dst="";
byte[] encryptedBytes = src.getBytes();
//Not Input!
if(src == null || src.length()==0)
return "";
//Decryption Setting
IvParameterSpec ivspec = new IvParameterSpec(iv);
byte[] k="Multimediaproces".getBytes();
SecretKeySpec Key = new SecretKeySpec(k,"AES");
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
decryptCipher.init(Cipher.DECRYPT_MODE,Key,ivspec);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteArrayInputStream bais = new ByteArrayInputStream(encryptedBytes);
CipherInputStream cin = new CipherInputStream(bais,decryptCipher);
byte[] buf = new byte[1024];
int read;
while((read=cin.read(buf))>=0) //reading encrypted data!
{
baos.write(buf,0,read); //writing decrypted data!
}
// closing streams
cin.close();
byte[] encryptedBytes = DatatypeConverter.parseHexBinary(src);
return dst;
}
}
There are three errors in your sample code:
It doesn't compile because of byte[] encryptedBytes = DatatypeConverter.parseHexBinary(src); at the end of decryptString method
Because you have printed out the encrypted string as a hexidecimal representation, you must convert it back to a normal byte array before trying to decrypt it. In decryptString you should call byte[] encryptedBytes = DatatypeConverter.parseHexBinary(src); instead. This will convert back to the byte[] instead of the hexidecimal representation.
You need a line which converts baos to a String at the end of decrpytString. This is as simple as dst = baos.toString();
With these three changes, I was able to encrypt and decrypt a String.
I would like to encrypt and decrypt files. While decrypting I wish to skip first few bytes of the file and decrypt only the rest.
Here contains three files
input_file.txt - input text file for encryption
output_file.txt - encrypted input_file.txt file
decrypted_file.txt - decrypted output_file.txt file
Sample code:
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class FileReadWrite {
public static byte[] getAESKey() {
SecureRandom secureRandom = new SecureRandom();
byte[] bytes = new byte[32];
secureRandom.nextBytes(bytes);
return bytes;
}
/**
* Method used to generate a random new iv
*
* #return Randomly generated iv
*/
public static byte[] getAESIV() {
SecureRandom secureRandom = new SecureRandom();
byte[] bytes = new byte[16];
secureRandom.nextBytes(bytes);
return bytes;
}
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
FileInputStream fin = new FileInputStream("/Users/emp/Research/Streams/input_file.txt");
FileOutputStream fout = new FileOutputStream("/Users/emp/Research/Streams/output_file.txt");
SecretKeySpec keySpec = null;
IvParameterSpec ivSpec = null;
Cipher ecipher = null;
Cipher dcipher = null;
byte[] keyBytes = getAESKey();
byte[] ivBytes = getAESIV();
// Creating keyspec and ivspec for generating cipher
keySpec = new SecretKeySpec(keyBytes,"AES");
ivSpec = new IvParameterSpec(ivBytes);
try {
ecipher = Cipher.getInstance("AES/CTR/NoPadding");
ecipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
} catch (Exception e) {
System.out.println("Thus the exception occured during cipher generation is ::: "+e);
}
CipherOutputStream cout = new CipherOutputStream(fout, ecipher);
try {
int count = 0;
int BUFFER_SIZE = 1024;
byte[] bytearray = new byte[BUFFER_SIZE];
while((count = fin.read(bytearray, 0, BUFFER_SIZE)) != -1) {
//fout.write(bytearray, 0, count);
cout.write(bytearray, 0, count);
}
} catch(Exception ex) {
System.out.println("Thus the exception occured is ::: "+ex);
} finally {
fin.close();
fout.close();
cout.close();
}
fin = new FileInputStream("/Users/emp/Research/Streams/output_file.txt");
fout = new FileOutputStream("/Users/emp/Research/Streams/decrypted_file.txt");
try {
dcipher = Cipher.getInstance("AES/CTR/NoPadding");
dcipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
} catch (Exception e) {
System.out.println("Thus the exception occured during cipher generation is ::: "+e);
}
//fin.skip(1024);
CipherInputStream cin = new CipherInputStream(fin, dcipher);
try {
int count = 0;
int BUFFER_SIZE = 1024;
byte[] bytearray = new byte[BUFFER_SIZE];
**//cin.read(bytearray, 0, 30);**
while((count = cin.read(bytearray, 0, BUFFER_SIZE)) != -1) {
//fout.write(bytearray, 0, count);
fout.write(bytearray, 0, count);
}
} catch(Exception ex) {
System.out.println("Thus the exception occured is ::: "+ex);
} finally {
fin.close();
cin.close();
fout.close();
}
System.out.println("File read write completed successfully !!! ");
}
}
Tried:
Cipher input stream skip - cin.skip(no_of_bytes) - this doesn't work - this decrypts the entire file
File input stream skip
fin.skip(no_of_bytes); CipherInputStream cin = new
CipherInputStream(fin, cipher);
This does not decrypt the file. The output file looks like encrypted.
Dummy read - Reading and ignoring from cipher input stream - this works
//cin.read(bytearray, 0, 30);
Please clarify me the following:
What skip and seek input stream does internally?
Why my cipher input stream seek doesn't work?
Why cipher input stream requires all the bytes to be read from the stream (I assume so) for decrypting? How does it work?
With reference to the article,
Random access InputStream using AES CTR mode in android
After updating the iv with offset value the decryption works fine.
Here is the updated code
package Streams;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class FileReadWrite {
private static final int AES_BLOCK_SIZE = 16;
public static final void jumpToOffset(final Cipher c, final SecretKeySpec aesKey, final IvParameterSpec iv, final long offset) {
if (!c.getAlgorithm().toUpperCase().startsWith("AES/CTR")) {
throw new IllegalArgumentException("Invalid algorithm, only AES/CTR mode supported");
}
if (offset < 0) {
throw new IllegalArgumentException("Invalid offset");
}
final int skip = (int) (offset % AES_BLOCK_SIZE);
final IvParameterSpec calculatedIVForOffset = calculateIVForOffset(iv, offset - skip);
try {
c.init(Cipher.ENCRYPT_MODE, aesKey, calculatedIVForOffset);
final byte[] skipBuffer = new byte[skip];
c.update(skipBuffer, 0, skip, skipBuffer);
Arrays.fill(skipBuffer, (byte) 0);
} catch (ShortBufferException | InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalStateException(e);
}
}
private static IvParameterSpec calculateIVForOffset(final IvParameterSpec iv, final long blockOffset) {
final BigInteger ivBI = new BigInteger(1, iv.getIV());
final BigInteger ivForOffsetBI = ivBI.add(BigInteger.valueOf(blockOffset / AES_BLOCK_SIZE));
final byte[] ivForOffsetBA = ivForOffsetBI.toByteArray();
final IvParameterSpec ivForOffset;
if (ivForOffsetBA.length >= AES_BLOCK_SIZE) {
ivForOffset = new IvParameterSpec(ivForOffsetBA, ivForOffsetBA.length - AES_BLOCK_SIZE, AES_BLOCK_SIZE);
} else {
final byte[] ivForOffsetBASized = new byte[AES_BLOCK_SIZE];
System.arraycopy(ivForOffsetBA, 0, ivForOffsetBASized, AES_BLOCK_SIZE - ivForOffsetBA.length, ivForOffsetBA.length);
ivForOffset = new IvParameterSpec(ivForOffsetBASized);
}
return ivForOffset;
}
public static byte[] getAESKey() {
SecureRandom secureRandom = new SecureRandom();
byte[] bytes = new byte[32];
secureRandom.nextBytes(bytes);
return bytes;
}
/**
* Method used to generate a random new iv
*
* #return Randomly generated iv
*/
public static byte[] getAESIV() {
SecureRandom secureRandom = new SecureRandom();
byte[] bytes = new byte[16];
secureRandom.nextBytes(bytes);
return bytes;
}
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
FileInputStream fin = new FileInputStream("/Users/emp/Research/Streams/input_file.txt");
FileOutputStream fout = new FileOutputStream("/Users/emp/Research/Streams/output_file.txt");
SecretKeySpec keySpec = null;
IvParameterSpec ivSpec = null;
Cipher ecipher = null;
Cipher dcipher = null;
byte[] keyBytes = getAESKey();
byte[] ivBytes = getAESIV();
// Creating keyspec and ivspec for generating cipher
keySpec = new SecretKeySpec(keyBytes,"AES");
ivSpec = new IvParameterSpec(ivBytes);
try {
ecipher = Cipher.getInstance("AES/CTR/NoPadding");
ecipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
} catch (Exception e) {
System.out.println("Thus the exception occured during cipher generation is ::: "+e);
}
CipherOutputStream cout = new CipherOutputStream(fout, ecipher);
try {
int count = 0;
int BUFFER_SIZE = 1024;
byte[] bytearray = new byte[BUFFER_SIZE];
while((count = fin.read(bytearray, 0, BUFFER_SIZE)) != -1) {
//fout.write(bytearray, 0, count);
cout.write(bytearray, 0, count);
}
} catch(Exception ex) {
System.out.println("Thus the exception occured is ::: "+ex);
} finally {
fin.close();
fout.close();
cout.close();
}
fin = new FileInputStream("/Users/emp/Research/Streams/output_file.txt");
fout = new FileOutputStream("/Users/emp/Research/Streams/decrypted_file.txt");
try {
dcipher = Cipher.getInstance("AES/CTR/NoPadding");
dcipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
} catch (Exception e) {
System.out.println("Thus the exception occured during cipher generation is ::: "+e);
}
fin.skip(1024);
jumpToOffset(dcipher, keySpec, ivSpec, 1024);
CipherInputStream cin = new CipherInputStream(fin, dcipher);
//cin.skip(1024);
try {
int count = 0;
int BUFFER_SIZE = 1024;
byte[] bytearray = new byte[BUFFER_SIZE];
//cin.read(bytearray, 0, 30);
while((count = cin.read(bytearray, 0, BUFFER_SIZE)) != -1) {
//fout.write(bytearray, 0, count);
fout.write(bytearray, 0, count);
}
} catch(Exception ex) {
System.out.println("Thus the exception occured is ::: "+ex);
} finally {
fin.close();
cin.close();
fout.close();
}
System.out.println("File read write completed successfully !!! ");
}
}
Hi I have a client using Triple DES. I know I know if AES I would not have a problem it is a older solution integration. I have code below that Takes in a file and writes to another. On the decrypt method I don't understand the 2nd parameter for length of the file. Please help. The error I get is the following:
Exception in thread "main" org.bouncycastle.crypto.DataLengthException: last block incomplete in decryption
at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.doFinal(Unknown Source)
at DESede_BC.decrypt(DESede_BC.java:102)
at DESede_BC.main(DESede_BC.java:120)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.ShortBufferException;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.DESedeEngine;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
public class DESede_BC {
PaddedBufferedBlockCipher encryptCipher;
PaddedBufferedBlockCipher decryptCipher;
// Buffers used to transport the bytes from one stream to another
byte[] buf = new byte[8]; //input buffer - block size length
byte[] obuf = new byte[557]; //output buffer
byte[] key = null; //the key
public DESede_BC(){
//use a default 192 bit key
key = "thekey".getBytes();
InitCiphers();
}
public DESede_BC(byte[] keyBytes){
key = new byte[keyBytes.length];
System.arraycopy(keyBytes, 0 , key, 0, keyBytes.length);
InitCiphers();
}
private void InitCiphers(){
encryptCipher = new PaddedBufferedBlockCipher(new DESedeEngine());
encryptCipher.init(true, new KeyParameter(key));
decryptCipher = new PaddedBufferedBlockCipher(new DESedeEngine());
decryptCipher.init(false, new KeyParameter(key));
}
public void ResetCiphers() {
if(encryptCipher!=null)
encryptCipher.reset();
if(decryptCipher!=null)
decryptCipher.reset();
}
public void encrypt(InputStream in, long length, OutputStream out)
throws ShortBufferException,
IllegalBlockSizeException,
BadPaddingException,
DataLengthException,
IllegalStateException,
InvalidCipherTextException
{
try {
// Bytes written to out will be encrypted
// Read in the cleartext bytes from in InputStream and
// write them encrypted to out OutputStream
int noBytesRead = 0; //number of bytes read from input
int noBytesProcessed = 0; //number of bytes processed
while ((noBytesRead = in.read(buf)) >= 0) {
noBytesProcessed =
encryptCipher.processBytes(buf, 0, noBytesRead, obuf, 0);
out.write(obuf, 0, noBytesProcessed);
}
noBytesProcessed =
encryptCipher.doFinal(obuf, 0);
out.write(obuf, 0, noBytesProcessed);
out.flush();
}
catch (java.io.IOException e) {
System.out.println(e.getMessage());
}
}
public void decrypt(InputStream in,long length, OutputStream out)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException,
DataLengthException, IllegalStateException, InvalidCipherTextException
{
try {
// Bytes read from in will be decrypted
// Read in the decrypted bytes from in InputStream and and
// write them in cleartext to out OutputStream
int noBytesRead = 0; //number of bytes read from input
int noBytesProcessed = 0; //number of bytes processed
while ((noBytesRead = in.read(buf)) >= 0) {
noBytesProcessed = decryptCipher.processBytes(buf, 0, noBytesRead, obuf, 0);
out.write(obuf, 0, noBytesProcessed);
}
noBytesProcessed = decryptCipher.doFinal(obuf, 0);
out.write(obuf, 0, noBytesProcessed);
out.flush();
}
catch (java.io.IOException e) {
System.out.println(e.getMessage());
}
}
public static void main(String... args)
throws Exception
{
DESede_BC d = new DESede_BC();
FileInputStream fis2 = new FileInputStream("c:\\2.in");
FileOutputStream fos2 = new FileOutputStream("c:\\decrypted.txt");
d.decrypt(fis2, new Long("128"), fos2);
}
}
I think you need to decode your input into Base64 before doFinal:
byte[] obuf = Base64.decode(obuf, Base64.NO_OPTIONS);
byte[] decValue = c.doFinal(obuf);
Im sending a file from Server to a Client but i need to send a encrypted file with AES 256 and receive a original for client use in diferente machine.
I want use 2 string to generate a SHA256 for example: "fruit" and "car29".
After generate this key I want to use this key as secretkey to encrypt with AES256.The client and server know the two strings.
My server code:
public final static int SOCKET_PORT = 4444;
public final static String FILE_TO_SEND = "C:\\file.txt";
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
FileInputStream fis = null;
BufferedInputStream bis = null;
OutputStream os = null;
ServerSocket servsock = null;
Socket sock = null;
try {
servsock = new ServerSocket(SOCKET_PORT);
while (true) {
System.out.println("Waiting...");
try {
sock = servsock.accept();
System.out.println("Accepted connection : " + sock);
// send file
File myFile = new File(FILE_TO_SEND);
byte[] mybytearray = new byte[(int) myFile.length()];
fis = new FileInputStream(myFile);
bis = new BufferedInputStream(fis);
bis.read(mybytearray, 0, mybytearray.length);
os = sock.getOutputStream();
System.out.println("Sending " + FILE_TO_SEND + "(" + mybytearray.length + " bytes)");
os.write(mybytearray, 0, mybytearray.length);
os.flush();
System.out.println("Done.");
} finally {
if (bis != null) {
bis.close();
}
if (os != null) {
os.close();
}
if (sock != null) {
sock.close();
}
}
}
} finally {
if (servsock != null) {
servsock.close();
}
}
}
My client code:
public final static int SOCKET_PORT = 4444;
public final static String SERVER = "127.0.0.1";
public final static String FILE_TO_RECEIVED = "C:\\file.txt";
public final static int FILE_SIZE = 6022386;
public static void main (String [] args ) throws IOException {
int bytesRead;
int current = 0;
FileOutputStream fos = null;
BufferedOutputStream bos = null;
Socket sock = null;
try {
sock = new Socket(SERVER, SOCKET_PORT);
System.out.println("Connecting...");
// receive file
byte [] mybytearray = new byte [FILE_SIZE];
InputStream is = sock.getInputStream();
fos = new FileOutputStream(FILE_TO_RECEIVED);
bos = new BufferedOutputStream(fos);
bytesRead = is.read(mybytearray,0,mybytearray.length);
current = bytesRead;
do {
bytesRead =
is.read(mybytearray, current, (mybytearray.length-current));
if(bytesRead >= 0) current += bytesRead;
} while(bytesRead > -1);
bos.write(mybytearray, 0 , current);
bos.flush();
System.out.println("File " + FILE_TO_RECEIVED
+ " downloaded (" + current + " bytes read)");
}
finally {
if (fos != null) fos.close();
if (bos != null) bos.close();
if (sock != null) sock.close();
}
}
Thanks in advance!
There's some function I write for you, check it out.
For generating HASH/Digest:
public byte[] generateHASH(byte[] message) throws Exception {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest.digest(message);
return hash;
}
For Encryption:
public byte[] encrypt(byte[] msg, byte[] key, byte[] iv) throws Exception {
//prepare key
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
//prepare cipher
String cipherALG = "AES/CBC/PKCS5padding"; // use your preferred algorithm
Cipher cipher = Cipher.getInstance(cipherALG);
String string = cipher.getAlgorithm();
//as iv (Initial Vector) is only required for CBC mode
if (string.contains("CBC")) {
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
} else {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
}
byte[] encMessage = cipher.doFinal(msg);
return encMessage;
}
For Decryption:
public byte[] decrypt(byte[] encMsgtoDec, byte[] key, byte[] iv) throws Exception {
//prepare key
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
//prepare cipher
String cipherALG = "AES/CBC/PKCS5padding"; // use your preferred algorithm
Cipher cipher = Cipher.getInstance(cipherALG);
String string = cipher.getAlgorithm();
//as iv (Initial Vector) is only required for CBC mode
if (string.contains("CBC")) {
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
} else {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
}
byte[] decMsg = cipher.doFinal(encMsgtoDec);
return decMsg;
}
Note:
If you use CBC mode, then both encrypt() and decrypt() use same iv, otherwise you don't need iv, and of course key is same for both.
Your key generation process is naive. You better use RSA public key encryption for key exchange or Diffie–Hellman key exchange method for secret key transfer.
I would suggest you to use SSL. The data encryption comes by default. The SSL handshake takes care of generating and exchanging the encryption keys and subsequently encrypting the data from the source and decrypting it at the receiving end. All this happens at the transport layer and the application does not have to bother or do anything explicit except for configuring it to use SSL.