Does AES/CBC really require IV parameter? - java

I am writing a simple app to encrypt my message using AES / CBC (mode). As my understanding CBC mode requires IV parameter but I don't know why my code work without IV parameter used. Anyone can explain why? Thanks.
The encrypted message printed: T9KdWxVZ5xStaisXn6llfg== without exception.
public class TestAES {
public static void main(String[] args) {
try {
byte[] salt = new byte[8];
new SecureRandom().nextBytes(salt);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec("myPassword".toCharArray(), salt, 100, 128);
SecretKey tmp = keyFactory.generateSecret(keySpec);
SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher enCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
enCipher.init(Cipher.ENCRYPT_MODE, key);
// enCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] cipherBytes = enCipher.doFinal("myMessage".getBytes());
String cipherMsg = BaseEncoding.base64().encode(cipherBytes);
System.out.println("Encrypted message: " + cipherMsg);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

When it is used without an IV, for certain types of ciphers including AES, it implicitly uses 0 IV. See Cipher class documentation.
The disadvantage of a null IV (or a deterministic IV) is that it is vulnerable to dictionary attacks. The requirement for IV is to prevent the same plain text block producing the same cipher text every time.

Like other users have said, it depends on the JCE provider. Java SE generates a random IV for you if you specify none.
Only Android1 and Javacard API use a blank IV, which is non-conforming to the Java Crypto spec, which states:
If this cipher requires any algorithm parameters that cannot be derived from the given key, the underlying cipher implementation is supposed to generate the required parameters itself (using provider-specific default or random values) if it is being initialized for encryption or key wrapping, and raise an InvalidKeyException if it is being initialized for decryption or key unwrapping. The generated parameters can be retrieved using getParameters or getIV (if the parameter is an IV).
If you do not specify the IV, in Java SE you get a random one, and will need to retrieve it with cipher.getIV() and store it, as it will be needed for decryption.
But better yet, generate a random IV yourself and provide it via IvParameterSpec.
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecureRandom rnd = new SecureRandom();
byte[] iv = new byte[cipher.getBlockSize()];
rnd.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), ivParams);
byte[] ciphertext = cipher.doFinal(input.getBytes());
1 That could be because Android is Java-esque, like the Eminem-esque ad. Just guessing, that's all.

Related

Encryption Decryption issue with Cipher in java

Hi I am facing a decryption problem. The decrypted value is not matching the original one.
Here is my logic for encryption :
public byte[] encrypt(String plainText) {
byte iv[] = new byte[ENCRYPTION_PARAM_SIZE];
SecureRandom secRandom = new SecureRandom();
secRandom.nextBytes(iv);
Cipher cipher = Cipher.getInstance(ENCRYPTION_INSTANCE);
SecretKeySpec key = new SecretKeySpec(fixSecret(encryptionKey), ENCRYPTION_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
return cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
}
And this is my logic for Decryption
public String decrypt(byte[] cipherText) {
byte iv[] = new byte[ENCRYPTION_PARAM_SIZE];
SecureRandom secRandom = new SecureRandom();
secRandom.nextBytes(iv);
Cipher cipher = Cipher.getInstance(ENCRYPTION_INSTANCE);
SecretKeySpec key = new SecretKeySpec(fixSecret(encryptionKey), ENCRYPTION_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return new String(cipher.doFinal(cipherText), StandardCharsets.UTF_8);
}
Encryption options :
ENCRYPTION_ALGORITHM = "DESede";
ENCRYPTION_INSTANCE = "DESede/CBC/PKCS5Padding";
Integer ENCRYPTION_PARAM_SIZE = 8;
This is how I am trying to verify :
public static void main(String[] args){
Long value = 9123456L;
String strval = value.toString();
byte[] encryptedVal = encrypt(strval);
String decryptedVal = decrypt(encryptedVal);
System.out.println("Original value : " +strval);
System.out.println("Encrypted value : " +encryptedVal.toString());
System.out.println("Decrypted value : " +decryptedVal);
System.out.println("Final value : " +Long.parseLong(decryptedVal));
}
What I need to do here to make it work.
Note : The above code is working fine if I use the below logic without SecureRandom :
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new byte[ENCRYPTION_PARAM_SIZE]));
Like others have said, your issue is that the IV you are using for encryption is different than the one you are using for decryption. The IV is not sensitive (from a confidentiality point of view) so it can be transmitted alongside the ciphertext. Some applications prepend the IV to the ciphertext while others use more standard formats such as CMS Encrypted Data. Since you are using Java, you can generate CMS Encrypted Data structures using Bouncy Castle's CMSEncryptedDataGenerator class.
There are two other issues with your code:
Choice of cryptographic primitives
Key management
Cryptographic Primitives
The cryptographic transformation you are using is "DESede/CBC/PKCS5Padding". Unless you have a good reason to use DES/3DES, you should consider switching to AES. Additionally, I would recommend using an AEAD such as AES-GCM or AES-GCM-SIV.
Key Management
All the cryptography in the world doesn't mean much if the keys aren't managed correctly. From your code it looks like you are constructing the SecretKeySpec object out of the key's bytes. If you can, try to use an hardware security module (HSM) or a key management system (KMS) where the key never gets exported from the tamper bounds of the device in plaintext format. AWS KMS, Azure Key Vault, and Google KMS all offer very affordable pricing for this. Of course, there are other options as well.
In the decrypt function you generate a random Initialization Vector (IV), so this won't ever work.
You need to store the IV from the encrypt function and provide it as an input to the decrypt function.
Here's an example:
public byte[] encryptAndDecrypt(String plainText) {
byte iv[] = new byte[ENCRYPTION_PARAM_SIZE];
SecureRandom secRandom = new SecureRandom();
secRandom.nextBytes(iv);
Cipher cipher = Cipher.getInstance(ENCRYPTION_INSTANCE);
SecretKeySpec key = new SecretKeySpec(fixSecret(encryptionKey), ENCRYPTION_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] cipherText=cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return decrypt(cipherText, iv)
}
public String decrypt(byte[] cipherText, byte[] iv) {
Cipher cipher = Cipher.getInstance(ENCRYPTION_INSTANCE);
SecretKeySpec key = new SecretKeySpec(fixSecret(encryptionKey), ENCRYPTION_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return cipher.doFinal(cipherText);
}
Note that by definition the IV should be random but shouldn't be treated as a secret so you can store it as plain data without any protection.
The idea behind the IV is to randomize the cipher text so if you're not using IV, or using a constant IV, and encrypt "X", cipher text is "Y", you could easily reverse the cipher text into plain text, while with random IV the cipher text is different every time.

iOS CryptoKit in Java

I am looking for settings/parameters of CryptoKit which will allow me to share data between iOS App and a Java Application. The flow would be something like below:
- Use CryptoKit to encrypt a text using a fixed key and random initialization vector (IV).
- In the Java application use standard javax libraries to perform the decryption using the same fixed key. The random IV will be transported/shared with the application along with the encrypted text.
Similarly, the reverse is also required, where text is encrypted using JavaX libraries using a fixed key and random IV. The random IV and encrypted text is shared with the iOS app where it should use CryptoKit to decrypt it.
Below is the code for Encrypt and Decrypt in Java
public static byte[] encrypt(byte[] plaintext, byte[] key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES_256/GCM/NoPadding");
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Encryption
byte[] cipherText = cipher.doFinal(plaintext);
return cipherText;
}
public static String decrypt(byte[] cipherText, byte[] key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES_256/GCM/NoPadding");
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
The CryptoKit commands as below:
let mykey = SymmetricKey(data: passhash)
let myiv = try AES.GCM.Nonce()
let mySealedBox = try AES.GCM.seal(source.data(using: .utf8)!, using: mykey, nonce: myiv)
let myNewSealedBox = try AES.GCM.SealedBox(nonce: myiv, ciphertext: mySealedBox.ciphertext, tag: mySealedBox.tag)
let myText = try String(decoding: AES.GCM.open(myNewSealedBox, using: mykey), as: UTF8.self)
Below are the steps to generate an encrypted text in Java:
int GCM_IV_LENGTH = 12;
//Generate Key
MessageDigest md = MessageDigest.getInstance("SHA265");
byte[] key = md.digest("pass".getBytes(StandardCharsets.UTF_8));
// Generate IV
SecureRandom sr = new SecureRandom(pass.getBytes(StandardCharsets.UTF_8));
byte[] IV = new byte[GCM_IV_LENGTH];
sr.nextBytes(IV);
//Encrypt
byte[] cipherText = encrypt("Text to encrypt".getBytes(), key, IV);
//Base64 Encoded CipherText
String cipherTextBase64 = Base64.getEncoder().encodeToString(cipherText);
To Decrypt this in SWIFT CryptoKit, I first need to create a sealed box with this CipherText however, the CryptoKit API to create a sealed box requires the following:
Nonce/IV (Available above)
CipherText (Available above)
Tag (NO IDEA FROM WHERE TO GET THIS????)
AES.GCM.SealedBox(nonce: , ciphertext: , tag: )
The other way, lets first encrypt data in CryptoKit
let mykey = SymmetricKey(data: SHA256.hash(data: "12345".data(using: .utf8)!))
let myiv = AES.GCM.Nonce()
let mySealedBox = try AES.GCM.seal("Text to encrypt".data(using: .utf8)!, using: mykey, nonce: myiv)
let cipherText = mySealedBox.cipherText.base64EncodedString()
let iv = myiv.withUnsafeBytes{
return Data(Array($0)).base64EncodedString()
}
If i pass this IV and CipherText to Java Decrypt function along with key (SHA265 hash of "12345" string), i get a TAG mismatch error.
This is the final set of code in SWIFT:
let pass = “Password”
let data = “Text to encrypt”.data(using: .utf8)!
let key = SymmetricKey(data: SHA256.hash(data: pass.datat(using: .utf8)!))
let iv = AES.GCM.Nonce()
let mySealedBox = try AES.GCM.seal(data, using: key, nonce: iv)
dataToShare = mySealedBox.combined?.base64EncodedData()
Write this data to a file (I am using google APIs to write this data to a file on google drive)
Read this data from the file in java and pass it to the functions as defined in the question using the below code:
byte[] iv = Base64.getDecoder().decode(text.substring(0,16));
cipher[] = Base64.getDecoder().decode(text.substring(16));
byte[] key = md.digest(pass.getBytes(StandardCharsets.UTF_8));
String plainText = decrypt(cipher, key, iv);

How to properly recreate SecretKey from string

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

Java crypto, reset the IV possible for performance improvement?

I create an encryption cipher as follows (in Scala, using bouncy-castle)
def encryptCipher(secret:SecretKeySpec, iv:IvParameterSpec):Cipher = {
val e = Cipher.getInstance("AES/GCM/NoPadding")
e.init(Cipher.ENCRYPT_MODE, secret, iv)
}
You see that the slow operation of generating the key spec is already handled. However calling init itself for each message is too slow.
I'm currently processing 50K messages, and calling the init method adds nearly 4 seconds.
Is there a way to re-initialise with a new IV which is not so time intensive?
There's no standard way to do that in the standard library,
but there's a good workaround if you're using AES:
The purpose of the IV is to eliminate the possibility that same plain texts encrypt into the same cipher texts.
You can just "update" (as in Cipher.update(byte[])) with a random block-size byte array before encrypting (and with the same block when decrypting). This is almost exactly the same as using the same random block as IV.
To see that, run this snippet (that uses the above method to generate exactly the same cipher text - but this is just for compatibility with other platforms, there's no need to calculate a specific IV for it to be secure.
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecureRandom secureRandom = new SecureRandom();
byte[] keyBytes = new byte[16];
secureRandom.nextBytes(keyBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
byte[] plain = new byte[256];
secureRandom.nextBytes(plain);
// first init using random IV (save it for later)
cipher.init(Cipher.ENCRYPT_MODE, key, secureRandom);
byte[] realIv = cipher.getIV();
byte[] expected = cipher.doFinal(plain);
// now init using dummy IV and encrypt with real IV prefix
IvParameterSpec nullIv = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.ENCRYPT_MODE, key, nullIv);
// calculate equivalent iv
Cipher equivalentIvAsFirstBlock = Cipher.getInstance("AES/CBC/NoPadding");
equivalentIvAsFirstBlock.init(Cipher.DECRYPT_MODE, key, nullIv);
byte[] equivalentIv = equivalentIvAsFirstBlock.doFinal(realIv);
cipher.update(equivalentIv);
byte[] result = cipher.doFinal(plain);
System.out.println(Arrays.equals(expected, result));
The decryption part is easier because the result of the block-decryption is XORed with the previous cipher text (see Block cipher mode of operation), you just need to append the real IV to cipher-text, and throw it afterwards:
// Encrypt as before
IvParameterSpec nullIv = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.DECRYPT_MODE, key, nullIv);
cipher.update(realIv);
byte[] result = cipher.doFinal(encrypted);
// result.length == plain.length + 16
// just throw away the first block

Decryption bug using des ede, javax.crypto.badpaddingexception

I've been stuck on a bug in my code, it will not let me decrypt properly!
I am only passing eight bytes of data to dataBytes and I am passing
a 24 byte key to keyBytes.
I am trying to return the decrypted data as an array of bytes.
I keep getting the bad padding exception.
Thanks!
Here is the code snippet:
private static byte[] DESEdeDecrypt(byte[] keyBytes, byte[] dataBytes){
byte[] decryptedData = null;
try{
DESedeKeySpec keySpec = new DESedeKeySpec(keyBytes, 0);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey key = keyFactory.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.DECRYPT_MODE, key);
decryptedData = cipher.doFinal(dataBytes);
}
catch(Exception e){System.out.println(e);}
return decryptedData;
You must use the same padding to decrypt as you did to encrypt. It is better to set it explicitly rather than to rely on defaults. Best also to specify the mode at both ends as well:
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
DESede is slow and obsolescent. You shouldn't use it except for compatibility with old code. For new work it is better to use AES.

Categories