I'm trying to create a proof of concept of file encryption with spring security crypto. Per some of the other posts I've found I have downloaded the Unlimited JCE Policy and added the jars to my java_home/jre/lib/security folder.
The error occurs when calling bytesEncryptor.decrypt. The encrypt may be working as there is encrypted content in the file after execution but I'm not sure of a way to confirm that's not where the problem lies. Some other posts about this error that aren't using Spring are saying the key is not right but this can't be because I'm using the same bytesEncryptor object? (Given final block not properly padded)
Stack:
Exception in thread "main" java.lang.IllegalStateException: Unable to invoke Cipher due to bad padding
at org.springframework.security.crypto.encrypt.CipherUtils.doFinal(CipherUtils.java:142)
at org.springframework.security.crypto.encrypt.AesBytesEncryptor.decrypt(AesBytesEncryptor.java:128)
at com.test.encryption.MyTest.crypt(MyTest.java:45)
at com.test.encryption.MyTest.decryptFile(MyTest.java:31)
at com.test.encryption.MyTest.main(MyTest.java:21)
Caused by: 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:2087)
at org.springframework.security.crypto.encrypt.CipherUtils.doFinal(CipherUtils.java:135)
... 4 more
Code:
public static void main(String args[]) throws Exception {
String salt = KeyGenerators.string().generateKey();
BytesEncryptor bytesEncryptor = Encryptors.standard("password", salt);
encryptFile(bytesEncryptor, "C:/test/testIn.txt", "C:/test/testOut.txt");
decryptFile(bytesEncryptor, "C:/test/testOut.txt", "C:/test/testOutDecode.txt");
}
private static void encryptFile (BytesEncryptor bytesEncryptor, String in, String out) throws Exception {
crypt(bytesEncryptor, in, out, true);
}
private static void decryptFile (BytesEncryptor bytesEncryptor, String in, String out) throws Exception {
crypt(bytesEncryptor, in, out, false);
}
private static void crypt (BytesEncryptor bytesEncryptor, String in, String out, boolean encrypt) throws Exception {
byte[] buffer = new byte[1024];
int numRead;
byte[] bytes = null;
InputStream input = new FileInputStream(in);
OutputStream output = new FileOutputStream(out);
while ((numRead = input.read(buffer)) > 0) {
if(encrypt) {
bytes = bytesEncryptor.encrypt(buffer);
} else {
bytes = bytesEncryptor.decrypt(buffer);
}
if (bytes != null) {
output.write(bytes, 0, numRead);
}
}
input.close();
output.close();
}
For what it's worth, I got this message, when I was using the wrong password/salt to decrypt (same length though)
I suspect the problem comes from they way you read your file in chunks of 1024 bytes. AES is a block cipher so operates on blocks of data of a certain size. When it enciphers it will pad output to make sure it fits into chunks of the appropriate size. When you give it data to decipher it expects the data to be similarly padded if necessary.
Try reading the whole file into a byte array, then encrypting and decrypting that. Alternatively you could try and use a chunk size matching the block size you are using for AES (it will be 128, 192 or 256 bits).
Turns out you need to use a CipherInputStream and CipherOutputStream for FileInputStreams and FileOutputStreams.
Below is more or less taken from this http://www.programcreek.com/java-api-examples/index.php?source_dir=cube-master/cube-common/src/main/java/ch/admin/vbs/cube/common/crypto/AESEncrypter.java
Updated code:
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
public class MyTest {
private static String algorithm = "AES/CBC/PKCS5Padding";
public static void main(String args[]) throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
final int AES_KEYLENGTH = 256;
byte[] iv = new byte[AES_KEYLENGTH / 16];
SecureRandom prng = new SecureRandom();
prng.nextBytes(iv);
Cipher aesCipherForEncryption = Cipher.getInstance(algorithm);
Cipher aesCipherForDecryption = Cipher.getInstance(algorithm);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
encryptFile(aesCipherForEncryption, "C:/test/testIn.txt", "C:/test/testOut.txt");
decryptFile(aesCipherForDecryption, "C:/test/testOut.txt", "C:/test/testOutDecode.txt");
}
private static void encryptFile (Cipher cipher, String in, String out) throws Exception {
crypt(cipher, in, out, true);
}
private static void decryptFile (Cipher cipher, String in, String out) throws Exception {
crypt(cipher, in, out, false);
}
private static void crypt (Cipher cipher, String in, String out, boolean encrypt) throws Exception {
byte[] buffer = new byte[256];
int numRead;
InputStream input = new FileInputStream(in);
OutputStream output = new FileOutputStream(out);
if(encrypt) {
output = new CipherOutputStream(output, cipher);
} else {
input = new CipherInputStream(input, cipher);
}
while ((numRead = input.read(buffer)) >= 0) {
output.write(buffer, 0, numRead);
}
input.close();
output.close();
}
}
Note - I did end up going away from using the Spring Security Crypto implementation.
Related
This is my full code:
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Test {
public static void main(String[] args) throws Exception {
encrypt();
decrypt();
}
void encrypt() throws Exception {
Path file = Paths.get("path/to/file");
Path backupFile = file.getParent().resolve(file.getFileName().toString() + ".bak");
Files.deleteIfExists(backupFile);
Files.copy(file, backupFile);
SecureRandom secureRandom = new SecureRandom();
byte[] initializeVector = new byte[96 / Byte.SIZE];
secureRandom.nextBytes(initializeVector);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec p = new GCMParameterSpec(128, initializeVector);
try (FileChannel src = FileChannel.open(backupFile, READ);
FileChannel dest = FileChannel.open(file, WRITE, TRUNCATE_EXISTING)) {
SecretKeySpec secretKeySpec =
new SecretKeySpec(MessageDigest.getInstance("MD5").digest(new byte[]{0x00}), "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, p);
ByteBuffer ivBuffer = ByteBuffer.allocate(Integer.BYTES + cipher.getIV().length);
ivBuffer.putInt(cipher.getIV().length);
ivBuffer.put(cipher.getIV());
ivBuffer.flip();
dest.write(ivBuffer);
ByteBuffer readBuf = ByteBuffer.allocateDirect(8192);
ByteBuffer writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
while (src.read(readBuf) >= 0) {
if (cipher.getOutputSize(8192) > writeBuf.capacity()) {
writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
}
readBuf.flip();
cipher.update(readBuf, writeBuf);
writeBuf.flip();
dest.write(writeBuf);
readBuf.clear();
writeBuf.clear();
}
if (cipher.getOutputSize(0) > writeBuf.capacity()) {
writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(0));
}
cipher.doFinal(ByteBuffer.allocate(0), writeBuf);
writeBuf.flip();
dest.write(writeBuf);
Files.delete(backupFile);
} catch (ShortBufferException e) {
//Should not happen!
throw new RuntimeException(e);
}
}
void decrypt() throws Exception {
Path file = Paths.get("path/to/file");
Path backupFile = file.getParent().resolve(file.getFileName().toString() + ".bak");
Files.deleteIfExists(backupFile);
Files.copy(file, backupFile);
try (FileChannel src = FileChannel.open(backupFile, READ);
FileChannel dest = FileChannel.open(file, WRITE, TRUNCATE_EXISTING)) {
ByteBuffer ivLengthBuffer = ByteBuffer.allocate(Integer.BYTES);
src.read(ivLengthBuffer);
ivLengthBuffer.flip();
int ivLength = ivLengthBuffer.getInt();
ByteBuffer ivBuffer = ByteBuffer.allocate(ivLength);
src.read(ivBuffer);
ivBuffer.flip();
byte[] iv = new byte[ivBuffer.limit()];
ivBuffer.get(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec p = new GCMParameterSpec(128, iv);
SecretKeySpec secretKeySpec =
new SecretKeySpec(MessageDigest.getInstance("MD5").digest(new byte[]{0x00}), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, p);
ByteBuffer readBuf = ByteBuffer.allocateDirect(8192);
ByteBuffer writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
while (src.read(readBuf) >= 0) {
if (cipher.getOutputSize(8192) > writeBuf.capacity()) {
writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
}
readBuf.flip();
cipher.update(readBuf, writeBuf);
writeBuf.flip();
dest.write(writeBuf);
readBuf.clear();
writeBuf.clear();
}
if (cipher.getOutputSize(0) > writeBuf.capacity()) {
writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(0));
}
cipher.doFinal(ByteBuffer.allocate(0), writeBuf);
writeBuf.flip();
dest.write(writeBuf);
Files.deleteIfExists(backupFile);
}
}
}
I found a strange issue: if the original file (unencrypted) is bigger than 4KB, upon decrypting, cipher.update(readBuf, writeBuf) will write nothing to the buffer, cipher.doFinal(ByteBuffer.allocate(0), writeBuf) also write nothing, and finally I get my data lost. Every calling to cipher.getOutputSize(8192), increases the result, I don't know why it happen but it may help.
Why is it happening and how can I fix it?
.update() is easy; SunJCE implements the GCM (and CCM) requirement that authenticated decryption not release (any) plaintext if the authentication fails; see How come putting the GCM authentication tag at the end of a cipher stream require internal buffering during decryption? and https://moxie.org/blog/the-cryptographic-doom-principle/ . Because the tag is at the end of the ciphertext, this means it must buffer all the ciphertext until (one of the overloads of) doFinal() is called. (This is why for a large file your reallocation of writeBuf to cipher.getOutputSize(8192) keeps growing as you keep reading and buffering more data.)
.doFinal() is harder; it is supposed to work. However, I've narrowed down the failure: it only happens when you use ByteBuffers not raw byte[] arrays -- which is implemented in javax.crypto.CipherSpi.bufferCrypt rather than dispatching to the implementation class; and the output ByteBuffer has no backing array (i.e. was direct-allocated); and the plaintext is more than 4096 bytes. I'll try to look deeper into why this fails, but in the meantime changing either of the first two fixes it (or limiting your data to 4096 bytes, but presumably you don't want that).
I am developing a GUI based encryptor/decryptor based on AES-128 bit symmetric encryption.
My problem is that how to determine that decryption has not occurred and show a dialog box "Decryption Failed". The code I have written would always generate a file without a .enc extension regardless of the fact that it is still encrypted !
Hoping to get a answer as always from Stack Overflow's top notch programmers :)
Do note that the decryption process doesn't fail or throws exception ! It's just the fact that it generates a file that's still not decrypted. That we have to stop and that's what I meant !
Code here: (Sorry for bad indentation !)
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.MessageDigest;
public class FileEncryptor{
private String algo;
private String path;
private String password;
public FileEncryptor(String algo,String path, String password) {
this.algo = algo; //setting algo
this.path = path;//setting file path
this.password = password;
}
public void encrypt() throws Exception{
SecureRandom padding = new SecureRandom();
byte[] salt = new byte[16];
padding.nextBytes(salt);
//generating key
byte k[] = password.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
k = sha.digest(k);
k = Arrays.copyOf(k, 16);
SecretKeySpec key = new SecretKeySpec(k,algo);
//creating and initialising cipher and cipher streams
Cipher encrypt = Cipher.getInstance(algo);
encrypt.init(Cipher.ENCRYPT_MODE, key);
//opening streams
FileOutputStream fos =new FileOutputStream(path+".enc");
try(FileInputStream fis =new FileInputStream(path)){
try(CipherOutputStream cout=new CipherOutputStream(fos, encrypt)){
copy(fis,cout);
}
}
}
public void decrypt() throws Exception{
SecureRandom padding = new SecureRandom();
byte[] salt = new byte[16];
padding.nextBytes(salt);
//generating same key
byte k[] = password.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
k = sha.digest(k);
k = Arrays.copyOf(k, 16);
SecretKeySpec key = new SecretKeySpec(k,algo);
//creating and initialising cipher and cipher streams
Cipher decrypt = Cipher.getInstance(algo);
decrypt.init(Cipher.DECRYPT_MODE, key);
//opening streams
FileInputStream fis = new FileInputStream(path);
try(CipherInputStream cin=new CipherInputStream(fis, decrypt)){
try(FileOutputStream fos =new FileOutputStream(path.substring(0,path.lastIndexOf(".")))){
copy(cin,fos);
}
}
}
private void copy(InputStream is,OutputStream os) throws Exception{
byte buf[] = new byte[4096]; //4K buffer set
int read = 0;
while((read = is.read(buf)) != -1) //reading
os.write(buf,0,read); //writing
}
public static void main (String[] args)throws Exception {
System.out.println("Enter Password: ");
new FileEncryptor("AES","sample.txt",new java.util.Scanner(System.in).nextLine()).encrypt();
new FileEncryptor("AES","sample.txt.enc",new java.util.Scanner(System.in).nextLine()).decrypt();
}
}
Without looking at the API calls, the decrypt methods should throw an exception if an error occurs. In your exception handler, you can set a flag that will allow you to display an error message. You can also delay the decrypted file creation till after successful decryption (or at least till after the first block has been successfully decrypted). If decryption then fails further along the line, you can delete the (essentially temporary) decrypted output file and display the error message.
[edit]
I slightly misunderstood the original post, so some suggestions to check for failed decryption (note that these are higher level than AES, so it might be specific to your application only):
Add a checksum to the plaintext data before encryption
Append other metadata (file size, user, date, etc) to the plaintext, and check for these when decrypting
Usually, a padding exception would occur on decryption - check for these (and any other giveaways)
Use PKI (public key infrastructure) functionality such as signatures (this is outside the scope of this answer, and possibly outside the scope of the problem you're trying to solve)
I suggest appending a constant, rather than a checksum, to your data before encryption, and verifying it after encryption.
And the encryption algorithm should use chaining, that means avoid ECB (see here why: http://bobnalice.wordpress.com/2009/01/28/friends-don%E2%80%99t-let-friends-use-ecb-mode-encryption).
Using a constant with chaining, is nearly as good as a checksum and much simpler.
For all haters, I READ MANY topics like this one, and non of them was helpful.
eg. here javax.crypto.BadPaddingException: Given final block not properly padded error while decryption or here Given final block not properly padded
I want to encrypt and then decrypt Strings. Read many topics about
"Given final block not properly padded" exception, but non of these solutions worked.
My Class:
package aes;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.JOptionPane;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
public class EncryptionExample {
private static SecretKeySpec key;
private static IvParameterSpec ivSpec;
private static Cipher cipher;
private static byte[] keyBytes;
private static byte[] ivBytes;
private static int enc_len;
public static void generateKey() throws Exception
{
String complex = new String ("9#82jdkeo!2DcASg");
keyBytes = complex.getBytes();
key = new SecretKeySpec(keyBytes, "AES");
complex = new String("#o9kjbhylK8(kJh7"); //just some randoms, for now
ivBytes = complex.getBytes();
ivSpec = new IvParameterSpec(ivBytes);
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
}
public static String encrypt(String packet) throws Exception
{
byte[] packet2 = packet.getBytes();
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted = new byte[cipher.getOutputSize(packet2.length)];
enc_len = cipher.update(packet2, 0, packet2.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);
return packet = new String(encrypted);
}
public static String decrypt(String packet) throws Exception
{
byte[] packet2 = packet.getBytes();
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(packet2, 0, enc_len, decrypted, 0);
HERE EXCEPTION>>>>> dec_len += cipher.doFinal(decrypted, dec_len); <<<<<<<<<
return packet = new String(decrypted);
}
// and display the results
public static void main (String[] args) throws Exception
{
// get the text to encrypt
generateKey();
String inputText = JOptionPane.showInputDialog("Input your message: ");
String encrypted = encrypt(inputText);
String decrypted = decrypt(encrypted);
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(),
"Encrypted: " + new String(encrypted) + "\n"
+ "Decrypted: : " + new String(decrypted));
.exit(0);
}
}
The thing is, when I decrypt strings (about 4/10 of shots), I get that exception:
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:479)
at javax.crypto.Cipher.doFinal(Cipher.java:2068)
at aes.EncryptionExample.deszyfrujBez(EncryptionExample.java:HERE tag)
at aes.EncryptionExample.main(EncryptionExample.java:Main starting)
Does anybody know what to change here (key? *.doFinal() method?) to make it work?
# for those curious - methods have to be static, as this is a part of something bigger ;)
When you use byte[] packet2 = packet.getBytes() you are converting the string based on the default encoding, which could be UTF-8, for example. That's fine. But then you convert the ciphertext back to a string like this: return packet = new String(encrypted) and this can get you into trouble if this does not round-trip to the same byte array later in decrypt() with another byte[] packet2 = packet.getBytes().
Try this instead: return packet = new String(encrypted, "ISO-8859-1"), and byte[] packet2 = packet.getBytes("ISO-8859-1") -- it's not what I would prefer, but it should round-trip the byte arrays.
The result of encryption is binary data. In most cases it cannot be interpreted as a valid string encoding. So the call to new String(encrypted) will most likely distort the encrypted bytes and after doing packet.getBytes() you end up with a byte array with different content.
The decryption now fails because the cypher text has been changed. The padding bytes are not correctly recovered and cannot be removed.
To fix that, don't convert the cypher text to a string, keep the byte array.
I am using some java code that encrypts the contents of a text file using Blowfish. When I convert the encrypted file back (i.e. decrypt it) the string is missing a character from the end. Any ideas why? I am very new to Java and have been fiddling with this for hours with no luck.
The file war_and_peace.txt just contains the string "This is some text". decrypted.txt contains "This is some tex" (with no t on the end). Here is the java code:
public static void encrypt(String key, InputStream is, OutputStream os) throws Throwable {
encryptOrDecrypt(key, Cipher.ENCRYPT_MODE, is, os);
}
public static void decrypt(String key, InputStream is, OutputStream os) throws Throwable {
encryptOrDecrypt(key, Cipher.DECRYPT_MODE, is, os);
}
private static byte[] getBytes(String toGet)
{
try
{
byte[] retVal = new byte[toGet.length()];
for (int i = 0; i < toGet.length(); i++)
{
char anychar = toGet.charAt(i);
retVal[i] = (byte)anychar;
}
return retVal;
}catch(Exception e)
{
String errorMsg = "ERROR: getBytes :" + e;
return null;
}
}
public static void encryptOrDecrypt(String key, int mode, InputStream is, OutputStream os) throws Throwable {
String iv = "12345678";
byte[] IVBytes = getBytes(iv);
IvParameterSpec IV = new IvParameterSpec(IVBytes);
byte[] KeyData = key.getBytes();
SecretKeySpec blowKey = new SecretKeySpec(KeyData, "Blowfish");
//Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/NoPadding");
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(Cipher.ENCRYPT_MODE, blowKey, IV);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
} else if (mode == Cipher.DECRYPT_MODE) {
cipher.init(Cipher.DECRYPT_MODE, blowKey, IV);
CipherOutputStream cos = new CipherOutputStream(os, cipher);
doCopy(is, cos);
}
}
public static void doCopy(InputStream is, OutputStream os) throws IOException {
byte[] bytes = new byte[4096];
//byte[] bytes = new byte[64];
int numBytes;
while ((numBytes = is.read(bytes)) != -1) {
os.write(bytes, 0, numBytes);
}
os.flush();
os.close();
is.close();
}
public static void main(String[] args) {
//Encrypt the reports
try {
String key = "squirrel123";
FileInputStream fis = new FileInputStream("war_and_peace.txt");
FileOutputStream fos = new FileOutputStream("encrypted.txt");
encrypt(key, fis, fos);
FileInputStream fis2 = new FileInputStream("encrypted.txt");
FileOutputStream fos2 = new FileOutputStream("decrypted.txt");
decrypt(key, fis2, fos2);
} catch (Throwable e) {
e.printStackTrace();
}
}
`
There is a couple of things not optimal here.
But let's first solve your problem. The reason why the last portion of your input is somehow missing is the padding you specify: none! Without specifying a padding, the Cipher can just operate on full-length blocks (8 bytes for Blowfish). Excess input that is less than a block long will be silently discarded, and there's your missing text. In detail: "This is some text" is 17 bytes long, so two full blocks will be decrypted, and the final 17th byte, "t", will be discarded.
Always use a padding in combination with symmetric block ciphers, PKCS5Padding is fine.
Next, when operating with Cipher, you don't need to implement your own getBytes() - there's String#getBytes already doing the job for you. Just be sure to operate on the same character encoding when getting the bytes and when reconstructing a String from bytes later on, it's a common source of errors.
You should have a look at the JCE docs, they will help you avoiding some of the common mistakes.
For example, using String keys directly is a no-go for symmetric cryptography, they do not contain enough entropy, which would make it easier to brute-force such a key. The JCE gives you theKeyGenerator class and you should always use it unless you know exactly what you are doing. It generates a securely random key of the appropriate size for you, but in addition, and that is something people tend to forget, it will also ensure that it doesn't create a weak key. For example, there are known weak keys for Blowfish that should be avoided in practical use.
Finally, you shouldn't use a deterministic IV when doing CBC encryption. There are some recent attacks that make it possible to exploit this, resulting in total recovery of the message, and that's obviously not cool. The IV should always be chosen at random (using a SecureRandom) in order to make it unpredictable. Cipher does this for you by default, you can simply obtain the used IV after encryption with Cipher#getIV.
On another note, less security-relevant: you should close streams in a finally block to ensure they're closed at all cost - otherwise you will be left with an open file handle in case of an exception.
Here's an updated version of your code that takes all these aspects into account (had to use Strings instead of files in main, but you can simply replace it with what you had there):
private static final String ALGORITHM = "Blowfish/CBC/PKCS5Padding";
/* now returns the IV that was used */
private static byte[] encrypt(SecretKey key,
InputStream is,
OutputStream os) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
return cipher.getIV();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static void decrypt(SecretKey key,
byte[] iv,
InputStream is,
OutputStream os)
{
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static void doCopy(InputStream is, OutputStream os)
throws IOException {
try {
byte[] bytes = new byte[4096];
int numBytes;
while ((numBytes = is.read(bytes)) != -1) {
os.write(bytes, 0, numBytes);
}
} finally {
is.close();
os.close();
}
}
public static void main(String[] args) {
try {
String plain = "I am very secret. Help!";
KeyGenerator keyGen = KeyGenerator.getInstance("Blowfish");
SecretKey key = keyGen.generateKey();
byte[] iv;
InputStream in = new ByteArrayInputStream(plain.getBytes("UTF-8"));
ByteArrayOutputStream out = new ByteArrayOutputStream();
iv = encrypt(key, in, out);
in = new ByteArrayInputStream(out.toByteArray());
out = new ByteArrayOutputStream();
decrypt(key, iv, in, out);
String result = new String(out.toByteArray(), "UTF-8");
System.out.println(result);
System.out.println(plain.equals(result)); // => true
} catch (Exception e) {
e.printStackTrace();
}
}
You have your CipherInputStream and CipherOutputStream mixed up. To encrypt, you read from a plain inputstream and write to a CipherOutputStream. To decrypt ... you get the idea.
EDIT:
What is happening is that you have specified NOPADDING and you are attempting to encrypt using a CipherInputStream. The first 16 bytes form two valid complete blocks and so are encrypted correctly. Then there is only 1 byte left over, and when the CipherInputStream class receives the end-of-file indication it performs a Cipher.doFinal() on the cipher object and receives an IllegalBlockSizeException. This exception is swallowed, and read returns -1 indicating end-of-file. If however you use PKCS5PADDING everything should work.
EDIT 2:
emboss is correct in that the real issue is simply that it is tricky and error-prone to use the CipherStream classes with the NOPADDING option. In fact, these classes explicitly state that they silently swallow every Security exception thrown by the underlying Cipher instance, so they are perhaps not a good choice for beginners.
Keys are binary, and String is not a container for binary data. Use a byte[].
When I had this problem I had to call doFinal on the cipher:
http://docs.oracle.com/javase/1.4.2/docs/api/javax/crypto/Cipher.html#doFinal()
I am trying to run a simple encryption/decryption program. I am getting a padding exception. There must be something hidden that I am not aware. I basically encrypted a string write it to a file, read it back, and decrypted it. The original encrypted array was decrypted without a problem. I compared the original encrypted array with the array read back from the file, they were identical from what I can see. The buffer from the file does not work, so there must be something difference. I don't know what to do.
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
public class sample
{
private static String _algo = "AES";
private static byte[] _key = new byte[16];
public static byte[] encrypt (String val) throws Exception
{
Key key = new SecretKeySpec (_key, _algo);
Cipher c = Cipher.getInstance (_algo);
c.init (Cipher.ENCRYPT_MODE, key);
byte[] encode = c.doFinal (val.getBytes());
return encode;
}
public static String decrypt (byte[] val) throws Exception
{
Key key = new SecretKeySpec (_key, _algo);
Cipher c = Cipher.getInstance (_algo);
c.init (Cipher.DECRYPT_MODE, key);
byte[] decode = c.doFinal (val);
String decodeStr = new String (decode);
return decodeStr;
}
public static void main (String[] args) throws Exception
{
String str = "Good bye cruel world";
//
// get password from command line
//
_key = args[0].getBytes();
byte[] encodeArray = sample.encrypt (str);
//
// write encrypted array to file
//
FileOutputStream os = new FileOutputStream ("data");
os.write (encodeArray);
os.close();
//
// decode and print out string
//
String decodeStr = sample.decrypt (encodeArray);
System.out.println ("decodeStr = " + decodeStr);
//
// read back encrypted string
byte[] buffer = new byte[64];
FileInputStream is = new FileInputStream ("data");
is.read (buffer);
is.close();
decodeStr = sample.decrypt (buffer);
System.out.println ("decodeStr = " + decodeStr);
}
}
Output:
java sample 1234567890123456
decodeStr = Good bye cruel world
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at sample.decrypt(sample.java:32)
at sample.main(sample.java:70)
The problem is that the byte buffer with a size of 64, which you are reading the file into, is too big. Change it to 32.
Or use the length of the file like this:
byte[] buffer = new byte[(int)new File("data").length()];