For our current project we need to send encripted data in TripleDES MD5 CBC mode to another company. The values for the pass, salt and init vector are given by the company and are not the ones given here (but with the same length).
They have sent us the code they use to encript it (in .Net C#) which is the following:
//Encription parameter def
static private string _pass = "12345678901234";
static private string _salt = "123456";
static private string _alg = "MD5";
static private string _iv = "1234567890123456";
public static string EncriptString(string textPlain)
{
return EncriptString(textPlain, _pass, _salt, _alg, 1, _iv, 128);
}
public static string EncriptString(string textPlain, string passBase, string saltValue, string hashAlgorithm, int passwordIterations, string initVector, int keySize)
{
byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
byte[] plainTextBytes = Encoding.UTF8.GetBytes(textPlain);
PasswordDeriveBytes password = new PasswordDeriveBytes(passBase, saltValueBytes, hashAlgorithm, passwordIterations);
byte[] keyBytes = password.GetBytes(keySize / 8);
RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.Mode = CipherMode.CBC;
ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
byte[] cipherTextBytes = memoryStream.ToArray();
memoryStream.Close();
cryptoStream.Close();
string cipherText = Convert.ToBase64String(cipherTextBytes);
return cipherText;
}
I've being searching for a day on Google and Stackoverflow trying to translate this into JAVA and I'm not able. This is my current approach:
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* #author alvaro
*/
public class Encriptor {
// Constants -----------------------------------------------------
private static final String PASS_PHRASE = "12345678901234";//says wrong length
private static final String SALT_VALUE = "123456";
private static final int PASSWORD_ITERATIONS = 1;
private static final String INIT_VECTOR = "1234567890123456";
private static final int KEY_SIZE = 128;
// Attributes ----------------------------------------------------
// Static --------------------------------------------------------
// Constructors --------------------------------------------------
// Public --------------------------------------------------------
public String encrypt(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException {
MessageDigest digest = MessageDigest.getInstance(DIGEST);
digest.update(SALT_VALUE.getBytes());
byte[] bytes = digest.digest(PASS_PHRASE.getBytes(ENCODING));
SecretKey password = new SecretKeySpec(bytes, "AES");
//Initialize objects
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] IV = INIT_VECTOR.getBytes();
IvParameterSpec ivParamSpec = new IvParameterSpec(IV);
cipher.init(Cipher.ENCRYPT_MODE, password, ivParamSpec);
byte[] encryptedData = cipher.doFinal(text.getBytes(ENCODING));
return new BASE64Encoder().encode(encryptedData).replaceAll("\n", "");
}
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
// Private -------------------------------------------------------
// Inner classes -------------------------------------------------
}
The problem is that the MD5 hash is not done the same way in Java than in C#.
Test code:
import org.junit.Test;
import static org.junit.Assert.*;
public class EncriptorTest {
#Test
public void shouldEncriptTextCorrectly() {
// GIVEN
String input = '<xml><oficina>1234</oficina><empleado>123456</empleado></xml>'
String expected = 'Lz1aG3CFYoyzjGcMzJXDB7DQgscrv9scP+d5JY8/fiUN6LV2RsnSPqDU/E5BGKz3QbeSl3RyhUgnYyN3uBBRJA=='
// WHEN
String output = new Encriptor().encrypt(input)
//THEN
assertEquals('Wrong encription', expected, output)
}
}
SOLUTION
At the end I used a solution given in another Stackoverlow question in which a port of PasswordDeriveBytes to Java is given.
You've had a miscommunication with your other party.
You say that you're trying to use Triple DES, MD5, CBC; however the .Net code they're using says this:
RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.Mode = CipherMode.CBC;
Rijndael is the name of the algorithm that was chosen as the AES; the example they gave you is using AES, not Triple DES.
Triple DES takes 3*56 bits for the key (expanded to 3*64), which is 168 or 192 bits, depending on how you count the bits; the API probably consumes and expects 168 bits. AES only supports 128, 192, and 256 bit key sizes, which explains why you're probably getting the wrong size errors.
You need to find out whether they intend to use Triple DES or AES and fix both sides of the code.
As an aside:
Single DES is known to be very insecure; a couple dollars spent on Amazon EC2 can crack DES. Triple DES, however, is roughly safe, but should be discouraged for new development.
MD5 is known to be very insecure, in particular, it is trivially vulnerable to length extension attacks, pre-image attacks, collision attacks, etc. Consider SHA 1, 2, or 3 instead.
Related
I am trying to encrypt a plain text string to integrate with a third-party system using AES encryption. The receiver does not have any document to explain what their encryption algorithm is, and they've simply shared the below Java code to explain how the encryption works:
import java.security.Key;
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 javax.xml.bind.DatatypeConverter;
public class CipherData {
private static final String SALT = "pa(MS"; //Salt. Required for key generation
private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; //Encryption Algorithm/Cipher/Padding
private static String SECRET_KEY_FACTORY_ALGORITHM = "PBKDF2WithHmacSHA1";
private static int KEY_SIZE = 256;
private static final String TOKEN = "Pre123454sk"; //Password. Used for Key Generation
private static String initVector = "pre1234Init12345"; //IV. Required for Key generation
private static int KEY_ITERATIONS = 22123;
public static String encrypt(String value) throws Exception { //Encryption Module
Cipher cipher = Cipher.getInstance(ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
Key key = generateKey();
cipher.init(1, key, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return base64Encode(encrypted);
}
public static String decrypt(String value) throws Exception { //Decryption module
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
Key key = generateKey();
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(2, key, iv);
byte[] original = cipher.doFinal(base64Decode(value));
return new String(original);
}
private static Key generateKey() throws Exception { //AES Key Generation
byte[] saltBytes = SALT.getBytes("UTF-8");
SecretKeyFactory skf = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_ALGORITHM);
PBEKeySpec spec = new PBEKeySpec(TOKEN.toCharArray(), saltBytes, KEY_ITERATIONS, KEY_SIZE);
SecretKey secretKey = skf.generateSecret(spec);
SecretKeySpec key = new SecretKeySpec(secretKey.getEncoded(), "AES");
return key;
}
private static String base64Encode(byte[] token) {
String encoded = DatatypeConverter.printBase64Binary(token);
return encoded;
}
private static byte[] base64Decode(String token) {
return DatatypeConverter.parseBase64Binary(token);
}
public static void main(String[] args) {
String clearPAN="ABCDE1234F", encrPAN="", decrPAN="";
try {
encrPAN=encrypt(clearPAN);
decrPAN=decrypt(encrPAN);
System.out.println("Clear PAN: " + clearPAN);
System.out.println("Encrypted PAN: " + encrPAN);
System.out.println("Decrypted PAN: " + decrPAN);
}
catch (Exception e) {
System.out.print("Exception Occured in main()");
e.printStackTrace();
}
}
}
I am developing my application in .NET, and I'm unable to get the same string as the receiver gets by using the above Java code, and we're out of ideas on what we should be doing.
Here is my .NET algorithm (I have just inferred this logic from the Java code, and this is my first time with Java, so please be kind if I've made a stupid error):
private static String TOKEN = "Pre123454sk";
private static String initVector = "pre1234Init12345";
private static string Encryption(string plainText)
{
using (var aesProvider = new AesCryptoServiceProvider())
{
//String SALT = "pa(MS";
PasswordDeriveBytes pdb = new PasswordDeriveBytes(TOKEN, Encoding.UTF8.GetBytes("pa(MS"));
pdb.IterationCount = 22123;
aesProvider.KeySize = 256;
aesProvider.Padding = PaddingMode.PKCS7;
aesProvider.Mode = CipherMode.CBC;
aesProvider.Key = pdb.GetBytes(16);
aesProvider.IV = Encoding.UTF8.GetBytes(initVector);
Byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
ICryptoTransform encryptor = aesProvider.CreateEncryptor(aesProvider.Key, aesProvider.IV);
using (var memStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
Byte[] cipherTextBytes = memStream.ToArray();
memStream.Close();
memStream.Flush();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
private static string Decryption(string plainText)
{
using (var aesProvider = new AesCryptoServiceProvider())
{
PasswordDeriveBytes pdb = new PasswordDeriveBytes(TOKEN, Encoding.UTF8.GetBytes("pa(MS"));
pdb.IterationCount = 22123;
aesProvider.KeySize = 256;
aesProvider.Padding = PaddingMode.Zeros;
aesProvider.Mode = CipherMode.CBC;
aesProvider.Key = pdb.GetBytes(16);
aesProvider.IV = Encoding.UTF8.GetBytes(initVector);
byte[] cipherTextBytes1 = Convert.FromBase64String(plainText);
ICryptoTransform decryptor = aesProvider.CreateDecryptor(aesProvider.Key, aesProvider.IV);
using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes1))
{
using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
{
using (StreamReader streamReader = new StreamReader((Stream)cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
I've already tried replacing the TOKEN value with the IV value in the PasswordDeriveBytes() function, but it's still giving me the same error (the receiving system is unable to decrypt this request).
Here is the plain text that I'm trying to encrypt: CXQPM4656P
Here is what I'm getting from the .NET code: pKjfaKu4AxBEbagiAWoLkg==
Here is what I should be getting: kY8lgWh97fqkm9gS8zgMHg==
I'm out of ideas at this point, and I have no support from the other end. Would be great if someone can help me figure it out.
Thanks to Topaco (first comment in the question), I was able to sort it out.
Here is Topaco's original answer:
The Java code uses PBKDF2 with HMAC/SHA1, the C# code an algorithm based on PBKDF1. For PBKDF2 in the C# code PasswordDeriveBytes has to be replaced by Rfc2898DeriveBytes (with HMAC/SHA1 as default). Note that the .NET implementation expects a minimum 8 bytes salt. Also, Java uses a 32 bytes key, the C# code a 16 bytes key. With consistent key derivation and key size, the generated ciphertexts are identical. –
Topaco
The only thing I had to change was to upgrade from .NET 5 to .NET 6.
I am trying to understand how a derived key is obtained by using PBKDF2, with SHA256.
I am getting tangled up, and need a clear, easy to understand example.
What I have so far:
I have found https://en.wikipedia.org/wiki/PBKDF2 which has a an example, but with SHA1, with the following values:
PASSWORD plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd UTF8
SALT A009C1A485912C6AE630D3E744240B04 HEX
Hashing function SHA1
Key Size 128
Iterations 1000
I have been using https://gchq.github.io/CyberChef and can get the output 17EB4014C8C461C300E9B61518B9A18B which matches the derived key bytes in the Wikipedia example.
I have been working with https://mkyong.com/java/java-aes-encryption-and-decryption/ which has a method named getAESKeyFromPassword, which is here:
// Password derived AES 256 bits secret key
public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
// iterationCount = 65536
// keyLength = 256
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return secret;
}
I want to carry out the same "investigation", as I have done with the Wikipedia page, SHA1, and CyberChef, but using SHA256 (replacing the values in the Java code, to match the salt, password, iterations, from the example).
This is where my confusion starts:
If I were to use CyberChef to work on the same values as above, but replace with SHA256:
PASSWORD plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd UTF8
SALT A009C1A485912C6AE630D3E744240B04 HEX
Hashing function SHA256
Key Size 128
Iterations 1000
I would expect the derived key to be the same in CyberChef, as the https://mkyong.com/java/java-aes-encryption-and-decryption/ example.
It's not.
I cannot help but think there is a flaw in my understanding.
Can someone please provide a simple (worked-through) example of PBKDF2 with SHA256, so I can understand what is going on. If the derived key is not meant to be the same (as with the SHA1 example, please explain why).
Is the Java SecretKey:
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
The same as the derived key?
There seems to be a lack of easy-to-understand examples to follow.
Thanks
Miles.
Thank you all for your input, especially Topaco :)
I am going to answer my question, as I have spent some time working on a MCVE, and have managed to get the same SecretKey as cyberChef.
The secret key value is: 28869b5f31ae29236f164c5cb33e2e3bb46f483867a15f8e7208e1836070f64a
Here is the output from cyberChef:
Here is the Java code, and output from running it:
package crypto;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
public class EncryptDecryptAesGcmPassword {
private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
private static final int TAG_LENGTH_BIT = 128; // must be one of {128, 120, 112, 104, 96}
private static final int IV_LENGTH_BYTE = 12;
private static final int SALT_LENGTH_BYTE = 16;
public static final int ITERATION_COUNT = 1000;
public static final int KEY_LENGTH = 256;
private static final Charset UTF_8 = StandardCharsets.UTF_8;
// return a base64 encoded AES encrypted text
public static String encrypt(byte[] salt, byte[] pText, String password) throws Exception {
// GCM recommended 12 bytes iv?
byte[] iv = getRandomNonce(IV_LENGTH_BYTE);
// secret key from password
SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
// ASE-GCM needs GCMParameterSpec
cipher.init(Cipher.ENCRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
byte[] cipherText = cipher.doFinal(pText);
// prefix IV and Salt to cipher text
byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length)
.put(iv)
.put(salt)
.put(cipherText)
.array();
// string representation, base64, send this string to other for decryption.
return Base64.getEncoder().encodeToString(cipherTextWithIvSalt);
}
// we need the same password, salt and iv to decrypt it
private static String decrypt(String cText, String password) throws Exception {
byte[] decode = Base64.getDecoder().decode(cText.getBytes(UTF_8));
// get back the iv and salt from the cipher text
ByteBuffer bb = ByteBuffer.wrap(decode);
byte[] iv = new byte[IV_LENGTH_BYTE];
bb.get(iv);
byte[] salt = new byte[SALT_LENGTH_BYTE];
bb.get(salt);
byte[] cipherText = new byte[bb.remaining()];
bb.get(cipherText);
// get back the aes key from the same password and salt
SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
byte[] plainText = cipher.doFinal(cipherText);
return new String(plainText, UTF_8);
}
public static byte hexToByte(String hexString) {
int firstDigit = toDigit(hexString.charAt(0));
int secondDigit = toDigit(hexString.charAt(1));
return (byte) ((firstDigit << 4) + secondDigit);
}
public static byte[] decodeHexString(String hexString) {
if (hexString.length() % 2 == 1) {
throw new IllegalArgumentException(
"Invalid hexadecimal String supplied.");
}
byte[] bytes = new byte[hexString.length() / 2];
for (int i = 0; i < hexString.length(); i += 2) {
bytes[i / 2] = hexToByte(hexString.substring(i, i + 2));
}
return bytes;
}
private static int toDigit(char hexChar) {
int digit = Character.digit(hexChar, 16);
if (digit == -1) {
throw new IllegalArgumentException(
"Invalid Hexadecimal Character: "+ hexChar);
}
return digit;
}
// Random byte[] with length numBytes
public static byte[] getRandomNonce(int numBytes) {
byte[] nonce = new byte[numBytes];
new SecureRandom().nextBytes(nonce);
return nonce;
}
// Password derived AES 256 bits secret key
public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
// iterationCount = 1000
// keyLength = 256
KeySpec spec = new PBEKeySpec(password, salt, ITERATION_COUNT,
KEY_LENGTH);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
String encodedKey = hex(secret.getEncoded());
// print SecretKey as hex
System.out.println("SecretKey: " + encodedKey);
return secret;
}
// hex representation
public static String hex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
public static void main(String[] args) throws Exception {
String OUTPUT_FORMAT = "%-30s:%s";
String PASSWORD = "plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd";
// plain text
String pText = "AES-GSM Password-Bases encryption!";
// convert hex string to byte[]
byte[] salt = decodeHexString("A009C1A485912C6AE630D3E744240B04");
String encryptedTextBase64 = EncryptDecryptAesGcmPassword.encrypt(salt, pText.getBytes(UTF_8), PASSWORD);
System.out.println("\n------ AES GCM Password-based Encryption ------");
System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText));
System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (base64) ", encryptedTextBase64));
System.out.println("\n------ AES GCM Password-based Decryption ------");
System.out.println(String.format(OUTPUT_FORMAT, "Input (base64)", encryptedTextBase64));
String decryptedText = EncryptDecryptAesGcmPassword.decrypt(encryptedTextBase64, PASSWORD);
System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));
}
}
Running this code, produces the following:
SecretKey: 28869b5f31ae29236f164c5cb33e2e3bb46f483867a15f8e7208e1836070f64a
------ AES GCM Password-based Encryption ------
Input (plain text) :AES-GSM Password-Bases encryption!
Encrypted (base64) :/PuTLBTKVWgJB2iMoAnBpIWRLGrmMNPnRCQLBABOkwNeY8BrrdtoRNVFqZ+xmUjvF2PET6Ne2+PAp34QLCUFjQodTMdmzaNAfzcLWOf4
------ AES GCM Password-based Decryption ------
Input (base64) :/PuTLBTKVWgJB2iMoAnBpIWRLGrmMNPnRCQLBABOkwNeY8BrrdtoRNVFqZ+xmUjvF2PET6Ne2+PAp34QLCUFjQodTMdmzaNAfzcLWOf4
SecretKey: 28869b5f31ae29236f164c5cb33e2e3bb46f483867a15f8e7208e1836070f64a
Decrypted (plain text) :AES-GSM Password-Bases encryption!
Thanks
Miles.
I am trying to Encrypt and Decrypt using Bouncy castle. I am getting below error. How to fix or is there any better way to encrypt and decrypt using Bouncy castle
Exception in thread "main" java.security.InvalidKeyException: invalid parameter passed to AES init - org.bouncycastle.crypto.params.ParametersWithIV
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(Unknown Source)
at javax.crypto.Cipher.init(Cipher.java:1394)
at com.test.PBE.encrypt(PBE.java:39)
at com.test.PBE.main(PBE.java:26)
This is my code
import java.security.SecureRandom;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class PBE {
private static final String salt = "A long, but constant phrase that will be used each time as the salt.";
private static final int iterations = 2000;
private static final int keyLength = 256;
private static final SecureRandom random = new SecureRandom();
public static void main(String [] args) throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
String passphrase = "The quick brown fox jumped over the lazy brown dog";
String plaintext = "hello world";
byte [] ciphertext = encrypt(passphrase, plaintext);
String encryptedText=ciphertext.toString();
System.out.println("text::"+encryptedText);
String recoveredPlaintext = decrypt(passphrase, encryptedText);
System.out.println(recoveredPlaintext);
}
private static byte [] encrypt(String passphrase, String plaintext) throws Exception {
SecretKey key = generateKey(passphrase);
Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");
cipher.init(Cipher.ENCRYPT_MODE, key, generateIV(cipher), random);
return cipher.doFinal(plaintext.getBytes());
}
private static String decrypt(String passphrase, String encryptedText) throws Exception {
byte[] ciphertext=encryptedText.getBytes();
SecretKey key = generateKey(passphrase);
Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");
cipher.init(Cipher.DECRYPT_MODE, key, generateIV(cipher), random);
return new String(cipher.doFinal(ciphertext));
}
private static SecretKey generateKey(String passphrase) throws Exception {
PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
return keyFactory.generateSecret(keySpec);
}
private static IvParameterSpec generateIV(Cipher cipher) throws Exception {
byte [] ivBytes = new byte[cipher.getBlockSize()];
random.nextBytes(ivBytes);
return new IvParameterSpec(ivBytes);
}
}
It is important to understand that the result of encryption is binary data and that it cannot be treated as if it were text.
If you want to convert the encrypted data to text, then you should hex-encode it, for example with a library like this one from apache commons. In most cases, it is not ever necessary to ever use a text version of the encrypted data.
And in particular, your way of converting the byte array through an intervening String certainly doesn't do what you intended.
Given your code, you will want to change your decrypt method to take a byte[] and pass the byte[] encryptedtext to it.
When running below programme i am getting this exception. Not able to figure out what the issue as AES allows the 128 -256 bit key?
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 29 bytes
at com.sun.crypto.provider.AESCipher.engineGetKeySize(DashoA13*..)
at javax.crypto.Cipher.b(DashoA13*..)
Getting exception at line 20
Here is the programme
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class AESEncryptionDecryptionTest {
private static final String ALGORITHM = "AES";
private static final String myEncryptionKey = "ThisIsSecurityKey";
private static final String UNICODE_FORMAT = "UTF8";
public static String encrypt(String valueToEnc) throws Exception {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.ENCRYPT_MODE, key); //////////LINE 20
byte[] encValue = c.doFinal(valueToEnc.getBytes());
String encryptedValue = new BASE64Encoder().encode(encValue);
return encryptedValue;
}
public static String decrypt(String encryptedValue) throws Exception {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedValue);
byte[] decValue = c.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
private static Key generateKey() throws Exception {
byte[] keyAsBytes;
keyAsBytes = myEncryptionKey.getBytes(UNICODE_FORMAT);
Key key = new SecretKeySpec(keyAsBytes, ALGORITHM);
return key;
}
public static void main(String[] args) throws Exception {
String value = "password1";
String valueEnc = AESEncryptionDecryptionTest.encrypt(value);
String valueDec = AESEncryptionDecryptionTest.decrypt(valueEnc);
System.out.println("Plain Text : " + value);
System.out.println("Encrypted : " + valueEnc);
System.out.println("Decrypted : " + valueDec);
}
}
AES allows 128, 192 or 256 bit key length. That is 16, 24 or 32 byte. Try taking just the first 16 bytes of your mEncryptionKey as the keyAsBytes.
Edit:
An after though occurred to me. A habit I have formed, and one which I recommend, is to take a SHA hash of a password/passphrase, and use that as the source bytes of your key. Taking a hash guarantees the key data will be the correct size, irrespective of the length of the password/passphrase. Your current implementation of using the String bytes has two problems;
It will break your key generation if someone uses a short password.
Two different passwords for which the first 16 bytes are the same will create the same key.
Both of these problems are eliminated by using a hash.
Take a look at the buildKey() method in this class; https://github.com/qwerky/DataVault/blob/master/src/qwerky/tools/datavault/DataVault.java
The key uses randomness as input, but there are stiill requirements for how it is composed. The SecretKeySpec constructor you used is for loading an already generated key into memory. Instead, use KeyGenerator.
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM);
kg.init(128);
SecretKey k = kg.generateKey();
Also note that AES-128 is now actually thought to be weaker than AES-256. It probably isn't drastically different but the benefit from the longer key size may be outweighed by simplifications elsewhere (fewer rounds).
I am trying to translate the following (working) Java code to Ruby.
public static final String PROVIDER = "BC";
public static final int IV_LENGTH = 16;
private static final String HASH_ALGORITHM = "SHA-512";
private static final String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String SECRET_KEY_ALGORITHM = "AES";
public String decrypt(SecretKey secret, String encrypted) {
Cipher decryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
String ivHex = encrypted.substring(0, IV_LENGTH * 2);
String encryptedHex = encrypted.substring(IV_LENGTH * 2);
IvParameterSpec ivspec = new IvParameterSpec(HexEncoder.toByte(ivHex));
decryptionCipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
byte[] decryptedText = decryptionCipher.doFinal(HexEncoder.toByte(encryptedHex));
String decrypted = new String(decryptedText, "UTF-8");
return decrypted;
}
My (not working) Ruby code is this:
require 'openssl'
require 'digest/sha2'
SECRET = "MY PASSWORD AS RAW TEXT"
IV_LENGHT = 16
encoded = "45D0EC4D910C0A6FF67325FF7362DCBC4613B6F3BFDFE35930D764FB1FE62251"
iv = encoded.slice(0, IV_LENGHT * 2)
e = encoded.slice(IV_LENGHT*2..-1)
binary_iv = iv.unpack('a2'*IV_LENGHT).map{|x| x.hex}.pack('c'*IV_LENGHT)
binary_e = e.unpack('a2'*IV_LENGHT).map{|x| x.hex}.pack('c'*IV_LENGHT)
c = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
c.decrypt
c.key = Digest::SHA256.digest(SECRET).slice(0, IV_LENGHT* 2 )
c.iv = binary_iv
d = c.update(binary_e)
d << c.final
puts "decrypted: #{d}\n"
I have tried the binary and non binary versions, with no luck.
Someone can point to the problem?
Based on the title here, I am going to assume that you want to be able to encrypt a message in Java, and then decrypt that message in Ruby, using password-based AES-CBC encryption.
Now, the openssl standard library in Ruby readily supports password-based key derivation function 2 based on PKCS5. You can greatly simplify your Ruby decryption code if you leverage this in your Java encryption.
Here is how you would encrypt using PBKDF2 in PKCS5 in Java:
// in Java-land
import java.security.AlgorithmParameters;
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;
...
static String printHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", (b & 0xFF)));
}
return sb.toString();
}
public static Map<String,String> encrypt(String msg, String pwd, byte[] salt)
throws Exception {
Map<String,String> retval = new HashMap<String,String>();
// prepare to use PBKDF2/HMAC+SHA1, since ruby supports this readily
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// our key is 256 bits, and can be generated knowing the password and salt
KeySpec spec = new PBEKeySpec(pwd.toCharArray(), salt, 1024, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
// given key above, our cippher will be aes-256-cbc in ruby/openssl
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
// generate the intialization vector
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
retval.put("iv", printHex(iv));
byte[] ciphertext = cipher.doFinal(msg.getBytes("UTF-8"));
retval.put("encrypted", printHex(ciphertext));
return retval;
}
public static void main(String[] args) throws Exception {
String msg = "To Ruby, from Java, with love...";
String pwd = "password";
String salt = "8 bytes!"; // in reality, you would use SecureRandom!
System.out.println("password (plaintext): " + pwd);
System.out.println("salt: " + salt);
Map<String,String> m = encrypt(msg, pwd, salt.getBytes());
System.out.println("encrypted: " + m.get("encrypted"));
System.out.println("iv: " + m.get("iv"));
}
Running the above will result in something like the following output.
password (plaintext): password
salt: 8 bytes!
encrypted: 4a39f1a967c728e11c7a5a3fb5d73ad07561f504c9d084d0b1ae600cc1f75137cbb82a4d826c060cb06e2e283449738d
iv: ecbc985b3550edc977a17acc066f2192
Hex strings are used for the encrypted message and initialization vector since you can use OpenSSL to verify the encryption/decryption process (highly recommended).
Now from a Ruby program, you would use the AES-256-CBC cipher, and derive the secret key from the password and salt strings (not byte[] as per Java). Using the output from the above-mentioned Java program, we have:
# from Ruby-land
require 'openssl'
d = OpenSSL::Cipher.new("AES-256-CBC")
d.decrypt
key = OpenSSL::PKCS5.pbkdf2_hmac_sha1("password", "8 bytes!", 1024, d.key_len)
d.key = key
d.iv = "ecbc985b3550edc977a17acc066f2192".scan(/../).map{|b|b.hex}.pack('c*')
data = "4a39f1a967c728e11c7a5a3fb5d73ad07561f504c9d084d0b1ae600cc1f75137cbb82a4d826c060cb06e2e283449738d".scan(/../).map{|b|b.hex}.pack('c*')
d.update(data) << d.final
=> "To Ruby, from Java, with love..."
NOTE: The Ruby part of this code pretty much comes verbatim from the Japanese documentation on the openssl standard library.
I once had a similar problem with CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; and decryption through the openSSL-library in C which I could't solve. I avoided the problem by using "AES/CBC/NoPadding" and by adding a particular padding to the plaintext manually.