AES File decrypting “given final block not properly padded” - java

I want to encrypt and then decrypt file use AES. I have read many topics about error "Given final block not properly padded". But i don't find solution for me.
Sorry about specify the language of my code, i don't know write language java
Here is my code :
Variables
// IV, secret, salt in the same time
private byte[] salt = { 'h', 'u', 'n', 'g', 'd', 'h', '9', '4' };
public byte[] iv;
public SecretKey secret;
createSecretKey
public void createSecretKey(String password){
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
secret = new SecretKeySpec(tmp.getEncoded(), "AES");
}
method Encrypt
public void encrypt(String inputFile){
FileInputStream fis = new FileInputStream(inputFile);
// Save file: inputFile.enc
FileOutputStream fos = new FileOutputStream(inputFile + ".enc");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
// Gen Initialization Vector
iv = (byte[]) ((IvParameterSpec) params
.getParameterSpec(IvParameterSpec.class)).getIV();
// read from file (plaint text) -----> save with .enc
int readByte;
byte[] buffer = new byte[1024];
while ((readByte = fis.read(buffer)) != -1) {
fos.write(cipher.doFinal(buffer), 0, readByte);
}
fis.close();
fos.flush();
fos.close();
}
method Decrypt
public void decrypt(String inputFile){
FileInputStream fis = new FileInputStream(inputFile);
// Save file: filename.dec
FileOutputStream fos = new FileOutputStream(inputFile.substring(0,
inputFile.length() - 4) + ".dec");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
// Read from file encrypted ---> .dec
int readByte;
byte[] buffer = new byte[1024];
while ((readByte = fis.read(buffer)) != -1) {
fos.write(cipher.doFinal(buffer), 0, readByte);
}
fos.flush();
fos.close();
fis.close();
}
Update
Solution: edit size of buffer is multiples of 16. Use CipherInput/ Output for read/ write file.
Tks Artjom B.

AES is a block cipher and as such only works on blocks of 16 bytes. A mode of operation such as CBC enables you to chain multiple blocks together. A padding such as PKCS#5 padding enables you to encrypt arbitrary length plaintext by filling the plaintext up to the next multiple of the block size.
The problem is that you're encrypting every 1024 bytes separately. Since 1024 divides the block size, the padding adds a full block before encryption. The ciphertext chunks are therefore 1040 bytes long. Then during decryption, you're only reading 1024 missing the padding. Java tries to decrypt it and then tries to remove the padding. If the padding is malformed (because it's not there), then the exception is thrown.
Easy fix
Simply increase your buffer for decryption to 1040 bytes.
Proper fix
Don't encrypt it in separate chunks, but either use Cipher#update(byte[], int, int) instead of Cipher.doFinal to update the ciphertext for every buffer you read or use a CipherInputStream.
Other security considerations:
You're missing a random IV. Without it, it may be possible for an attacker to see that you encrypted the same plaintext under the same key only by observing the ciphertexts.
You're missing ciphertext authentication. Without it, you can't reliably detect (malicious) changes in the ciphertexts and may open your system to attacks such as padding oracle attack. Either use an authenticated mode like GCM or run your created ciphertext through HMAC to create an authentication tag and write it to the end. Then you can verify the tag during/before decryption.

You are under the false assumption that the length of the encrypted data equals the length of the plain data, but the encrypted AES data is always a multiple of the AES block size (16 bytes) and can have an additional full padding block.
The most efficient way of dealing with stream encryption would be to use JCE's CipherOutputStream and CipherInputStream (http://docs.oracle.com/javase/7/docs/api/javax/crypto/CipherInputStream.html). These classes do all the work for you.
Also, make sure you always save the newly generated IV in your encryption method to be able to use it for the decryption.

Related

'javax.crypto.BadPaddingException' while using cipherInputStream

I'm writing a program to encrypt and decrypt data.
for encrypting,
I created a symmetric key using keyGenerator.
I transferred the key to the cipher, and created a string version of the key:
String keyString = Base64.getEncoder().encodeToString(symmetricKey.getEncoded());
in order to store it in a configuration file (so I can retrieve the key in the decrypt function).
Now, in the decrypt function I need to get that string back to key format, so I can send it as a parameter to the cipher in dercypt mode.
I convert it back to key this way:
byte[] keyBytes = key.getBytes(Charset.forName("UTF-8"));
Key newkey = new SecretKeySpec(keyBytes,0,keyBytes.length, "AES");
And I transffer it to the cipher and write the output (the decrypted data) using CipherInputStream:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, newkey, newiv, SecureRandom.getInstance("SHA1PRNG"));
CipherInputStream cipherInputStream = new CipherInputStream(
new ByteArrayInputStream(encryptedBytes), cipher);
ArrayList<Byte> decryptedVal = new ArrayList<>();
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
decryptedVal.add((byte) nextByte);
}
byte[] bytes = new byte[decryptedVal.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = decryptedVal.get(i);
}
String decryptedData = new String(bytes);
cipherInputStream.close();
System.out.println("decryptedData: " + decryptedData);
I get this error:
Exception in thread "main" java.io.IOException: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
So I suspect that there might be a problem with the way I treat the key.
Any suggestions? help would be appreciated!
I think you have not sent IV to decryption function. For decryption in CBC mode, you must provide an IV which is used in encryption process.
Update:
IV will affect only first block in CBC decryption mode. So my answer may affect the unpadding if your data is less than 1 block. It will just change the decrypted plaintext of the first block otherwise.
Of course you get this error: first you apply base 64 encoding:
String keyString = Base64.getEncoder().encodeToString(symmetricKey.getEncoded());
and then you use character-encoding to turn it back into bytes:
byte[] keyBytes = key.getBytes(Charset.forName("UTF-8"));
which just keeps be base64 encoding, probably expanding the key size from 16 bytes to 24 bytes which corresponds with a 192 bit key instead of a 128 bit key. Or 24 bytes key to a 32 bytes key of course - both seem to work.
To solve this you need to use Base64.getDecoder() and decode the key.
Currently you get a key with a different size and value. That means that each block of plaintext, including the last one containing the padding, will decrypt to random plaintext. As random plaintext is unlikely to contain valid padding, you will be greeted with a BadPaddingException.
Reminder:
encoding, e.g. base 64 or hex: encoding bytes to a text string
character-encoding, e.g. UTF-8 or ASCII: encoding a text string into bytes
They are not opposites, that would be decoding and character-decoding respectively.
Remarks:
yes, listen to Ashfin; you need to use a random IV during encryption and then use it during decryption, for instance by prefixing it to the ciphertext (unencrypted);
don't use ArrayList<Byte>; that stores a reference to each separate byte (!) - use ByteArrayOutputStream or any other OutputStream instead;
you can better use a byte buffer and use that to read / write to the streams (note that the read function may not fill the buffer, even if at the start or in the middle of the stream) - reading a single byte at the time is not performant;
lookup try-with-resources for Java;
using a KeyStore may be better than storing in a config file;
GCM mode (AES/GCM/NoPadding) also authenticates data and should be preferred over CBC mode.

How to properly recreate SecretKey from string

I'm trying to make an encryption-decryption app. I've got two classes - one with functions to generate the key, encrypt and decrypt, second one for JavaFX GUI. In the GUI class I've got 4 textareas: 1st to write text to encrypt, 2nd for encrypted text, 3rd for the key (String encodedKey = Base64.getEncoder().encodeToString(klucz.getEncoded());) and 4th for decrypted text.
The problem is, I am not able to decrypt the text. I'm trying to recreate the SecretKey like this:
String encodedKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz = new SecretKeySpec(decodedKey, "DESede");
When I encrypt the key looks like this: com.sun.crypto.provider.DESedeKey#4f964d80 and when I try to recreate it: javax.crypto.spec.SecretKeySpec#4f964d80 and I'm getting javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
Here is my 1st class:
public class Encryption {
public static SecretKey generateKey() throws NoSuchAlgorithmException {
Security.addProvider(new com.sun.crypto.provider.SunJCE());
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(168);
SecretKey klucz = keygen.generateKey();
return klucz;
}
static byte[] encrypt(byte[] plainTextByte, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, klucz);
byte[] encryptedBytes = cipher.doFinal(plainTextByte);
return encryptedBytes;
}
static byte[] decrypt(byte[] encryptedBytes, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, klucz);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return decryptedBytes;
}
}
edit
btnEncrypt.setOnAction((ActionEvent event) -> {
try {
String plainText = textAreaToEncrypt.getText();
SecretKey klucz = Encryption.generateKey();
byte[] plainTextByte = plainText.getBytes();
byte[] encryptedBytes = Encryption.encrypt(plainTextByte, klucz);
String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
textAreaEncryptedText.setText(encryptedText);
byte[] byteKey = klucz.getEncoded();
String stringKey = Base64.getEncoder().encodeToString(byteKey);
textAreaKey.setTextstringKey
} catch (Exception ex) {
ex.printStackTrace();
}
});
btnDecrypt.setOnAction((ActionEvent event) -> {
try {
String stringKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz2 = new SecretKeySpec(decodedKey, "DESede");
String encryptedText = textAreaEncryptedText.getText();
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes());
byte[] decryptedBytes = Encryption.decrypt(encryptedBytes, klucz2;
String decryptedText = Base64.getEncoder().encodeToString(decryptedBytes);
textAreaDecryptedText.setText(decryptedText);
} catch (Exception ex) {
ex.printStackTrace();
}
});
One of your problems is here:
String encryptedText = new String(encryptedBytes, "UTF8");
Generally, many byte sequences in cipher text are not valid UTF-8–encoded characters. When you try to create a String, this malformed sequences will be replaced with the "replacement character", and then information from the the cipher text is irretrievably lost. When you convert the String back to bytes and try to decrypt it, the corrupt cipher text raises an error.
If you need to represent the cipher text as a character string, use base-64 encoding, just as you do for the key.
The other principal problem is that you are aren't specifying the full transformation. You should specify the "mode" and "padding" of the cipher explicitly, like "DESede/ECB/PKCS5Padding".
The correct mode will depend on your assignment. ECB is generally not secure, but more secure modes add a bit of complexity that may be outside the scope of your assignment. Study your instructions and clarify the requirements with your teacher if necessary.
There are two main issues:
You should not use user entered password as a key (there are difference between them). The key must have specific size depending on the cipher (16 or 24 bytes for 3des)
Direct 3DES (DESede) is a block cipher encrypting 8 bytes at once. To encrypt multiple blocks, there are some methods defined how to do that properly. It is calls Block cipher mode.
For proper encryption you need to take care of a few more things
Creating a key from the password
Let's assume you want to use DESede (3des). The key must have fixed size - 16 or 24 bytes. To properly generate a key from password you should use PBKDF. Some people are sensitive to "must use", however neglecting this step really compromises the encryption security mainly using user-entered passwords.
For 3DES you can use :
int keySize = 16*8;
int iterations = 800000;
char[] password = "password".toCharArray();
SecureRandom random = new SecureRandom();
byte[] salt = random.generateSeed(8);
SecretKeyFactory secKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, iterations, keySize);
SecretKey pbeSecretKey = secKeyFactory.generateSecret(spec);
SecretKey desSecret = new SecretKeySpec(pbeSecretKey.getEncoded(), "DESede");
// iv needs to have block size
// we will use the salt for simplification
IvParameterSpec ivParam = new IvParameterSpec(salt);
Cipher cipher = Cipher.getInstance("DESEde/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, desSecret, ivParam);
System.out.println("salt: "+Base64.getEncoder().encodeToString(salt));
System.out.println(cipher.getIV().length+" iv: "+Base64.getEncoder().encodeToString(cipher.getIV()));
byte[] ciphertext = cipher.doFinal("plaintext input".getBytes());
System.out.println("encrypted: "+Base64.getEncoder().encodeToString(ciphertext));
if you can ensure that your password has good entropy (is long and random enough) you may be good with a simple hash
MessageDigest dgst = MessageDigest.getInstance("sha-1");
byte[] hash = dgst.digest("some long, complex and random password".getBytes());
byte[] keyBytes = new byte[keySize/8];
System.arraycopy(hash, 0, keyBytes, 0, keySize/8);
SecretKey desSecret = new SecretKeySpec(keyBytes, "DESede");
The salt serves to randomize the output and should be used.
The output of the encryption should be salt | cipthertext | tag (not necessarily in this order, but you will need all of these for proper encryption).
To decrypt the output, you will need to split the output to salt, ciphertext and the tag.
I see zero vectors ( static salt or iv ) very often in examples from StackOverflow, but in many cases it may lead to broken ciphers revelaling key or plaintext.
The initialization vector iv is needed for block chain modes (encrypting longer input than a single block), we could use the salt from the key as well
when having the same size ( 8 bytes in our case). For really secure solution the password salt should be longer.
The tag is an authentication tag, to ensure that nobody has manipulated with the ciphertext. You could use HMAC of the plaintext or ciphertext. It is important you should use different key for HMAC than for encryption. However - I believe in your case your homework will be ok even without the hmac tag

How can i encrypt and decrypt images in java android [duplicate]

I want to encrypt file and store it in SD card. I want to decrypt that encrypted file and store it in SD card again. I have tried to encrypt file by opening it as file stream and encrypt it but it is not working. I want some idea on how to do this.
Use a CipherOutputStream or CipherInputStream with a Cipher and your FileInputStream / FileOutputStream.
I would suggest something like Cipher.getInstance("AES/CBC/PKCS5Padding") for creating the Cipher class. CBC mode is secure and does not have the vulnerabilities of ECB mode for non-random plaintexts. It should be present in any generic cryptographic library, ensuring high compatibility.
Don't forget to use a Initialization Vector (IV) generated by a secure random generator if you want to encrypt multiple files with the same key. You can prefix the plain IV at the start of the ciphertext. It is always exactly one block (16 bytes) in size.
If you want to use a password, please make sure you do use a good key derivation mechanism (look up password based encryption or password based key derivation). PBKDF2 is the most commonly used Password Based Key Derivation scheme and it is present in most Java runtimes, including Android. Note that SHA-1 is a bit outdated hash function, but it should be fine in PBKDF2, and does currently present the most compatible option.
Always specify the character encoding when encoding/decoding strings, or you'll be in trouble when the platform encoding differs from the previous one. In other words, don't use String.getBytes() but use String.getBytes(StandardCharsets.UTF_8).
To make it more secure, please add cryptographic integrity and authenticity by adding a secure checksum (MAC or HMAC) over the ciphertext and IV, preferably using a different key. Without an authentication tag the ciphertext may be changed in such a way that the change cannot be detected.
Be warned that CipherInputStream may not report BadPaddingException, this includes BadPaddingException generated for authenticated ciphers such as GCM. This would make the streams incompatible and insecure for these kind of authenticated ciphers.
I had a similar problem and for encrypt/decrypt i came up with this solution:
public static byte[] generateKey(String password) throws Exception
{
byte[] keyStart = password.getBytes("UTF-8");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
sr.setSeed(keyStart);
kgen.init(128, sr);
SecretKey skey = kgen.generateKey();
return skey.getEncoded();
}
public static byte[] encodeFile(byte[] key, byte[] fileData) throws Exception
{
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(fileData);
return encrypted;
}
public static byte[] decodeFile(byte[] key, byte[] fileData) throws Exception
{
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(fileData);
return decrypted;
}
To save a encrypted file to sd do:
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "your_folder_on_sd", "file_name");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
byte[] yourKey = generateKey("password");
byte[] filesBytes = encodeFile(yourKey, yourByteArrayContainigDataToEncrypt);
bos.write(fileBytes);
bos.flush();
bos.close();
To decode a file use:
byte[] yourKey = generateKey("password");
byte[] decodedData = decodeFile(yourKey, bytesOfYourFile);
For reading in a file to a byte Array there a different way out there. A Example: http://examples.javacodegeeks.com/core-java/io/fileinputstream/read-file-in-byte-array-with-fileinputstream/
You could use java-aes-crypto or Facebook's Conceal
java-aes-crypto
Quoting from the repo
A simple Android class for encrypting & decrypting strings, aiming to
avoid the classic mistakes that most such classes suffer from.
Facebook's conceal
Quoting from the repo
Conceal provides easy Android APIs for performing fast encryption and
authentication of data

Java NIO + AES Encryption from Client to Server - ByteBuffer issue

I'm quite a newbie regarding encryption and NIO,
I have the following code for client:
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] encrypted = cipher.doFinal(msg.getBytes());
System.out.println("Encrypted info: " + encrypted);
String send = encrypted.toString();
bytebuf = ByteBuffer.allocate(48);
bytebuf.clear();
bytebuf.put(send.getBytes());
bytebuf.flip();
while(bytebuf.hasRemaining()) {
nBytes += client.write(bytebuf);
}
and the following code for server:
// Server receives data and decrypts
SocketChannel socket = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
nBytes = socket.read(buf);
String data = new String(buf.array()).trim();
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secret);
byte[] decrypted = cipher.doFinal(data.getBytes());
System.out.println("Decrypted Info: " + new String(decrypted));
When a message is sent from the Client to the Server, "HELLO" for example is encrypted to [B#34d74aa5 and on the Server side I get *Data packet found as [B#34d74aa5.
Till here everything looks fine, but I get the following exception:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
I suspect that I have some issue with the way the data is coming out of the buffer on the server side?
Any ideas on this?
UPDATE:
**Based on Erickson's answer this is the final solution
javax.crypto.BadPaddingException: Given final block not properly padded
Client Code:
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
System.out.println("Encrypted info: " + text);
bytebuf = ByteBuffer.allocate(32);
bytebuf.clear();
bytebuf.put(text.getBytes());
bytebuf.flip();
while(bytebuf.hasRemaining()) {
nBytes += client.write(bytebuf);
}
Server Code:
LOGGER.info("Confirming write");
String data = new String(buf.array());
LOGGER.info("Data packet found as {}", data);
/*******************************************************/
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted Info: " + new String(decrypted, StandardCharsets.UTF_8));
Your cipher text, encrypted, is a byte[], and invoking toString() on an array doesn't render the array content, it returns type ([B) and hash code (#34d74aa5) information as described by Object.toString().
You can't just use new String(encrypted) either. When a byte array is decoded to text, the decoder will replace any invalid byte sequences with the replacement character, \uFFFD (�). Thus, information is lost and subsequent decryption will fail.
Use an encoding like base-64 to convert byte sequences to printable characters instead. Don't junk up your code with third-party libraries for this; you can use javax.xml.bind.DatatypeConverter.
/* Client: */
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
…
/* Server: */
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = Cipher.doFinal(encrypted);
System.out.println(new String(decrypted, StandardCharsets.UTF_8);
You should also be explicit in selecting your mode and padding (like "AES/CBC/PKCS5Padding") because there's no guarantee the recipient will use the same provider, or that the same provider will use the same defaults over time. Same goes for specifying character encodings, like UTF-8.
The AES scheme is a "block cipher" it works on fixed-size blocks of data. You are creating a "raw" Cipher instance, which will expect you to make sure that every byte array that you pass to the cipher is aligned to the cipher's "native" block length. That's usually not what you want to do.
An additional problem that you are exposing yourself to in using the cipher "raw", although it's not causing an actual error, is that if you were to pass it the same block of data on separate occasions, each time, that block would be encrypted identically, therefore giving an attacker clues as to the structure of the data. Again, that's usually not what you want to do in a practical application.
So usually, you need to specify two extra things: a padding scheme, which determines what happens when sections of data are not exactly aligned to a block size, and a block mode, which determines what scheme the cipher will use to avoid identical input blocks being encrypted to identical output blocks. The block mode generally needs initialising with a "starting state" called the initialisation vector (you could use a default state of "all zero", but that's less secure).
So you need to do two things:
You need to initialise you cipher with a padding scheme and block
mode, e.g. "AES/CBC/PKCS5PADDING"
For additional security, you would also usually set up (and transmit
before the data) a random initialisation vector. See this example for more
information.
You are converting the ciphertext, which is a byte[], to a String here:
byte[] encrypted = cipher.doFinal(msg.getBytes());
String send = encrypted.toString();
This is incorrect. You also cannot do new String(byte[]) because the byte[] is random, not a stream of character data in the platform default encoding assumed by new String(byte[]). You should convert the byte[] data to a String by using a hex or base64 encoding (I recommend Apache Commons Codec) e.g.
hexEncodedCipherText = new String(Hex.encodeHex(binaryCipherText))
On the server-side, use the opposite operation to convert the hex or base64 encoded data back to a byte[] before decryption e.g.
binaryCipherText = Hex.decodeHex(hexEncodedCipherText.toCharArray());
UPDATE:
The updated question is not working during decryption because of the incorrect use of the initialization vector. You don't specify an IV during encryption, which means Java will generate a random one. You need to obtain this random IV from the cipher by calling cipher.getIV() after the encryption (or specify it explicitly, though generating a random one is more secure). Then, during the decryption, create the IvParameterSpec using the IV created during encryption. In addition, you will need to encode/decode the IV in the same manner as the ciphertext, since it is also binary data.
UPDATE 2:
I see you have updated your question with the IV, but you are using a null IV. Generally, this is only "safe" when you have a unique key for every message you send. If your key is fixed or re-used for any significant length of time, you should generate a unique IV for each encryption/decryption. Otherwise, you are leaving yourself open to cryptanalysis based on multiple ciphertexts encrypted with the same key and IV.

BadPaddingException: pad block corrupted

I am trying to decrypt a file in Java which was encrypted in C# using Rijndael/CBC/PKCS7. I keep getting the following exception:
javax.crypto.BadPaddingException: pad block corrupted
at org.bouncycastle.jce.provider.JCEBlockCipher.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at AESFileDecrypter.decrypt(AESFileDecrypter.java:57)
when the doFinal(inpbytes) method is called by the web server for the first byte[]. I am guessing this is a problem with the key or IV. I have the encrypted files on my file system for testing. Is there anything that anyone can see glaringly wrong with my code below?
***keyStr is base64 encoded
public AESFileDecrypter(String keyStr){
try {
Security.addProvider(new BouncyCastleProvider());
convertIvParameter();
key = new sun.misc.BASE64Decoder().decodeBuffer(keyStr);
//use the passed in Base64 decoded key to create a key object
decryptKey = new SecretKeySpec(key, "AES");
//specify the encryption algorithm
decryptCipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
//make a parameter object for the initialization vector(IV)
IvParameterSpec ivs = new IvParameterSpec(_defaultIv);
//initialize the decrypter to the correct mode, key used and IV
decryptCipher.init(Cipher.DECRYPT_MODE, decryptKey, ivs);
}
catch (Exception e) {
e.printStackTrace();
}
}
public void convertIvParameter() {
int[] iv = new int[] {11, 190, 165, 33, 68, 88, 11, 200, 245, 35, 68, 23, 60, 24, 223, 67};
_defaultIv = new byte[16];
for(int x = 0; x < _defaultIv.length; x++) {
_defaultIv[x] = (byte)iv[x];
}
}
public void decryptUpdate(byte[] inpBytes) throws Exception {
//decrypt the byte passed in from the web server
decryptCipher.update(inpBytes);
}
public byte[] decryptFinal() throws Exception {
//decrypt the byte passed in from the web server
return decryptCipher.doFinal();
}
//sends bytes to the client for diaply
private void sendBytes(FileInputStream fis, OutputStream os)throws Exception {
//set the buffer size to send 4k segments of data
aesFileDecrypter = new AESFileDecrypter(<Insert Key string here>);
byte[] buffer = new byte[4096];
int bytes = 0, totalBytes = fis.available();
//while there is still data to be sent keep looping and write the data
//to the output stream as the buffer is filled
try {
while ((bytes = fis.read(buffer)) != -1) {
aesFileDecrypter.decryptUpdate(buffer);
//os.write(buffer, 0, bytes);
}
os.write(aesFileDecrypter.decryptFinal(), 0, totalBytes);
}
catch(Exception e) {
e.printStackTrace();
}
}
Firstly, just to be clear, from comments below, you shouldn't call doFinal() on every block, because doFinal() expects any padding at the end, which obviouslly won't be there in intermediate blocks. Either (a) call update() on intermediate data, then doFinal() at the end, or (b) just arrange to have all your data in one buffer or byte array, and call doFinal() once on the whole job lot.
It's not clear from the code you posted that that's actually what you're doing, but it should be mentioned just in case.
Failing that, then as a first step to debugging, I'd suggest whichever of these two is easier for you:
Decrypting in ECB mode with no padding and seeing what you get. Look at the first block of data this brings back. If you can XOR this with your IV bytes and get the expected decrypted data, you know your key is OK.
Dumping out the actual key bytes from C# before base 64 encoding and Java after decoding and checking they are the same.
As I recall, C# has unsigned bytes (whereas Java signed) so there are a few places where there's room for things subtly going wrong with byte signedness.
I have encountered this problem before.
When I wrote some code to do encryption and decryption like this:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sec, "AES"),new IvParameterSpec(new byte[cipher.getBlockSize()]));
byte[] encode = cipher.doFinal(data);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sec, "AES"), new IvParameterSpec(new byte[cipher.getBlockSize()]));
byte[] decode = cipher.doFinal(encode);
I forgot the first IvParameterSpec(new byte[cipher.getBlockSize()]) when encrypting data, then I got an exception "pad block corrupted", so maybe you should check you encryption code.
As far as I know AES is based on Rijndael, but the specification is not exactly the same. I would suggest to check the key and block size you are using to cipher in C# and the sizes being use in Java. (.Net differences between Rijndael and AES).
The doFinal() was the undoing of the code above, and I ended up just using cipher streams instead of the update/doFinal approach. This way I could use the FileInputStream and my cipher as parameters for the CipherInputStream, and then pass the output to the web browser through an OutputStream. Breaking the update and doFinal out into their own method calls made the task much more difficult and both methods were deleted from the decrypter class (leaving a single while loop that read in chunks of data and output it to the browser). The Bouncy Castle Provider was also not needed in this case and PKCS5Padding was enough, which was given by the SunJCE.

Categories