Java Decryption creating additional symbols - java

I'm writing a encryption and decryption code as follows
import java.io.*;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.BadPaddingException;
import java.nio.file.Files;
import java.util.Scanner;
public class EncryptFile
{
public static void main(String args[]) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
//Encrypt Mode
FileOutputStream outputStream = new FileOutputStream(new File("D:\\encryptedNewStringFile.txt"));
Key secretKey = new SecretKeySpec("encKey".getBytes(), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] getFileBytes = "writing a file using encryption ".getBytes();
byte[] outputBytes = cipher.doFinal(getFileBytes);
outputStream.write(outputBytes);
getFileBytes = "\n".getBytes();
outputBytes = cipher.doFinal(getFileBytes);
outputStream.write(outputBytes);
getFileBytes = "This is New Line 2 \nThis is NewLine 3".getBytes();
outputBytes = cipher.doFinal(getFileBytes);
outputStream.write(outputBytes);
outputStream.close();
//Decrypt Mode
File curFile = new File("D:\\encryptedNewStringFile.txt");
secretKey = new SecretKeySpec("encKey".getBytes(), "Blowfish");
cipher = Cipher.getInstance("Blowfish/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
getFileBytes = Files.readAllBytes(curFile.toPath());
outputBytes = cipher.doFinal(getFileBytes);
InputStream bai = new ByteArrayInputStream(outputBytes);
BufferedReader bfReader = new BufferedReader(new InputStreamReader(bai));
Scanner scan = new Scanner(bfReader);
while(scan.hasNextLine())
{
System.out.println(scan.nextLine());
}
}
}
here i have a problem in output which is the printed output has some extra symbols (i.e question marks and box symbols)in it.
The output i received is
Any suggestions will be really helpful thanks in advance

Cipher cipher = Cipher.getInstance("Blowfish");
is equivalent to
Cipher cipher = Cipher.getInstance("Blowfish/ECB/PKCS5Padding");
which means that each time you call cipher.doFinal additional padding is produced.
In order to write a file without intermittent padding, you should be using
outputBytes = cipher.update(getFileBytes);
and use cipher.doFinal only when writing the last time to the file. Then you will be able to use PKCS5Padding instead of NoPadding during decryption in order to remove the valid padding at the end automatically.
Security considerations:
ECB mode is bad and should not be used. There are only very few use cases where this makes sense to use. At least use CBC mode with a randomly generated IV. The IV doesn't need to be secret but only unpredictable. We usually prepend it to the ciphertext and slice it off before decryption. Since it has always a predefined length, this is easy to do.
Use an authenticated mode of operation like GCM or use a message authentication code like HMAC-SHA256 in order to detect and react to (malicious) manipulation of the ciphertext.
Blowfish should not be used today. Although it has no direct vulnerability, its small block size may open you up to different protocol based vulnerabilities. It would be advisable to use a block cipher with a block size of 128-bit. AES comes to mind.

Combining the answers from #Artjom B. and #The 5th column mouse you get a file encryption program that will encrypt a file with Blowfish in CBC mode. The encryption and decryption is done in chunks so large files (up to some GB) could get encrypted and decrypted without "out of memory errors".
The key is generated randomly, and you should keep in mind - without knowledge of the key no decryption of the file is possible.
output:
file encryption with Blowfish CBC mode
used key (Base64): jsErS04so1NCC7Jmds6Grr+0tPkNoaj0hx/izLaW5H8=
result encryption: true
result decryption: true
Security warning: the code has no exception handling, no correct file handling (e.g. overwriting without notice) and is for educational purpose only:
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class BlowfishCbcFileEncryption {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, IOException,
InvalidKeyException, InvalidAlgorithmParameterException {
System.out.println("file encryption with Blowfish CBC mode");
String uncryptedFilename = "uncrypted.txt";
String encryptedFilename = "encrypted.enc";
String decryptedFilename = "decrypted.txt";
// random blowfish 256 key
byte[] key = new byte[32];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(key);
System.out.println("used key (Base64): " + base64Encoding(key));
// random iv
byte[] iv = new byte[8]; // blowfish iv is 8 bytes long
secureRandom.nextBytes(iv);
boolean result;
result = encryptCbcFileBufferedCipherOutputStream(uncryptedFilename, encryptedFilename, key, iv);
System.out.println("result encryption: " + result);
result = decryptCbcFileBufferedCipherInputStream(encryptedFilename, decryptedFilename, key);
System.out.println("result decryption: " + result);
}
public static boolean encryptCbcFileBufferedCipherOutputStream(String inputFilename, String outputFilename, byte[] key, byte[] iv)
throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
try (FileInputStream in = new FileInputStream(inputFilename);
FileOutputStream out = new FileOutputStream(outputFilename);
CipherOutputStream encryptedOutputStream = new CipherOutputStream(out, cipher);) {
out.write(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "Blowfish");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] buffer = new byte[8096];
int nread;
while ((nread = in.read(buffer)) > 0) {
encryptedOutputStream.write(buffer, 0, nread);
}
encryptedOutputStream.flush();
}
if (new File(outputFilename).exists()) {
return true;
} else {
return false;
}
}
public static boolean decryptCbcFileBufferedCipherInputStream(String inputFilename, String outputFilename, byte[] key) throws
IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
byte[] iv = new byte[8]; // blowfish iv is 8 bytes long
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
try (FileInputStream in = new FileInputStream(inputFilename); // i don't care about the path as all is local
CipherInputStream cipherInputStream = new CipherInputStream(in, cipher);
FileOutputStream out = new FileOutputStream(outputFilename)) // i don't care about the path as all is local
{
byte[] buffer = new byte[8192];
in.read(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "Blowfish");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
int nread;
while ((nread = cipherInputStream.read(buffer)) > 0) {
out.write(buffer, 0, nread);
}
out.flush();
}
if (new File(outputFilename).exists()) {
return true;
} else {
return false;
}
}
private static String base64Encoding(byte[] input) {
return Base64.getEncoder().encodeToString(input);
}
}

Each time you convert string into the byte array, you use default file encoding from your VM properties which is not UTF-8.
So, to fix this issue you have two options: to define the default encoding in java system properties:
System.setProperty("file.encoding", StandardCharsets.UTF_8.name());
or add the charset encoding by each converting of strings into bytes:
"writing a file using encryption ".getBytes(StandardCharsets.UTF_8);

Related

Getting BadPaddingException due to byte[] too long to decipher

The following code is tested for short strings, in that case it decrypts the string nicely.
byte[] ciphertext = Base64.decode(myverylongstring,Base64.DEFAULT);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(Charset.forName("UTF-8")), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(Charset.forName("UTF-8")));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decryptedtextByte = cipher.doFinal(ciphertext);
String decryptedtext = new String(decryptedtextByte); //Successfully decrypts the string
System.out.println("decryptedtext: " + decryptedtext);
[...]
} catch (BadPaddingException e) {
e.printStackTrace();
}
But if the string is too large I get fired the exception I mention.
What could I do so it decrypts the string correctly maintaining the same large string to decrypt?
Edit, well technically what's decrypted is a byte[], changing title and adding code to not cause possible confussion.
As you did not show the encryption and we do not know what kind of key you are using there are many possible reasons for failure. As well you did not show the imports so I could just argue what Base64-encoder is in use.
Below you find a simple program that does the en- and decryption a byte array that is much larger than the size you are using.
Edit:
Security warning: The code below uses a stringified key and static IV, uses single part encryption / decryption for a huge byte array, uses the older CBC mode.
Please do not copy below code or use it in production - it is for educational purposes only.
The code does not have any proper exception handling !
It generates a (random filled) byte array with the plaintext and gets the key & iv from strings that are converted to byte[] using the Standard.Charset "UFT-8".
That it's doing the encryption, convert the ciphertext to a Base64 encoded string followed by the decoding to a new ciphertext byte[] and decryption with the same key and iv.
In the end there is simple comparison of the plaintext and decryptedtext byte arrays.
My advice it is to check for any forgotten charset-setting and correct (means identical) usage of key and iv.
code:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
System.out.println("https://stackoverflow.com/questions/63143007/getting-badpaddingexception-due-to-byte-too-long-to-decipher");
byte[] plaintext = new byte[100000];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(plaintext); // generate a random filled byte array
byte[] key = "12345678901234567890123456789012".getBytes(StandardCharsets.UTF_8);
byte[] iv = "1234567890123456".getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] ciphertextEnc = cipher.doFinal(plaintext);
String ciphertextBase64 = Base64.getEncoder().encodeToString(ciphertextEnc);
byte[] ciphertextDec = Base64.getDecoder().decode(ciphertextBase64);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decryptedtextByte = cipher.doFinal(ciphertextDec);
System.out.println("decryptedtextByte equals plaintext: " + Arrays.equals(plaintext, decryptedtextByte));
}
}

AES/CFB/NOPADDING (128-bit) with password salting

I've tried running the code for about 3 days now but I am not able to figure out the mistake I've done.
I 'am using AES/CFB/NOPadding in 128 bit with password salting
Salt_Len = 8 bytes and IV_Len = 16 bytes
package Firstage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Random;
import java.util.Scanner;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class Thealgorithm1
{
static Scanner get = new Scanner(System.in);
private static final String ALGORITHM = "AES";
private static final String ALGORITHM_MODE ="AES/CFB/NoPadding";
private static String password;
public static void encrypt(File inputFile, File outputFile)
throws Exception
{
System.out.println("Enetr passprhase");
password=get.nextLine();
final Random ivspc = new SecureRandom();
byte[] ivspec = new byte[16];
ivspc.nextBytes(ivspec);
IvParameterSpec enciv = new IvParameterSpec(ivspec);
FileOutputStream outputstrm = new FileOutputStream(outputFile);
byte[] outputBytes = doCrypto(Cipher.ENCRYPT_MODE, inputFile,password,enciv);
System.arraycopy(ivspec, 0,outputBytes , 0, 16);
outputstrm.write(outputBytes);
outputstrm.close();
System.out.println("File encrypted successfully!");
}
public static void decrypt(File inputFile, File outputFile)
throws Exception
{
System.out.println("Enter password");
password=get.nextLine();
IvParameterSpec hj = null;
byte[]outpytBytes=doCrypto(Cipher.DECRYPT_MODE, inputFile,password,hj);
FileOutputStream outputstrm = new FileOutputStream(outputFile);
outputstrm.write(outpytBytes);
outputstrm.close();
System.out.println("File decrypted successfully!");
}
private static byte[] doCrypto(int cipherMode, File inputFile,String keyo ,IvParameterSpec ivespec)
throws Exception {
/* Derive the key, given password and salt. */
final Random slt = new SecureRandom();
byte[] salt = new byte[8];
slt.nextBytes(salt);
char[] passkeyo = keyo.toCharArray();
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(passkeyo, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), ALGORITHM);
FileInputStream fylin = new FileInputStream(inputFile);
byte[] inputBytes = new byte[(int)inputFile.length()];
fylin.read(inputBytes);
fylin.close();
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE);
byte[] outputBytes;
if(cipherMode==2)
{
IvParameterSpec ivdec = new IvParameterSpec(inputBytes,0,16);
cipher.init(cipherMode, secret,ivdec);
}
else
{
cipher.init(cipherMode, secret, ivespec);
}
if(cipherMode==2)
{
outputBytes = cipher.doFinal(inputBytes, 16,(inputBytes.length-16));
}
else
{
outputBytes=cipher.doFinal(inputBytes);
}
return outputBytes;
}
public static void main(String[] args)
{
File inputFile = new File("C:/temp/File.txt");
File encryptedFile = new File("C:/temp/encryaes.enc");
File decryptedFile = new File("C:/temp/mydr.txt");
try {
Thealgorithm1.encrypt(inputFile, encryptedFile);
Thealgorithm1.decrypt(encryptedFile, decryptedFile);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
The code encrypts it properly and also decrypts it, but the problem is that what it decrypts is not proper i.e there's a fault in the code.
You have two problems:
during encryption, you're overwriting a the first 16 bytes of the ciphertext with the IV. An easy fix is to write the IV to the stream instead of using System.arraycopy(). Change
System.arraycopy(ivspec, 0,outputBytes , 0, 16);
to
outputstrm.write(ivspec);
You're using a different salts during encryption and decryption, because you're always generating a new one. You should also write the salt in front of the ciphertext beside the IV and read it back during decryption.
You could also just use one of the two: generate a 16 byte salt and write that in front of the ciphertext. Then you can use a static IV, because the semantic property is achieved from the random salt.
You have two main issues in your code:
The key must be exactly the same for encryption and decryption. When you decrypt you are generating a different (random) salt value so, even if you enter the same passphrase, the decryption key is going to be different. You can test this by making the salt a class attribute and only initializing it once.
When you do this inside the encrypt method:
System.arraycopy(ivspec, 0,outputBytes , 0, 16);
You are writing over the first 16 bytes of the encrypted output.
My simple suggestion is to do something like this:
byte[] outputBytes = doCrypto(Cipher.ENCRYPT_MODE, inputFile, password, enciv);
byte[] outputBytesWithIV = new byte[outputBytes.length + 16];
System.arraycopy(ivspec, 0, outputBytesWithIV, 0, 16);
System.arraycopy(outputBytes, 0, outputBytesWithIV, 16, outputBytes.length);
outputstrm.write(outputBytesWithIV);

Initial bytes incorrect after Java AES/CBC decryption

What's wrong with the following example?
The problem is that the first part of the decrypted string is nonsense. However, the rest is fine, I get...
Result: `£eB6O�geS��i are you? Have a nice day.
#Test
public void testEncrypt() {
try {
String s = "Hello there. How are you? Have a nice day.";
// Generate key
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey aesKey = kgen.generateKey();
// Encrypt cipher
Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);
// Encrypt
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
cipherOutputStream.write(s.getBytes());
cipherOutputStream.flush();
cipherOutputStream.close();
byte[] encryptedBytes = outputStream.toByteArray();
// Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
// Decrypt
outputStream = new ByteArrayOutputStream();
ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
outputStream.write(buf, 0, bytesRead);
}
System.out.println("Result: " + new String(outputStream.toByteArray()));
}
catch (Exception ex) {
ex.printStackTrace();
}
}
Lot of people including myself face lot of issues in making this work due to missing some information like, forgetting to convert to Base64, initialization vectors, character set, etc. So I thought of making a fully functional code.
Hope this will be useful to you all:
To compile you need additional Apache Commons Codec jar, which is available here:
http://commons.apache.org/proper/commons-codec/download_codec.cgi
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
System.out.println("encrypted string: "
+ Base64.encodeBase64String(encrypted));
return Base64.encodeBase64String(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(decrypt(key, initVector,
encrypt(key, initVector, "Hello World")));
}
}
In this answer I choose to approach the "Simple Java AES encrypt/decrypt example" main theme and not the specific debugging question because I think this will profit most readers.
This is a simple summary of my blog post about AES encryption in Java so I recommend reading through it before implementing anything. I will however still provide a simple example to use and give some pointers what to watch out for.
In this example I will choose to use authenticated encryption with Galois/Counter Mode or GCM mode. The reason is that in most case you want integrity and authenticity in combination with confidentiality (read more in the blog).
AES-GCM Encryption/Decryption Tutorial
Here are the steps required to encrypt/decrypt with AES-GCM with the Java Cryptography Architecture (JCA). Do not mix with other examples, as subtle differences may make your code utterly insecure.
1. Create Key
As it depends on your use-case, I will assume the simplest case: a random secret key.
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");
Important:
always use a strong pseudorandom number generator like SecureRandom
use 16 byte / 128 bit long key (or more - but more is seldom needed)
if you want a key derived from a user password, look into a password hash function (or KDF) with stretching property like PBKDF2 or bcrypt
if you want a key derived from other sources, use a proper key derivation function (KDF) like HKDF (Java implementation here). Do not use simple cryptographic hashes for that (like SHA-256).
2. Create the Initialization Vector
An initialization vector (IV) is used so that the same secret key will create different cipher texts.
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
Important:
never reuse the same IV with the same key (very important in GCM/CTR mode)
the IV must be unique (ie. use random IV or a counter)
the IV is not required to be secret
always use a strong pseudorandom number generator like SecureRandom
12 byte IV is the correct choice for AES-GCM mode
3. Encrypt with IV and Key
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);
Important:
use 16 byte / 128 bit authentication tag (used to verify integrity/authenticity)
the authentication tag will be automatically appended to the cipher text (in the JCA implementation)
since GCM behaves like a stream cipher, no padding is required
use CipherInputStream when encrypting large chunks of data
want additional (non-secret) data checked if it was changed? You may want to use associated data with cipher.updateAAD(associatedData); More here.
3. Serialize to Single Message
Just append IV and ciphertext. As stated above, the IV doesn't need to be secret.
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Optionally encode with Base64 if you need a string representation. Either use Android's or Java 8's built-in implementation (do not use Apache Commons Codec - it's an awful implementation). Encoding is used to "convert" byte arrays to string representation to make it ASCII safe e.g.:
String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
4. Prepare Decryption: Deserialize
If you have encoded the message, first decode it to byte array:
byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
Important:
be careful to validate input parameters, so to avoid denial of service attacks by allocating too much memory.
5. Decrypt
Initialize the cipher and set the same parameters as with the encryption:
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);
Important:
don't forget to add associated data with cipher.updateAAD(associatedData); if you added it during encryption.
A working code snippet can be found in this gist.
Note that most recent Android (SDK 21+) and Java (7+) implementations should have AES-GCM. Older versions may lack it. I still choose this mode, since it is easier to implement in addition to being more efficient compared to similar mode of Encrypt-then-Mac (with e.g. AES-CBC + HMAC). See this article on how to implement AES-CBC with HMAC.
Here a solution without Apache Commons Codec's Base64:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AdvancedEncryptionStandard
{
private byte[] key;
private static final String ALGORITHM = "AES";
public AdvancedEncryptionStandard(byte[] key)
{
this.key = key;
}
/**
* Encrypts the given plain text
*
* #param plainText The plain text to encrypt
*/
public byte[] encrypt(byte[] plainText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(plainText);
}
/**
* Decrypts the given byte array
*
* #param cipherText The data to decrypt
*/
public byte[] decrypt(byte[] cipherText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(cipherText);
}
}
Usage example:
byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);
System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));
Prints:
Hello world!
դ;��LA+�ߙb*
Hello world!
Looks to me like you are not dealing properly with your Initialization Vector (IV).
It's been a long time since I last read about AES, IVs and block chaining, but your line
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
does not seem to be OK. In the case of AES, you can think of the initialization vector as the "initial state" of a cipher instance, and this state is a bit of information that you can not get from your key but from the actual computation of the encrypting cipher. (One could argue that if the IV could be extracted from the key, then it would be of no use, as the key is already given to the cipher instance during its init phase).
Therefore, you should get the IV as a byte[] from the cipher instance at the end of your encryption
cipherOutputStream.close();
byte[] iv = encryptCipher.getIV();
and you should initialize your Cipher in DECRYPT_MODE with this byte[] :
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Then, your decryption should be OK.
Hope this helps.
The IV that your using for decryption is incorrect. Replace this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
With this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
And that should solve your problem.
Below includes an example of a simple AES class in Java. I do not recommend using this class in production environments, as it may not account for all of the specific needs of your application.
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AES
{
public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
byte[] transformedBytes = null;
try
{
final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
cipher.init(mode, keySpec, ivSpec);
transformedBytes = cipher.doFinal(messageBytes);
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e)
{
e.printStackTrace();
}
return transformedBytes;
}
public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
{
//Retrieved from a protected local file.
//Do not hard-code and do not version control.
final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";
//Retrieved from a protected database.
//Do not hard-code and do not version control.
final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";
//Extract the iv and the ciphertext from the shadow entry.
final String[] shadowData = shadowEntry.split(":");
final String base64Iv = shadowData[0];
final String base64Ciphertext = shadowData[1];
//Convert to raw bytes.
final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);
//Decrypt data and do something with it.
final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);
//Use non-blocking SecureRandom implementation for the new IV.
final SecureRandom secureRandom = new SecureRandom();
//Generate a new IV.
secureRandom.nextBytes(ivBytes);
//At this point instead of printing to the screen,
//one should replace the old shadow entry with the new one.
System.out.println("Old Shadow Entry = " + shadowEntry);
System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
System.out.println("New Shadow Entry = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
}
}
Note that AES has nothing to do with encoding, which is why I chose to handle it separately and without the need of any third party libraries.
Online Editor Runnable version:-
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
//System.out.println("encrypted string: "
// + Base64.encodeBase64String(encrypted));
//return Base64.encodeBase64String(encrypted);
String s = new String(Base64.getEncoder().encode(encrypted));
return s;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(encrypt(key, initVector, "Hello World"));
System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
}
}
This is an improvement over the accepted answer.
Changes:
(1) Using random IV and prepend it to the encrypted text
(2) Using SHA-256 to generate a key from a passphrase
(3) No dependency on Apache Commons
public static void main(String[] args) throws GeneralSecurityException {
String plaintext = "Hello world";
String passphrase = "My passphrase";
String encrypted = encrypt(passphrase, plaintext);
String decrypted = decrypt(passphrase, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);
}
private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}
private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}
public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
byte[] initVector = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(initVector);
Cipher cipher = getCipher();
cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] encrypted = cipher.doFinal(value.getBytes());
return DatatypeConverter.printBase64Binary(initVector) +
DatatypeConverter.printBase64Binary(encrypted);
}
public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
Cipher cipher = getCipher();
cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
return new String(original);
}
It's often the good idea to rely on standard library provided solution:
private static void stackOverflow15554296()
throws
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// prepare key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecretKey aesKey = keygen.generateKey();
String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
aesKey.getEncoded()
);
// cipher engine
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// cipher input
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] clearTextBuff = "Text to encode".getBytes();
byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);
// recreate key
byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");
// decipher input
aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
System.out.println(new String(decipheredBuff));
}
This prints "Text to encode".
Solution is based on Java Cryptography Architecture Reference Guide and https://stackoverflow.com/a/20591539/146745 answer.
Another solution using java.util.Base64 with Spring Boot
Encryptor Class
package com.jmendoza.springboot.crypto.cipher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
#Component
public class Encryptor {
#Value("${security.encryptor.key}")
private byte[] key;
#Value("${security.encryptor.algorithm}")
private String algorithm;
public String encrypt(String plainText) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
}
public String decrypt(String cipherText) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
}
}
EncryptorController Class
package com.jmendoza.springboot.crypto.controller;
import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/cipher")
public class EncryptorController {
#Autowired
Encryptor encryptor;
#GetMapping(value = "encrypt/{value}")
public String encrypt(#PathVariable("value") final String value) throws Exception {
return encryptor.encrypt(value);
}
#GetMapping(value = "decrypt/{value}")
public String decrypt(#PathVariable("value") final String value) throws Exception {
return encryptor.decrypt(value);
}
}
application.properties
server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0
Example
http://localhost:8082/cipher/encrypt/jmendoza
2h41HH8Shzc4BRU3hVDOXA==
http://localhost:8082/cipher/decrypt/2h41HH8Shzc4BRU3hVDOXA==
jmendoza
Optimized version of the accepted answer.
no 3rd party libs
includes IV into the encrypted message (can be public)
password can be of any length
Code:
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encryptor {
public static byte[] getRandomInitialVector() {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] initVector = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(initVector);
return initVector;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static byte[] passwordTo16BitKey(String password) {
try {
byte[] srcBytes = password.getBytes("UTF-8");
byte[] dstBytes = new byte[16];
if (srcBytes.length == 16) {
return srcBytes;
}
if (srcBytes.length < 16) {
for (int i = 0; i < dstBytes.length; i++) {
dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
}
} else if (srcBytes.length > 16) {
for (int i = 0; i < srcBytes.length; i++) {
dstBytes[i % dstBytes.length] += srcBytes[i];
}
}
return dstBytes;
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
return null;
}
public static String encrypt(String key, String value) {
return encrypt(passwordTo16BitKey(key), value);
}
public static String encrypt(byte[] key, String value) {
try {
byte[] initVector = Encryptor.getRandomInitialVector();
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String encrypted) {
return decrypt(passwordTo16BitKey(key), encrypted);
}
public static String decrypt(byte[] key, String encrypted) {
try {
String[] encryptedParts = encrypted.split(" ");
byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
if (initVector.length != 16) {
return null;
}
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
Usage:
String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);
Example output:
QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World

encrypting with AES CBC Java

I have little problem. When I try to encrypt text and then decrypt this text I get an error:
javax.crypto.IllegalBlockSizeException: Input length must be multiple
of 16 when decrypting with padded cipher
Here is my code:
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
*
* #author Grzesiek
*/
public class SymmethricCipherCBC {
/* Klucz: */
private byte[] keyBytes = new byte[] {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
0x00,0x01,0x02,0x03,0x04,0x05
};
/* Wektor inicjalizacyjny: */
private byte[] ivBytes = new byte[] {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
0x00,0x01,0x02,0x03,0x04,0x05
};
private Cipher cipher;
private SecretKeySpec keySpec;
private IvParameterSpec ivSpec;
public SymmethricCipherCBC() throws NoSuchAlgorithmException, NoSuchPaddingException{
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //Utworzenie obiektu dla operacji szyfrowania/deszyfrowania algorytmem AES w trybie CBC.
keySpec = new SecretKeySpec(keyBytes, "AES"); // Utworzenie obiektu klucza dla algorytmu AES z tablicy bajtow
ivSpec = new IvParameterSpec(ivBytes); // // Utworzenie obiektu dla wektora inicjalizacyjnego
}
public String encryptText(String plainText) throws NoSuchAlgorithmException,
InvalidKeyException,
NoSuchPaddingException,
InvalidAlgorithmParameterException,
ShortBufferException,
IllegalBlockSizeException,
BadPaddingException,
UnsupportedEncodingException{
int cipherTextLength;
byte[] cipherText; // Bufor dla szyfrogramu
byte[] plainTextBytes = plainText.getBytes(); // Reprezentacja tekstu jawnego w bajtach
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); //Inicjalizacja obiektu dla operacji szyfrowania z kluczem okreslonym przez keySpec:
cipherText = new byte[cipher.getOutputSize(plainTextBytes.length)]; //Utworzenie buforu dla szyfrogramu
cipherTextLength = cipher.update(plainTextBytes, 0, plainTextBytes.length, cipherText, 0); // Szyfrowanie tekstu jawnego
cipherTextLength += cipher.doFinal(cipherText, cipherTextLength); //Zakonczenie szyfrowania
return new BigInteger(1, cipherText).toString(16); // zapisanie 16
}
public String decryptText(String ciptherTextString) throws InvalidKeyException, InvalidAlgorithmParameterException, ShortBufferException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException{
byte[] cipherTextBytes = ciptherTextString.getBytes();
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); //Inicjalizacja obiektu cipher dla odszyfrowywania z kluczem okreslonym przez keySpec
byte[] plainTextBytes = new byte[cipher.getOutputSize(cipherTextBytes.length)]; // Utworzenie wyzerowanej tablicy
int plainTextLength = cipher.update(cipherTextBytes, 0, cipherTextBytes.length, plainTextBytes, 0);
plainTextLength += cipher.doFinal(plainTextBytes, plainTextLength);
return new String(plainTextBytes); //Odtworzona wiadomosc
}
}
Any ideas what I should do?
You're doing it harder than necessary, and you're encrypting your cipher text when doing
cipher.doFinal(cipherText, cipherTextLength);
I would rewrite it as is:
public String encryptText(String plainText) throws ... {
byte[] plainTextBytes = plainText.getBytes("UTF8");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plainTextBytes);
return toHex(encrypted);
}
public String decryptText(String cipherTextString) throws ... {
byte[] cipherTextBytes = fromHex(cipherTextString);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] plainTextBytes = cipher.doFinal(cipherTextBytes);
return new String(plainTextBytes, "UTF8");
}
as far as I can tell, you are taking the byte array output from the encryption algorithm, and converting it to a hex string using BigInteger. then the decryption algorithm takes the hex string and converts it to the byte representation of the ASCII characters in the hex string using .toString()
This is where your code is wrong (among other places). rather than turning, say, the hex string output "FFFF" into a byte array [0xff, 0xff] it turns it into the byte array [0x46,0x46,0x46,0x46] (e.g. the ASCII byte representation of the upper case F). This means that not only will all of the bytes in your conversion be wrong, the byte array will be the wrong length (which causes the exception you listed in your question).
Instead, you should return byte[] from your encryption method, and accept byte[] as a parameter in your decryption method. failing that, you should use something like Apache Commons Codec's Hex class to reliably convert between byte arrays and hex strings.

Encrypt in Ruby and Decrypt in Java - Why is it not working?

What am I doing wrong? I expected the Java program to print "private". My goal is to try to write the MessageEncryptor.decrypt ruby method in Java.
Ruby encryption (most code was taken from MessageEncryptor, but modified not to Marshal), but I've extracted it so that it's easier to see what's going on:
require 'openssl'
require 'active_support/base64'
#cipher = 'aes-256-cbc'
d = OpenSSL::Cipher.new(#cipher)
#secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1("password", "some salt", 1024, d.key_len)
cipher = OpenSSL::Cipher::Cipher.new(#cipher)
iv = cipher.random_iv
cipher.encrypt
cipher.key = #secret
cipher.iv = iv
encrypted_data = cipher.update("private")
encrypted_data << cipher.final
puts [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--")
Which printed:
tzFUIVllG2FcYD7xqGPmHQ==--UAPvdm3oN3Hog9ND9HrhEA==
Java code:
package decryptruby;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class DecryptRuby {
public static String decrypt(String encrypted, String pwd, byte[] salt)
throws Exception {
String[] parts = encrypted.split("--");
if (parts.length != 2) return null;
byte[] encryptedData = Base64.decodeBase64(parts[0]);
byte[] iv = Base64.decodeBase64(parts[1]);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(pwd.toCharArray(), salt, 1024, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey aesKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv));
byte[] result = cipher.doFinal(encryptedData);
return result.toString();
}
public static void main(String[] args) throws Exception {
String encrypted = "tzFUIVllG2FcYD7xqGPmHQ==--UAPvdm3oN3Hog9ND9HrhEA==";
System.out.println("Decrypted: " + decrypt(encrypted, "password", "some salt".getBytes()));
}
}
Which printed
Decrypted: [B#432a0f6c
This is the problem - or at least a problem:
byte[] result = cipher.doFinal(encryptedData);
return result.toString();
You're calling toString() on a byte array. Arrays don't override toString(). That won't give you what you want at all - as you can see. Instead, you need to write something like:
return new String(result, "UTF-8");
... but you need to know what encoding was used to turn the original string into bytes before encryption. It's not clear to me from the Ruby code what encoding is used, but if you can be explicit about it (ideally using UTF-8) it'll make your life a lot easier.
In short, I suspect this problem has nothing to do with encryption at all - it has everything to do with converting text to bytes in Ruby and then converting the same sequence of bytes back into a string in Java.
Of course the encryption may be failing as well but that's a different matter.

Categories

Resources