Using password-based encryption on a file in Java - java

I'm trying to encrypt the contents of one file into another file using a passphrase in Java. The file is getting read to a byte array, encrypted to another byte array, and then written to the new file. Unfortunately, when I try to reverse the encryption, the output file gets decrypted as garbage.
I strongly suspect that the issue has to do with generating an identical key every time the same passphrase is used. I wrote a testing method that dumps the key into a file whenever one gets generated. The key is recorded both directly and in encoded form. The former is identical every time, but the latter is always different for some reason.
In all honesty, I don't know a great deal about encryption methods, especially in Java. I only need the data to be moderately secure, and the encryption doesn't have to withstand an attack from anyone with significant time and skills. Thanks in advance to anyone who has advice on this.
Edit: Esailija was kind enough to point out that I was always setting the cipher with ENCRYPT_MODE. I corrected the problem using a boolean argument, but now I'm getting the following exception:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
That sounds to me like the passphrase isn't being used properly. I was under the impression that "PBEWithMD5AndDES" would hash it into a 16 byte code, which most certainly is a multiple of 8. I'm wondering why the key generates and gets used just fine for encryption mode, but then it complains when trying to decrypt under the exact same conditions.
import java.various.stuff;
/**Utility class to encrypt and decrypt files**/
public class FileEncryptor {
//Arbitrarily selected 8-byte salt sequence:
private static final byte[] salt = {
(byte) 0x43, (byte) 0x76, (byte) 0x95, (byte) 0xc7,
(byte) 0x5b, (byte) 0xd7, (byte) 0x45, (byte) 0x17
};
private static Cipher makeCipher(String pass, Boolean decryptMode) throws GeneralSecurityException{
//Use a KeyFactory to derive the corresponding key from the passphrase:
PBEKeySpec keySpec = new PBEKeySpec(pass.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(keySpec);
//Create parameters from the salt and an arbitrary number of iterations:
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 42);
/*Dump the key to a file for testing: */
FileEncryptor.keyToFile(key);
//Set up the cipher:
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
//Set the cipher mode to decryption or encryption:
if(decryptMode){
cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec);
} else {
cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec);
}
return cipher;
}
/**Encrypts one file to a second file using a key derived from a passphrase:**/
public static void encryptFile(String fileName, String pass)
throws IOException, GeneralSecurityException{
byte[] decData;
byte[] encData;
File inFile = new File(fileName);
//Generate the cipher using pass:
Cipher cipher = FileEncryptor.makeCipher(pass, false);
//Read in the file:
FileInputStream inStream = new FileInputStream(inFile);
decData = new byte[(int)inFile.length()];
inStream.read(decData);
inStream.close();
//Encrypt the file data:
encData = cipher.doFinal(decData);
//Write the encrypted data to a new file:
FileOutputStream outStream = new FileOutputStream(new File(fileName + ".encrypted"));
outStream.write(encData);
outStream.close();
}
/**Decrypts one file to a second file using a key derived from a passphrase:**/
public static void decryptFile(String fileName, String pass)
throws GeneralSecurityException, IOException{
byte[] encData;
byte[] decData;
File inFile = new File(fileName);
//Generate the cipher using pass:
Cipher cipher = FileEncryptor.makeCipher(pass, true);
//Read in the file:
FileInputStream inStream = new FileInputStream(inFile);
encData = new byte[(int)inFile.length()];
inStream.read(encData);
inStream.close();
//Decrypt the file data:
decData = cipher.doFinal(encData);
//Write the decrypted data to a new file:
FileOutputStream target = new FileOutputStream(new File(fileName + ".decrypted.txt"));
target.write(decData);
target.close();
}
/**Record the key to a text file for testing:**/
private static void keyToFile(SecretKey key){
try {
File keyFile = new File("C:\\keyfile.txt");
FileWriter keyStream = new FileWriter(keyFile);
String encodedKey = "\n" + "Encoded version of key: " + key.getEncoded().toString();
keyStream.write(key.toString());
keyStream.write(encodedKey);
keyStream.close();
} catch (IOException e) {
System.err.println("Failure writing key to file");
e.printStackTrace();
}
}
}

You are using the Cipher.ENCRYPT_MODE for both, decrypting and encrypting. You should use Cipher.DECRYPT_MODE for decrypting the file.
That has been fixed, but your boolean is wrong. It should be true for encrypt and false for decrypt. I would strongly recommend against using false/true as function arguments and always use enum like Cipher.ENCRYPT... moving on
Then you are encrypting to .encrypted file, but trying to decrypt the original plain text file.
Then you are not applying padding to encryption. I am surprised this actually has to be done manually,
but padding is explained here. The padding scheme PKCS5 appeared to be implicitly used here.
This is full working code, writing encrypted file to test.txt.encrypted, and decrypted file to test.txt.decrypted.txt.
Adding padding in encryption and removing it in decryption is explained in the comments.
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class FileEncryptor {
public static void main( String[] args ) {
try {
encryptFile( "C:\\test.txt", "password" );
decryptFile( "C:\\test.txt", "password" );
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//Arbitrarily selected 8-byte salt sequence:
private static final byte[] salt = {
(byte) 0x43, (byte) 0x76, (byte) 0x95, (byte) 0xc7,
(byte) 0x5b, (byte) 0xd7, (byte) 0x45, (byte) 0x17
};
private static Cipher makeCipher(String pass, Boolean decryptMode) throws GeneralSecurityException{
//Use a KeyFactory to derive the corresponding key from the passphrase:
PBEKeySpec keySpec = new PBEKeySpec(pass.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(keySpec);
//Create parameters from the salt and an arbitrary number of iterations:
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 42);
/*Dump the key to a file for testing: */
FileEncryptor.keyToFile(key);
//Set up the cipher:
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
//Set the cipher mode to decryption or encryption:
if(decryptMode){
cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec);
} else {
cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec);
}
return cipher;
}
/**Encrypts one file to a second file using a key derived from a passphrase:**/
public static void encryptFile(String fileName, String pass)
throws IOException, GeneralSecurityException{
byte[] decData;
byte[] encData;
File inFile = new File(fileName);
//Generate the cipher using pass:
Cipher cipher = FileEncryptor.makeCipher(pass, true);
//Read in the file:
FileInputStream inStream = new FileInputStream(inFile);
int blockSize = 8;
//Figure out how many bytes are padded
int paddedCount = blockSize - ((int)inFile.length() % blockSize );
//Figure out full size including padding
int padded = (int)inFile.length() + paddedCount;
decData = new byte[padded];
inStream.read(decData);
inStream.close();
//Write out padding bytes as per PKCS5 algorithm
for( int i = (int)inFile.length(); i < padded; ++i ) {
decData[i] = (byte)paddedCount;
}
//Encrypt the file data:
encData = cipher.doFinal(decData);
//Write the encrypted data to a new file:
FileOutputStream outStream = new FileOutputStream(new File(fileName + ".encrypted"));
outStream.write(encData);
outStream.close();
}
/**Decrypts one file to a second file using a key derived from a passphrase:**/
public static void decryptFile(String fileName, String pass)
throws GeneralSecurityException, IOException{
byte[] encData;
byte[] decData;
File inFile = new File(fileName+ ".encrypted");
//Generate the cipher using pass:
Cipher cipher = FileEncryptor.makeCipher(pass, false);
//Read in the file:
FileInputStream inStream = new FileInputStream(inFile );
encData = new byte[(int)inFile.length()];
inStream.read(encData);
inStream.close();
//Decrypt the file data:
decData = cipher.doFinal(encData);
//Figure out how much padding to remove
int padCount = (int)decData[decData.length - 1];
//Naive check, will fail if plaintext file actually contained
//this at the end
//For robust check, check that padCount bytes at the end have same value
if( padCount >= 1 && padCount <= 8 ) {
decData = Arrays.copyOfRange( decData , 0, decData.length - padCount);
}
//Write the decrypted data to a new file:
FileOutputStream target = new FileOutputStream(new File(fileName + ".decrypted.txt"));
target.write(decData);
target.close();
}
/**Record the key to a text file for testing:**/
private static void keyToFile(SecretKey key){
try {
File keyFile = new File("C:\\keyfile.txt");
FileWriter keyStream = new FileWriter(keyFile);
String encodedKey = "\n" + "Encoded version of key: " + key.getEncoded().toString();
keyStream.write(key.toString());
keyStream.write(encodedKey);
keyStream.close();
} catch (IOException e) {
System.err.println("Failure writing key to file");
e.printStackTrace();
}
}
}

These are some improvements to the #Esailija 's answer given some new features in Java.
By using the CipherInputStream and CipherOutputStream classes, the length and complexity of the code is greatly reduced.
I also use char[] instead of String for the password.
You can use System.console().readPassword("input password: ") to get the password as a char[] so that it is never a String.
public static void encryptFile(String inFileName, String outFileName, char[] pass) throws IOException, GeneralSecurityException {
Cipher cipher = PasswordProtectFile.makeCipher(pass, true);
try (CipherOutputStream cipherOutputStream = new CipherOutputStream(new FileOutputStream(outFileName), cipher);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inFileName))) {
int i;
while ((i = bis.read()) != -1) {
cipherOutputStream.write(i);
}
}
}
public static void decryptFile(String inFileName, String outFileName, char[] pass) throws GeneralSecurityException, IOException {
Cipher cipher = PasswordProtectFile.makeCipher(pass, false);
try (CipherInputStream cipherInputStream = new CipherInputStream(new FileInputStream(inFileName), cipher);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFileName))) {
int i;
while ((i = cipherInputStream.read()) != -1) {
bos.write(i);
}
}
}
private static Cipher makeCipher(char[] pass, Boolean decryptMode) throws GeneralSecurityException {
// Use a KeyFactory to derive the corresponding key from the passphrase:
PBEKeySpec keySpec = new PBEKeySpec(pass);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(keySpec);
// Create parameters from the salt and an arbitrary number of iterations:
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 43);
// Set up the cipher:
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
// Set the cipher mode to decryption or encryption:
if (decryptMode) {
cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec);
} else {
cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec);
}
return cipher;
}

Related

RSA with AES encryption and decryption

What is the problem of my decryption for RSA?
here is the code for encryption :
try {
//Get the public key from the keyStore and set up the Cipher object
PublicKey publicKey = getPubKey(keyStore,keyName);
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
//Read the plainText
System.out.println("Loading plaintext file: "+inFile);
RandomAccessFile rawDataFromFile = new RandomAccessFile(inFile, "r");
byte[] plainText = new byte[(int)rawDataFromFile.length()];
rawDataFromFile.read(plainText);
// Generate a symmetric key to encrypt the data and initiate the AES Cipher Object
System.out.println("Generating AES key");
KeyGenerator sKenGen = KeyGenerator.getInstance("AES");
Key aesKey = sKenGen.generateKey();
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
// Encrypt the symmetric AES key with the public RSA key
System.out.println("Encrypting Data");
byte[] encodedKey = rsaCipher.doFinal(aesKey.getEncoded());
// Encrypt the plaintext with the AES key
byte[] cipherText = aesCipher.doFinal(plainText);
//Write the encrypted AES key and Ciphertext to the file.
System.out.println("Writting to file: "+outFile);
FileOutputStream outToFile = new FileOutputStream(outFile);
outToFile.write(encodedKey);
outToFile.write(cipherText);
System.out.println("Closing Files");
rawDataFromFile.close();
outToFile.close();
}
catch (Exception e) {
System.out.println("Doh: "+e);
}
and here is my code for decryption, i thought it will work quite well but it didnt. anyone can help me ?
it kept having the error : javax.crypto.BadPaddingException: Decryption error
dont really know what to do, anyone can give me some advices?
private static void decryptRSA() {
try {
System.out.println("Loading plaintext file: "+inFile);
RandomAccessFile rawDataFromFile = new RandomAccessFile(inFile, "r");
byte[] cipherText = new byte[(int)rawDataFromFile.length()];
byte encodedkey[] = new byte[256];
rawDataFromFile.read(encodedkey, 0, 256);
rawDataFromFile.read(cipherText);
PublicKey publicKey = getPubKey(keyStore,keyName);
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] aeskey = rsaCipher.doFinal(encodedkey);
SecretKeySpec aesKey = new SecretKeySpec(aeskey, "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
byte[] plainText = aesCipher.doFinal(cipherText);
System.out.println("Writting to file: "+outFile);
FileOutputStream outToFile = new FileOutputStream(outFile);
outToFile.write(plainText);
System.out.println("Closing Files");
rawDataFromFile.close();
outToFile.close();
}
catch (Exception e) {
System.out.println("Doh: "+e);
}
}
RSA decryption is done with the private key, not the public key.
The length of the cipherText array in the decryption code isn't correct. You should subtract 256, or pass the actual read length to Cipher.doFinal(), or in fact both.
NB Despite the message you're printing, your decrypt step is really reading from the ciphertext file, not the plaintext file.
package rsa;
import java.math.BigInteger;
import java.util.Scanner;
public class RSA {
public static void main(String[] args) {
// TODO code application logic here
Scanner sc=new Scanner(System.in);
System.out.println("Enter Plaintext");
String plaintext=sc.nextLine();
BigInteger n,n1,n2,M=BigInteger.valueOf(0),M1,p,q,pn,e,d,c;
System.out.println("Enter p q");
p=sc.nextBigInteger();
q=sc.nextBigInteger();
n=p.multiply(q);
n1=p.subtract(BigInteger.valueOf(1));
n2=q.subtract(BigInteger.valueOf(1));
pn=n1.multiply(n2);
System.out.println("Choose e");
e=sc.nextBigInteger();
d=e.modInverse(pn);
System.out.println("D is"+d);
plaintext=plaintext.toLowerCase();
char arr[]=new char[]{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
if(plaintext.length()%2!=0)
plaintext=plaintext+"a";
String cc="",s="",plain="",t="";
int z;
for(int i=0;i<plaintext.length();i=i+2)
{
z=plaintext.codePointAt(i)-87;
s=s+z;
z=plaintext.codePointAt(i+1)-87;
s=s+z;
M=BigInteger.valueOf(Long.parseLong(s));
t=t+M.toString();
c=M.pow(e.intValue());
c=c.mod(n);
cc=cc+c;
s="";
M1=c.pow(d.intValue());
M1=M1.mod(n);
plain=plain+M1.toString();
}
System.out.println("Plaintext is "+plaintext);
System.out.println("Before Encryption "+t);
System.out.println("cipher "+cc);
System.out.println("First Decryption "+plain);
String h="";
for(int i=0;i<plain.length();i=i+2)
{
int k=Integer.parseInt(Character.toString(plain.charAt(i))+Character.toString(plain.charAt(i+1)));
h=h+arr[k-10];
}
System.out.println("Decryption "+h);
}
}

Java: write and read password based encrypted private key

I am trying to read a password based encrypted private key from a file, but I'm getting the following exception:
java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big.
at sun.security.util.DerInputStream.getLength(DerInputStream.java:561)
at sun.security.util.DerValue.init(DerValue.java:365)
at sun.security.util.DerValue.<init>(DerValue.java:294)
at javax.crypto.EncryptedPrivateKeyInfo.<init>(EncryptedPrivateKeyInfo.java:84) ...
This is how I encrypt and write to file the private key:
public static void savePrivateKeyToDisk(PrivateKey privateKey, String passord){
try {
// unencrypted PKCS#8 private key
byte[] encodedPrivateKey = privateKey.getEncoded();
String MYPBEALG = "PBEWithSHA1AndDESede";
int count = 20;
SecureRandom random = new SecureRandom();
byte[] salt = new byte[8];
random.nextBytes(salt);
// Create PBE parameter set
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(MYPBEALG);
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
Cipher pbeCipher = Cipher.getInstance(MYPBEALG);
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
// Encrypt the encoded Private Key with the PBE key
byte[] cipherText = pbeCipher.doFinal(encodedPrivateKey);
// Now construct PKCS #8 EncryptedPrivateKeyInfo object
AlgorithmParameters algparms = AlgorithmParameters.getInstance(MYPBEALG);
algparms.init(pbeParamSpec);
EncryptedPrivateKeyInfo encinfo = new EncryptedPrivateKeyInfo(algparms, cipherText);
// DER encoded PKCS#8 encrypted key
byte[] encryptedPkcs8 = encinfo.getEncoded();
File encryptedPrivate = new File(PRIVATE_KEY_FILE);
if (encryptedPrivate.getParentFile() != null) {
encryptedPrivate.getParentFile().mkdirs();
}
encryptedPrivate.createNewFile();
ObjectOutputStream publicKeyOS = new ObjectOutputStream(
new FileOutputStream(encryptedPrivate));
publicKeyOS.writeObject(encryptedPkcs8);
publicKeyOS.close();
}
catch (Exception e){
e.printStackTrace();
}
}
... and this is how I'm trying to read the encrypted private key:
public static PrivateKey getPrivateKey(String passwd){
try {
byte[] encodedPrivateKey = getFileBytes(PRIVATE_KEY_FILE);
// exception thrown from here
EncryptedPrivateKeyInfo encryptPKInfo = new EncryptedPrivateKeyInfo(encodedPrivateKey);
Cipher cipher = Cipher.getInstance(encryptPKInfo.getAlgName());
PBEKeySpec pbeKeySpec = new PBEKeySpec(passwd.toCharArray());
SecretKeyFactory secFac = SecretKeyFactory.getInstance(encryptPKInfo.getAlgName());
Key pbeKey = secFac.generateSecret(pbeKeySpec);
AlgorithmParameters algParams = encryptPKInfo.getAlgParameters();
cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
KeySpec pkcs8KeySpec = encryptPKInfo.getKeySpec(cipher);
KeyFactory kf = KeyFactory.getInstance(ALGORITHM);
return kf.generatePrivate(pkcs8KeySpec);
}
catch (Exception e){
e.printStackTrace();
return null;
}
}
... the getFileBytes method:
private static byte[] getFileBytes(String infile){
File f = new File(infile) ;
int sizecontent = ((int) f.length());
byte[] data = new byte[sizecontent];
try
{
FileInputStream freader = new FileInputStream(f);
freader.read(data, 0, sizecontent) ;
freader.close();
return data;
}
catch(IOException ioe)
{
System.out.println(ioe.toString());
return null;
}
}
It seems like the encrypted private key is not in the right format, but I save it in DER PKCS#8 format.
So, the question: What is the mistake in this code?
I guess the problem is that you write an Object, but then you read byte[] (not an Object)
I would suggest that you either read the whole object and then get the required bytes or even better write byte[] directly (don't use ObjectOutputStream) and then load these bytes, eg:
FileOutputStream fos = new FileOutputStream(PRIVATE_KEY_FILE);
fos.write(myByteArray);
fos.close();
and then to retrieve it:
byte[] bytes = Files.readAllBytes(Paths.get(PRIVATE_KEY_FILE));

Android decryption: Error while finalizing cipher

I am using Android to encrypt and encrypt images sent between apps.
The encryption works well but when the file arrives at the destination it will not decrypt. Now I have copied the file at the destination app and decrypted it successfully using 3rd-party software.
The error I get is:"Error while finalizing cipher" at CipherInputStream (CipherInputStream.java:107) caused by IllegalBlockSizeException.
The encryption & decryption code is below:
public static String encrypt(String plainFile, String encryptedFile) throws IOException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException {
// Here you read the cleartext.
File extStore = Environment.getExternalStorageDirectory();
FileInputStream fis = new FileInputStream(plainFile);
// This stream write the encrypted text. This stream will be wrapped by
// another stream.
FileOutputStream fos = new FileOutputStream(encryptedFile);
// Length is 16 byte
SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(), "AES");
// Create cipher
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks);
// Wrap the output stream
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
// Write bytes
int b;
byte[] d = new byte[8];
while ((b = fis.read(d)) != -1) {
cos.write(d, 0, b);
}
// Flush and close streams.
cos.flush();
cos.close();
fis.close();
return encryptedFile;
}
static String decrypt(String plainFile, String encryptedFile) throws IOException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException {
File encFile=new File(encryptedFile);
FileInputStream fis = new FileInputStream(encFile);
FileOutputStream fos = new FileOutputStream(plainFile);
SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(),
"AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks);
CipherInputStream cis = new CipherInputStream(fis, cipher);
int b;
byte[] d = new byte[8];
while ((b = cis.read(d)) != -1) {
fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();
return plainFile;
}
Any ideas? Thanks!
Ronan
Update:
The received encrypted file is consistently 1 byte smaller that the original file which seems to be generating the error. The error re block size is triggered at the code line
while ((b = fis.read(d)) != -1) { in the decrypt function.
Update:
Thanks for the feedback. The ultimate solution is as defined at last block incomplete with CipherInputStream/CipherOutputStream, even with padding AES/CBC/PKCS5Padding
Ronan

Cannot decrypt the encrypted file?

I tried to encrypt my file by this way:
Encrypt:
static void encrypt(String strInput , String strOutput) throws IOException,
NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException {
FileInputStream fis = new FileInputStream(strInput);
FileOutputStream fos = new FileOutputStream(strOutput);
SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(),
"AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks);
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
int b;
byte[] d = new byte[8];
while ((b = fis.read(d)) != -1) {
cos.write(d, 0, b);
}
// Flush and close streams.
cos.flush();
cos.close();
fis.close();
}
and decrypt it back by:
Decrypt:
static String decrypt(String strInput) throws IOException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException {
FileInputStream fis = new FileInputStream(strInput);
int endFile = strInput.length() - 4;
String strOut = strInput.substring(0, endFile) + "xx.jpg";
FileOutputStream fos = new FileOutputStream(strOut);
SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(),
"AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks);
CipherInputStream cis = new CipherInputStream(fis, cipher);
int b;
byte[] d = new byte[8];
while ((b = cis.read(d)) != -1) {
fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();
return strOut;
}
However, the result file's size is 0 kb and when I tried to troubleshoot b = cis.read(d) in decrypt, always returns -1, also cis.available() always returns 0. Can anyone advise me which part of my code is wrong?
Note: I can ensure that the file that is going to be decrypted is always exist.
I believe that this problem is because you are trying to decrypt data that is not encrypted (or not properly encrypted).
In your decrypt() method, the CipherOutputStream hides all exception that the Cipher class may be throwing. See javadoc for CipherOutputStream:
Moreover, this class catches all exceptions that are not thrown by its ancestor classes.
To expose the problem, you may want to implement the cipher usage manually. Here is a quick example:
static String decrypt(String strInput) throws IOException,
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
FileInputStream fis = new FileInputStream(strInput);
int endFile = strInput.length() - 4;
String strOut = strInput.substring(0, endFile) + "xx.txt";
FileOutputStream fos = new FileOutputStream(strOut);
SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks);
int b;
byte[] d = new byte[8];
while ((b = fis.read(d)) != -1) {
fos.write(cipher.update(d));
}
fos.write(cipher.doFinal());
fos.flush();
fos.close();
fis.close();
return strOut;
}
The algorithm you posted in your question seems to work fine for valid inputs. For example, let`s assume the following main:
public static void main(String[] argv) {
try {
encrypt("test.txt", "XXX.txt");
decrypt("XXX.txt");
}
catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
}
Using this, and testing both with a text file and a JPG file, your algorithms executed flawlessly. However, when using an invalid input to the decryption algorithm, then the problem you described started to appear.
For testing, lets imagine that we make the "mistake" of trying to decrypt the file that was in clear like so (just changing the parameter passed to decrypt() in the main):
encrypt("test.txt", "XXX.txt");
decrypt("test.txt");
Then of course the padding on the input to the decrypt() method will be wrong and we should get an exception.
Using your version of decrypt()however, there is no exception. All we get is an empty file.
Using the modified version fo the decrypt() method that is shown above we get the following exception:
javax.crypto.BadPaddingException: Given final block not properly padded
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:1970)
at MainTest.decrypt(MainTest.java:71)
at MainTest.main(MainTest.java:21)

getting javax.crypto.IllegalBlockSizeException error when decrypting

I'm trying to encrypt/decrypt text from a file but i am receiving the following error:
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
I'm using AES 128 bit with AES/ECB/PKCS5Padding. Any idea why I am getting this error?
Here is my code:
public class AES_Encryption {
public static void main(String[] args) throws Exception {
String str = new Scanner(new File("src//plainText.txt")).useDelimiter("\\Z").next();
FileWriter fstream = new FileWriter("src//cipherText.txt");
BufferedWriter out = new BufferedWriter(fstream);
FileWriter fstream2 = new FileWriter("src//decrpytedText.txt");
BufferedWriter out2 = new BufferedWriter(fstream2);
System.out.println("" + str);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
Key key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = cipher.doFinal(str.getBytes());
String ct = new String(cipherText);
System.out.println( new String(cipherText, "UTF8") );
out.append(ct);
out.close();
String cipherT = new Scanner(new File("src//cipherText.txt")).useDelimiter("\\Z").next();
cipher.init(Cipher.DECRYPT_MODE, key);
//byte[] decVal = Base64.decode(cipherT.getBytes());
byte[] newPlainText = cipher.doFinal(cipherT.getBytes());
String dt = new String(newPlainText, "UTF8");
out2.append(dt);
out2.close();
}
}
Your error is treating the ciphertext as a string:
String ct = new String(cipherText); // <--- Noooo!
There will be values in your byte array that cannot be expressed as characters in the default charset.
Always treat your ciphertext as a byte array, even when reading or writing to files.
One get the IllegalBlockSizeException in following case as mentioned in Cipher API documentaion:
IllegalBlockSizeException - If this cipher is a block cipher, no padding has been requested (only in encryption mode), and the total input length of the data
processed by this cipher is not a multiple of block size
In Your case you are Encrypting the String correctly , But while decryption you are treating the cipherText as String and then
you are putting cipherT.getBytes() byte array in doFinal method of Cipher. The byte array conversion of String is not same as reading byte array from the file in binary mode.
The functionality and limitation of String.toBytes() as mentioned in String API documentaion is as follows:
Encodes this String into a sequence of bytes using the platform's default charset, storing the result into a new byte array. The
behavior of this method when this string cannot be encoded in the
default charset is unspecified. The CharsetEncoder class should be
used when more control over the encoding process is required.
What I suggest for you is to read the cipherText.txt File in binary mode and then put the byte array you got after reading the file in doFinal method of Cipher . I have modified your code in following way:
public class AES_Encryption {
public static void main(String[] args) throws Exception {
String str = new Scanner(new File("plainText.txt")).useDelimiter("\\t").next();
FileOutputStream fstream = new FileOutputStream("cipherText.txt");
BufferedOutputStream out = new BufferedOutputStream(fstream);
FileOutputStream fstream2 = new FileOutputStream("decrpytedText.txt");
BufferedOutputStream out2 = new BufferedOutputStream(fstream2);
System.out.println("INPUT String:\n" + str);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
Key key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = cipher.doFinal(str.getBytes());
System.out.println("ENCRYPTED String:\n"+new String(cipherText, "UTF8") );
out.write(cipherText);
out.flush();
out.close();
//String cipherT = new Scanner(new File("cipherText.txt")).nextLine();
BufferedInputStream bfin = new BufferedInputStream(new FileInputStream(new File("cipherText.txt")));//To read the file in Binary Mode.
cipher.init(Cipher.DECRYPT_MODE, key);
int BUFFERSIZE = 1024;
byte[] readBytes = new byte[BUFFERSIZE];
byte[] data = null;
int totalRead = -1;
while( (totalRead = bfin.read(readBytes))!=-1)
{
byte[] temp = new byte[(data == null ? totalRead : data.length)];
System.arraycopy((data==null ? readBytes : data),0,temp,0, temp.length);
data = new byte[(data == null ? 0 : data.length) + totalRead];
System.arraycopy(temp, 0, data, 0, temp.length);
System.arraycopy(readBytes, 0, data, data.length - temp.length, totalRead);
}
if (data!=null)
{
byte[] newPlainText = cipher.doFinal(data);
out2.write(newPlainText);
out2.flush();
System.out.println("DECRYPTED String:\n"+new String(newPlainText,"UTF8"));
}
else
{
System.out.println("No Data Found");
}
//String dt = new String(newPlainText, "UTF8");
out2.close();
}
}
I hope this would help you in resolving the exception you getting ...

Categories