I am working with the PBEWITHHMACSHA512ANDAES_128 excryption method from Java 8 If I initialize a KeySpec and Cipher object as follows:
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithHmacSHA512AndAES_128");
KeySpec spec = new PBEKeySpec(PASSWORD.toCharArray(), SALT.getBytes(), 4096, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(tmp.getEncoded(),"PBEWithHmacSHA512AndAES_128" );
Cipher cipher = Cipher.getInstance("PBEWITHHMACSHA512ANDAES_128");
cipher.init(Cipher.ENCRYPT_MODE, secret);
I can encrypt and decrypt within the same execution of a Java program, however once the program completes, any data encrypted will be irretrievable.
My first assumption was that the JRE was selecting a random IV since it is required for block ciphers, but attempting to retrieve the IV with:
cipher.getParameters().getParameterSpec(IvParameterSpec.class);
Yields an InvalidParameterSpecException. The only way to get to decryptable data is to specify an IvParameterSpec wrapped by a PBEParameterSpec (passing an IvParameterSpec alone gives an InvalidAlgorithmParameterException).
Is passing the PBEParameterSpec the correct way to specify the IV and what would be the purpose in permitting users to not specify an IV when it is needed?
Thanks
By default PBEWITHHMACSHA512ANDAES_128 uses CBC mode and PKCS5Padding.
You are right, according to the Java source code PBES2Core.java an random IV is used.
If you want to specify your own IV you have to do it this way (which uses an IV of null bytes):
KeySpec spec = new PBEKeySpec(PASSWORD.toCharArray());
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(tmp.getEncoded(), "PBEWithHmacSHA512AndAES_128");
Cipher cipher = Cipher.getInstance("PBEWITHHMACSHA512ANDAES_128");
IvParameterSpec iv = new IvParameterSpec(new byte[16]);
PBEParameterSpec pbeSpec = new PBEParameterSpec(SALT.getBytes(), 4096, iv);
cipher.init(Cipher.ENCRYPT_MODE, secret, pbeSpec);
Related
I have implemented a CBC Mode AES encryption and decryption mechanism in which I am generating Random IV and Random Secret key for each encryption attempt which is recommended.
Now, I have saved my key in a separate file and IV in another file, but after going through the different forums I have found that the IV should not be kept secure and shall be appended with the Ciphertext while encryption and at the time of decryption we can get the 16 bytes plucked out from that cipher byte array..
Now, I tried a snippet of code to achieve the same, but the result were not good as the first block was not encrypting properly; however the rest of the block does.
Can someone tell me whats wrong with my approach?
Any help will be highly appreciated thanks :).
public static byte[] encrypt (byte[] plaintext,SecretKey key,byte[] IV ) throws Exception {
//Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
//Create IvParameterSpec
IvParameterSpec ivSpec = new IvParameterSpec(IV);
System.out.println( "IV encrypt= " + ivSpec );
//Initialize Cipher for ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
//Perform Encryption
byte[] cipherText = cipher.doFinal(plaintext);
ByteArrayOutputStream b = new ByteArrayOutputStream();
b.write(IV);
b.write( cipherText );
return b.toByteArray();
}
--------------------------------------------------------------------------
public static String decrypt (byte[] cipherText, SecretKey key ) throws Exception
{
//Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
byte[] iv = Arrays.copyOfRange( cipherText , 0, 16);
//Create IvParameterSpec
IvParameterSpec ivSpec = new IvParameterSpec(iv);
//Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec,ivSpec);
//Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
----------------------------------------------------------------------------
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
// Generate Key
SecretKey key = keyGenerator.generateKey();
// Generating IV.
byte[] IV = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(IV);
System.out.println("Original Text : " + plainText);
byte[] cipherText = encrypt(plainText.getBytes("UTF-8") ,key, IV);
String decryptedText = decrypt(cipherText,key, IV);
System.out.println("DeCrypted Text : "+decryptedText);
RESULT
Original Text : This is a plain text which need to be encrypted by AES Algorithm with CBC Mode
DeCrypted Text : ûª¯Î¥pAï2EÞi+¼‹Ý$8ŶÄDDNâOæàtext which need to be encrypted by AES Algorithm with CBC Mode
Just because you copy out the IV here:
byte[] iv = Arrays.copyOfRange( cipherText , 0, 16);
Doesn't mean it isn't still present when you try to decrypt it during:
byte[] decryptedText = cipher.doFinal(cipherText);
You should decrypt everything in ciphertext except for the first 16 bytes. At the moment you're also performing AES decryption on the IV - which is why you're getting garbage.
I have been using aes-js in Node to encrypt/decrypt using AES counter mode.
As you can see in the example, I'm using it without padding and I can specify which block (0 in this case) I want to start with.
var aesCTR = new aesjs.ModeOfOperation.ctr(keyBytes, new aesjs.Counter(0));
var encryptedBytes = aesCTR.encrypt(plaintextBytes);
I wanted to reproduce the same behavior above in Java. I'm using BouncyCastle like the example below.
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted=cipher.doFinal(msgBytes);
But this implementation doesn't seem to be outputting the sames values as the one above. Plus it seems to increment the counter automatically each run (undesirable behavior in this case).
Is there a way to match the Node JS implementation using Java ?
You should get the same behaviour if you provide an IV / initial ctr value that is all zero e.g:
byte[] iv = new byte[16];
Arrays.fill(iv, (byte)0);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted=cipher.doFinal(msgBytes);
The way it is set up in your code, a random IV is generated every time you call init().
BTW, if you omit BC, you will get the stock AES implementation
I'm trying to replicate a Java-based encryption scheme in Node.js but unfortunately I'm getting inconsistent results.
Here's the Java method:
private Transfer encrypt(byte[] salt, String ticketNumber, String data) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(ticketNumber.toCharArray(), salt, 1000, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
String encoded = java.util.Base64.getEncoder().encodeToString(secret.getEncoded());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv ={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
IvParameterSpec ips = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secret, ips);
AlgorithmParameters params = cipher.getParameters();
Transfer myRetVal = new Transfer();
byte[] ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
//Set some decrypt information
myRetVal.setIv(Base64.encodeBase64String(ivBytes));
//Set the attendee data
myRetVal.setData(Base64.encodeBase64String(cipher.doFinal(data.getBytes())));
//Set the hashed Ticket number
myRetVal.setTicketNumberHashed(Base64.encodeBase64String(getHash(hashIterations, ticketNumber, salt)));
return myRetVal;
}
And my Node version:
exports.getEncryptedString = function(salt, password, data) {
var iv = new Buffer('0000000000000000');
var key = crypto.pbkdf2Sync(password, salt, 1000, 16, 'sha1');
var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
return cipher.update(data, 'utf8', 'base64') + cipher.final('base64');
};
When I pass both functions the string "SomeJSON" and the same key I get different encrypted results.
From Java: ENnQzWowzrl7LQchRmL7sA==
From Node: TGreJNmQH92gHb1bSy4xAA==
I can't figure out what is different in my Node implementation.
new Buffer('0000000000000000') uses utf8 encoding by default, but "0" in UTF-8 is the byte 0x30 whereas in Java you're using 0x00 bytes for the IV. What you want is either
var iv = new Buffer('00000000000000000000000000000000', 'hex');
or
var iv = new Buffer(16);
iv.fill(0);
After you've done your tests, you should change the procedure to generate a new IV for every encryption. The IV doesn't have to be secret, so you can simply prepend it to the ciphertext. When you want to decrypt it later, you can slice the IV off (16 bytes for AES) and use it during decryption.
I receive an error while decrypting: (javax.crypto.BadPaddingException: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt)
My code encryption / decryption:
private static byte[] password = null; // this.password = editText.getBytes();
static final byte[] ivBytes = {'6','g','6','o','d','a','0','u','4','n','w','i','6','9','i','j'};
public static byte[] encrypt(String text) throws Exception {
byte[] clear = text.getBytes("UTF-8");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(password);
kgen.init(256, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] key = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
public static String decrypt(byte[] encrypted) throws Exception {
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(password);
kgen.init(256, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] key = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
String decrypted = new String(cipher.doFinal(encrypted));
return decrypted;
}
I suspect that the bug generateKey.
You're doing two things wrong:
Generating a key from a password by using the key to seed a PRNG is a bad idea. Use password-based-encryption instead. Java has an implementation of PKCS#5 that will generate a key from a password.
You need to use a new strong-random IV for each encryption:
When you encrypt, don't specify an IV in cipher.init(). A new one will be generated for you.
encrypt() needs to serialise both the IV (cipher.getIV()) and the ciphertext into a byte array.
decrypt(): separate the IV from the ciphertext, build an IvParameterSpec from it and feed into cipher.init() as you currently do.
Your issues is that when you decrypt, you generate a new secret key instead of deriving it from password. Check out this blog post to see how password-based encryption has to be implemented. There are examples of encryption and decryption functions.
Replace the below line:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
with below line:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding","BC");
I am doing a simple implementation for AES Algorithm.
// Get the Key Generator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available
// Generate the secret key specs.
SecretKey secret = kgen.generateKey();
byte[] raw = secret.getEncoded();
String key = new String(Base64.encodeBase64(raw));
I am saving "key" in database.
Now again in another operation i am fetching a "key" from database n trying to decrypt data. i call decryption function as
String dencryptReq = Utils.decrypt2(new String(Base64.decodeBase64(secretKeyInformation.getSecretKey().getBytes())),Base64.decodeBase64(encryptReq.getBytes()) );
public static String decrypt2(String key, byte[] encrypted)
throws GeneralSecurityException {
byte[] raw = Base64.decodeBase64(key.getBytes());
if (raw.length != 16) {
throw new IllegalArgumentException("Invalid key size.");
}
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec,
new IvParameterSpec(new byte[16]));
byte[] original = cipher.doFinal(encrypted);
return new String(original, Charset.forName("US-ASCII"));
}
But it is throwing me invalid key size exception.
If i do in one time this without saving in databse and fetching from database it is working fine.
I have tried your code with some modifications I have used apache commons codec library for Base64 conversion,
/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec("password".toCharArray(), "salt".getBytes(), 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal("Hello, World! My data is here.. !".getBytes("UTF-8"));
System.out.println("cipher :"+new String(ciphertext));
/*String-key convertion */
String stringKey=Base64.encodeBase64String(secret.getEncoded());//To String key
byte[] encodedKey = Base64.decodeBase64(stringKey.getBytes());
SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");// Convert from string
/* Decrypt the message, given derived key and initialization vector. */
Cipher cipher1 = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher1.init(Cipher.DECRYPT_MODE, originalKey, new IvParameterSpec(iv));
String plaintext = new String(cipher1.doFinal(ciphertext), "UTF-8");
System.out.println(plaintext);
This code worked perfectly in my system.