0

I am trying to read a normal text file and encrypt it using AES algorithm and then i am trying to read that encrypted file and decrypt it. Encryption is working fine.But while decrypting,i get an error saying "Input length must be multiple of 16 when decrypting with padded cipher". I dont know what i am doing wrong.Any suggestions would really help me. Thank You.

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class crypt {

    public static void main(String args[]) throws Exception {


        String keyString = "averylongtext!@$@#$#@$#&(&}{23432432432dsfsdf";
        FileWriter fileWriter = null, fileWriter1 = null;
        File enc = new File("C:\\test\\encrypted.txt");
        File dec = new File("C:\\test\\decrypted.txt");
        String path = "C:\\test\\normal_file.txt";
        String path2 = "C:\\test\\encrypted.txt";
        fileWriter = new FileWriter(enc);
        fileWriter1 = new FileWriter(dec);


        String input = readFile(path, StandardCharsets.UTF_8);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] iv = new byte[cipher.getBlockSize()];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        digest.update(keyString.getBytes());
        byte[] key = new byte[16];
        System.arraycopy(digest.digest(), 0, key, 0, key.length);
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] encrypted = cipher.doFinal(input.getBytes());
        System.out.println(new String(encrypted));

   //writing encrypted information to a new file encrypted.txt
        fileWriter.write(new String(encrypted));
        fileWriter.close();

  //reading encrypted information from the file encrypted.txt
  //This part is where the error is
        encrypted = readFile(path2, StandardCharsets.UTF_8).getBytes();
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        System.out.println("decrypted: \n" + new String(decrypted, "UTF-8"));

  //writing the decrypted information to the file decrypted.txt
        fileWriter1.write(new String(decrypted));
        fileWriter1.close();

    }

 //method to read a file
    static String readFile(String path, Charset encoding) throws IOException {
        byte[] encoded = Files.readAllBytes(Paths.get(path));
        return encoding.decode(ByteBuffer.wrap(encoded)).toString();
    }

}
Srinidhi Shankar
  • 311
  • 5
  • 15

1 Answers1

3

Your problem is here:

fileWriter.write(new String(encrypted));

It looks like you have seen my post on reading files, but you haven't grasped the concept of character encoding.

The output of a good cipher are unpredictable bytes covering the range 0 to 255.

Text, on the other hand, comprises characters; to represent them in a digital computer, a different number is assigned to each character glyph. Because different people use different sets of characters, a lot of different coding schemes have been created over the years. Some Western languages only use 7 bits per character. Others use 8 bits, but still don't assign a character to every 8-bit code. Other systems use multiple bytes, but only certain byte sequences represent a valid character.

Why am I telling you this?

Well, when you say new String(encrypted), you are taking a bunch of pseudo-random bytes and trying to convert them to characters using the default character encoding on your system. In general, there will be bytes or byte sequences that don't convert to a character under that encoding. These bytes will be replaced with the character � (U+FFFD, the Unicode "replacement character"). That replacement corrupts the cipher text; different byte sequences are all replaced with the same symbol. When you attempt to decrypt, this missing information causes bad block sizes or padding errors.

Cipher text is not really text. Don't convert it to a String, or write it to a file as text. Use InputStream, OutputStream, and byte[] to handle cipher text. Only treat the information as text before encryption and after decryption.

Your key derivation is not secure either. Read how to use PBKDF2 to securely derive a key from a password.

/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
try (OutputStream fos = Files.newOutputStream(output, StandardOpenOption.CREATE_NEW);
     CipherOutputStream os = new CipherOutputStream(fos, cipher);
     InputStream is = Files.newInputStream(input)) {
  byte[] buffer = new byte[4096];
  while (true) {
    int n = is.read(buffer);
    if (n < 0)
      break;
    os.write(buffer, 0, n);
  }
  os.flush();
}

If you do need to convert cipher text to a String, use an encoding like Base-64 or Base-85. These will allow you to print your cipher text using only US-ASCII characters.

Community
  • 1
  • 1
erickson
  • 265,237
  • 58
  • 395
  • 493