256bit AES/CBC/PKCS5Padding with Bouncy Castle - java

I am having trouble mapping the following JDK JCE encryption code to Bouncy Castles Light-weight API:
public String dec(String password, String salt, String encString) throws Throwable {
// AES algorithm with CBC cipher and PKCS5 padding
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
// Construct AES key from salt and 50 iterations
PBEKeySpec pbeEKeySpec = new PBEKeySpec(password.toCharArray(), toByte(salt), 50, 256);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHA256And256BitAES-CBC-BC");
SecretKeySpec secretKey = new SecretKeySpec(keyFactory.generateSecret(pbeEKeySpec).getEncoded(), "AES");
// IV seed for first block taken from first 32 bytes
byte[] ivData = toByte(encString.substring(0, 32));
// AES encrypted data
byte[] encData = toByte(encString.substring(32));
cipher.init( Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec( ivData ) );
return new String( cipher.doFinal( encData ) );
}
The above works great, but is not very portable due to Oracle's restriction on encryption strengths. I've made several attempts at porting to Bouncy Castles Light-weight API but without success.
public String decrypt1(String password, String salt, String encString) throws Exception {
byte[] ivData = toByte(encString.substring(0, 32));
byte[] encData = toByte(encString.substring(32));
PKCS12ParametersGenerator gen = new PKCS12ParametersGenerator(new SHA256Digest());
gen.init(password.getBytes(), toByte(salt), 50);
CBCBlockCipher cbcBlockcipher = new CBCBlockCipher(new RijndaelEngine(256));
CipherParameters params = gen.generateDerivedParameters(256, 256);
cbcBlockcipher.init(false, params);
PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(cbcBlockcipher, new PKCS7Padding());
byte[] plainTemp = new byte[aesCipher.getOutputSize(encData.length)];
int offset = aesCipher.processBytes(encData, 0, encData.length, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
return new String(plain);
}
The above attempt results in a org.bouncycastle.crypto.DataLengthException: last block incomplete in decryption.
I have searched for examples online, but there isn't many examples of providing your own IV data for 256bit AES with CBC using PKCS5/PKCS7 as padding.
NB: The toByte function converts a String into a byte array using base64 or similar.

This should work for you:
public String dec(String password, String salt, String encString)
throws Exception {
byte[] ivData = toByte(encString.substring(0, 32));
byte[] encData = toByte(encString.substring(32));
// get raw key from password and salt
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(),
toByte(salt), 50, 256);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBEWithSHA256And256BitAES-CBC-BC");
SecretKeySpec secretKey = new SecretKeySpec(keyFactory.generateSecret(
pbeKeySpec).getEncoded(), "AES");
byte[] key = secretKey.getEncoded();
// setup cipher parameters with key and IV
KeyParameter keyParam = new KeyParameter(key);
CipherParameters params = new ParametersWithIV(keyParam, ivData);
// setup AES cipher in CBC mode with PKCS7 padding
BlockCipherPadding padding = new PKCS7Padding();
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new CBCBlockCipher(new AESEngine()), padding);
cipher.reset();
cipher.init(false, params);
// create a temporary buffer to decode into (it'll include padding)
byte[] buf = new byte[cipher.getOutputSize(encData.length)];
int len = cipher.processBytes(encData, 0, encData.length, buf, 0);
len += cipher.doFinal(buf, len);
// remove padding
byte[] out = new byte[len];
System.arraycopy(buf, 0, out, 0, len);
// return string representation of decoded bytes
return new String(out, "UTF-8");
}
I assume that you're actually doing hex encoding for toByte() since your code uses 32 characters for the IV (which provides the necessary 16 bytes). While I don't have the code you used to do the encryption, I did verify that this code will give the same decrypted output as your code.

Related

Why AES decryption giving empty result?

Encryption done in java using AES and wanted to decrypt in Python, but the result is empty in python (no errors).
See the code before marking as duplicate. I want to detect problem in my code.
java encryption code:
public static String encrypt(String plainText) throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
String keyStr = Base64.encodeToString(secretKey.getEncoded(), Base64.NO_WRAP);
byte[] initVector = new byte[16];
(new Random()).nextBytes(initVector);
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] plainTextByte = plainText.getBytes();
byte[] encryptedByte = cipher.doFinal(plainTextByte);
byte[] messagebytes = new byte[initVector.length + encryptedByte.length];
System.arraycopy(initVector, 0, messagebytes, 0, 16);
System.arraycopy(encryptedByte, 0, messagebytes, 16, encryptedByte.length);
return Base64.encodeToString(messagebytes, Base64.NO_WRAP);
}
Python code
def decrypt(key, message):
"""
Input encrypted bytes, return decrypted bytes, using iv and key
"""
byte_array = message.encode("UTF-8")
iv = byte_array[0:16] # extract the 16-byte initialization vector
messagebytes = byte_array[16:] # encrypted message is the bit after the iv
cipher = AES.new(key.encode("UTF-8"), AES.MODE_CBC, iv)
decrypted_padded = cipher.decrypt(messagebytes)
decrypted = unpad(decrypted_padded)
return decrypted.decode("UTF-8");
I see that the Java is explicitly encoding the string as Base 64 at return Base64.encodeToString(messagebytes, Base64.NO_WRAP);
But i see that you are again encoding in python byte_array = message.encode("UTF-8") where in you will have to decode the encrypted message
# Standard Base64 Decoding
decodedBytes = base64.b64decode(encodedStr)
decodedStr = str(decodedBytes, "utf-8")
And only then Decrypt the decoded message.

Android String Encryption/Decryption

I want to encrypt and decrypt a String from an EditText using AndroidKeyStore. My problem is that at the decrypt process is get a BadPaddingException.
Key generator code:
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).
setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE).build();
keyGenerator.init(keyGenParameterSpec);
keyGenerator.generateKey();
Encryption code:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null);
SecretKey secretKey = secretKeyEntry.getSecretKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
cipherIV = cipher.getIV();
plainText.setText(new String(cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
Decryption code:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null);
final SecretKey secretKey = secretKeyEntry.getSecretKey();
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
final GCMParameterSpec spec = new GCMParameterSpec(128, cipherIV);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
byte[] decrypted = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
plainText.setText(new String(decrypted, StandardCharsets.UTF_8));
This line is in error:
plainText.setText(new String(cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
If we break it apart, we have something like
byte [] cipherBytes = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
plainText.setText(new String(cipherBytes, StandardCharsets.UTF_8);
The problem is that cipherBytes is a sequence of arbitrary bytes rather than the characters of a string. The String constructor will silently replace invalid characters with something else, a process which corrupts the data.
If you want to display the cipher bytes or otherwise send it to a character oriented channel you must encode it. Typically encodings are base64 or hex. To decrypt the String you must then decode it to bytes first and then decrypt it.
Example:
byte [] cipherBytes = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
plainText.setText(Base64.encodeToString(cipherBytes, Base64.DEFAULT));
And on decrypt:
byte[] cipherBytes = Base64.decode(plainText.getText().toString(), Base64.DEFAULT);
byte[] decrypted = cipher.doFinal(cipherBytes);
byte[] decrypted = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
This line may not work well because of the call of getBytes(StandardCharsets.UTF_8). If your EditText is hex representation, try to convert it to string and then invoke getBytes(). E.g.
public static byte[] convertHexStringToByteArray(String hexString) {
int l = hexString.length();
byte[] data = new byte[l/2];
for (int i = 0; i < l; i += 2) {
data[i/2] = (byte)((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i+1), 16));
}
return data;
}

How authentication tag is calculated in AES-GCM-256

I have a sample code,which encrypt and decrypt a string using AES-GCM-256.
I am unable to understand,how authentication tag is being generated on encrypter side and how is that being used on decrypter side.
Actually here i am not generating authentication tag either on encrypter side nor validating decrypter side,so is it being done internally by library itself.
private static String encrypt(String s, byte[] k) throws Exception {
SecureRandom r = SecureRandom.getInstance("SHA1PRNG");
// Generate 128 bit IV for Encryption
byte[] iv = new byte[12]; r.nextBytes(iv);
SecretKeySpec eks = new SecretKeySpec(k, "AES");
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
// Generated Authentication Tag should be 128 bits
c.init(Cipher.ENCRYPT_MODE, eks, new GCMParameterSpec(128, iv));
byte[] es = c.doFinal(s.getBytes(StandardCharsets.UTF_8));
// Construct Output as "IV + CIPHERTEXT"
byte[] os = new byte[12 + es.length];
System.arraycopy(iv, 0, os, 0, 12);
System.arraycopy(es, 0, os, 12, es.length);
// Return a Base64 Encoded String
return Base64.getEncoder().encodeToString(os);
}
private static String decrypt(String eos, byte[] k) throws Exception {
// Recover our Byte Array by Base64 Decoding
byte[] os = Base64.getDecoder().decode(eos);
// Check Minimum Length (IV (12) + TAG (16))
if (os.length > 28) {
byte[] iv = Arrays.copyOfRange(os, 0, 12);
byte[] es = Arrays.copyOfRange(os, 12, os.length);
// Perform Decryption
SecretKeySpec dks = new SecretKeySpec(k, "AES");
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.DECRYPT_MODE, dks, new GCMParameterSpec(128, iv));
// Return our Decrypted String
return new String(c.doFinal(es), StandardCharsets.UTF_8);
}
throw new Exception();
}

Inconsistencies in encryption between Java and Node

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.

AES Algorithm JAVA Giving : java.lang.IllegalArgumentException: Invalid key size

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.

Categories

Resources