tldr:
Is there a way to create CMS Enveloped Data when i have already encrypted content with AES and secret key which is already encrypted with public key?
Long version:
I have an application which encrypts and decrypts data with AES (CBC and GCM mode). Symetric key is encrypted/decrypted with RSA key pairs. When user requests for data we decrypt it in backend (Java) and send it to the browser
Usually we have public key and private key but there is requirement that in some cases we dont have private key and the decryption should take place in browser (user provides PFX with privatkey). The solution for this is PKI.js which can decrypt data using PFX and CMS Enveloped Data.
The problem is that we already encrypted the data and dont have access to plain data which we can use to build CMS Enveloped Data.
Edit:
#dave_thompson_085 thank you for reply! I have an follow-up question. I dont hold certificates in system so only thing i have is public key. Is there a way to adjust your code to this requirement?
Before your answer i was encrypting data for second time just for CMS Enveloped Object. In this code i used only public key for generating reciepents. Is there a way to adjust your code to generate reciepent with public key only?
My previous code:
SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
OAEPParameterSpec oaepSpec = new OAEPParameterSpec(digest, "MGF1", new MGF1ParameterSpec(mgfDigest), PSource.PSpecified.DEFAULT);
AlgorithmIdentifier oaepAlgId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, oaepSpec);
RecipientInfoGenerator recipientInfoGenerator = new JceKeyTransRecipientInfoGenerator(subjectKeyIdentifier.getEncoded(),oaepAlgId,publicKey).setProvider("BC");
envelopedDataGenerator.addRecipientInfoGenerator(recipientInfoGenerator);
And what about Hashing Algorithm? Do i need one or it is only additional protection to ensure that CMS Enveloped Object didnt changed?
FWIW you can use BouncyCastle only to do the DER formatting (plus set the versions, a minor convenience), plus PEM if you want that (also a minor convenience), after you do all the rest of the work yourself. Example:
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.security.spec.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
// sample data and encryption, replace as needed
byte[] input = "testdata".getBytes();
X509Certificate cert = null;
try(InputStream is = new FileInputStream(args[0])){
cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
}
byte[] skey = new byte[16], snonce = new byte[16];
SecureRandom rand = new SecureRandom(); rand.nextBytes(skey); rand.nextBytes(snonce);
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(skey,"AES"), new IvParameterSpec(snonce));
byte[] ctx1 = aes.doFinal(input);
Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsa.init(Cipher.ENCRYPT_MODE, cert.getPublicKey());
byte[] ctx2 = rsa.doFinal(skey);
// now build the message
byte[] issuer = cert.getIssuerX500Principal().getEncoded();
ASN1Set recips = new DERSet( new KeyTransRecipientInfo(
new RecipientIdentifier(
new IssuerAndSerialNumber(X500Name.getInstance(issuer),cert.getSerialNumber() )),
new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption),
new DEROctetString(ctx2) ));
EnvelopedData env = new EnvelopedData (null/*no originfo*/, recips,
new EncryptedContentInfo(CMSObjectIdentifiers.data,
new AlgorithmIdentifier(NISTObjectIdentifiers.id_aes128_CBC,
new DEROctetString(snonce) ),
new DEROctetString(ctx1) ),
new DERSet() /*no attributes*/ );
ContentInfo msg = new ContentInfo(CMSObjectIdentifiers.envelopedData, env);
try(OutputStream os = new FileOutputStream(args[1]) ){
os.write(msg.getEncoded());
}
// or use PemWriter (and a PemObject) if you want PEM
Ok, i figured out how to adjust code to fit my needs so i can share.
Generating recipients:
public RecipientInfo generateRecipientInfo(){
try{
SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
RecipientIdentifier recipId = new RecipientIdentifier(new DEROctetString(subjectKeyIdentifier));
return new RecipientInfo(new KeyTransRecipientInfo(recipId, getOAEPAlgorithmIdentifier(),
new DEROctetString(encryptedOaepKey)));
}catch(Exception e){
return null;
}
}
AlgorithIdentifier for RSA-OAEP:
private AlgorithmIdentifier getOAEPAlgorithmIdentifier(){
try{
String digest = "SHA-1";
String mgfDigest = "SHA-1";
JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
OAEPParameterSpec oaepSpec = new OAEPParameterSpec(digest, "MGF1", new MGF1ParameterSpec(mgfDigest), PSource.PSpecified.DEFAULT);
AlgorithmIdentifier oaepAlgId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, oaepSpec);
return oaepAlgId;
}catch (Exception e){
return null;
}
}
AlgorithmIdentifier for AES CBC (or GCM as well after changing CMSAlgorithm.*)
public AlgorithmIdentifier getAlgorithmIdentifier() {
try{
AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("AES","BC");
algorithmParameters.init(new IvParameterSpec(new byte[keyLength/8]));
return new AlgorithmIdentifier(CMSAlgorithm.AES128_CBC, AlgorithmParametersUtils.extractParameters(algorithmParameters));
}catch (Exception e){
return null;
}
}
And finally generating CMS Enveloped Object:
public CMSEnvelopedData generate() throws CMSException {
ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
AlgorithmIdentifier encAlgId = aesCryptography.getAlgorithmIdentifier();;
ASN1OctetString encContent = new BEROctetString(encryptedContent);;
recipientInfos.add(generateRecipientInfo());
EncryptedContentInfo eci = new EncryptedContentInfo(
CMSObjectIdentifiers.data,
encAlgId,
encContent);
ContentInfo contentInfo = new ContentInfo(
CMSObjectIdentifiers.envelopedData,
new EnvelopedData(null, new DERSet(recipientInfos), eci, (ASN1Set)null));
return new CMSEnvelopedData(contentInfo);
}
Related
I am trying to write a Java code decrypt a file encrypted with AES256 using BouncyCastle compatible with OpenSSL decryption.
s_key is the file provided which contains the key that will be used to encrypt and decrypt
Steps to be done: 1 - Read the key file 2 - Use the key provided to decrypt file inputfilename
Below I have use so far but I am getting error:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherOutputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;
import javax.crypto.NoSuchPaddingException;
public class test5_encrypt {
public static void main(String[] args) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException {
File file = new File("/home/roxane/key");
String passwordStr = FileUtils.readFileToString(file, "UTF-8");
String outputPath = "/home/roxane/test1";
String inputPath = "/home/roxane/test";
SecureRandom random = new SecureRandom();
byte salt[] = new byte[8];
random.nextBytes(salt);
// Derive 32 bytes key (AES_256) and 16 bytes IV
byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); // SHA256 as of v1.1.0 (if in OpenSSL the default digest is applied)
pbeGenerator.init(password, salt);
ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128);// keySize, ivSize in bits
System.out.println(parameters.getIV());
// Decrypt with AES-256
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
// Encrypt chunkwise (for large data)
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
cipher.init(false, parameters);
try (FileInputStream fis = new FileInputStream(inputPath);
CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {
int bytesRead = -1;
byte[] buffer = new byte[64 * 1024 * 1024];
while ((bytesRead = fis.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Error:
Exception in thread "main" java.lang.RuntimeException: org.bouncycastle.crypto.io.InvalidCipherTextIOException: Error finalising cipher data
at decrypt.test5_encrypt.main(test5_encrypt.java:61)
Caused by: org.bouncycastle.crypto.io.InvalidCipherTextIOException: Error finalising cipher data
at org.bouncycastle.crypto.io.CipherOutputStream.close(Unknown Source)
at decrypt.test5_encrypt.main(test5_encrypt.java:59)
Caused by: org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
When using a password, OpenSSL stores the ciphertext in a specific format, namely the ASCII encoding of Salted__, followed by the 8 bytes salt, then the actual ciphertext.
During decryption, the salt must not be randomly generated (as it is done in the posted code), otherwise the wrong key and IV will be derived. Instead, the salt must be determined from the metadata of the ciphertext. Also the use of the stream classes must be fixed:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;
...
String inputPath = "..."; // path to enc file
String outputPath = "..."; // path to dec file
String passwordStr = "...";
// Decrypt with AES-256, CBC using streams
try (FileInputStream fis = new FileInputStream(inputPath)){
// Determine salt from OpenSSL format
fis.readNBytes(8); // Skip prefix Salted__
byte[] salt = fis.readNBytes(8); // Read salt
// Derive 32 bytes key (AES_256) and 16 bytes IV via EVP_BytesToKey()
byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); // SHA256 as of v1.1.0 (if in OpenSSL the default digest is applied)
pbeGenerator.init(password, salt);
ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128); // keySize, ivSize in bits
// Decrypt chunkwise (for large data)
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
cipher.init(false, parameters);
try (CipherInputStream cis = new CipherInputStream(fis, cipher);
FileOutputStream fos = new FileOutputStream(outputPath)) {
int bytesRead = -1;
byte[] buffer = new byte[64 * 1024 * 1024]; // chunksize, e.g. 64 MiB
while ((bytesRead = cis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}
This is functionally equivalent to the OpenSSL statement:
openssl enc -d -aes256 -k <passpharse> -in <enc file> -out <dec file>
Note that OpenSSL applied MD5 as digest by default in earlier versions and SHA256 as of v.1.1.0. Code and OpenSSL statement must use the same digest for compatibility.
In the code the digest is explicitly specified, in the OpenSSL statement it can be explicitly set via the -md option so that matching is possible on both sides.
Keep in mind that EVP_BytesToKey(), which is used by default by OpenSSL for key derivation, is deemed insecure nowadays.
Addition regarding Java 8: For Java 8, e.g. the following implementation can be applied for the determination of the salt:
int i = 0;
byte[] firstBlock = new byte[16];
while (i < firstBlock.length) {
i += fis.read(firstBlock, i, firstBlock.length - i);
}
byte[] salt = Arrays.copyOfRange(firstBlock, 8, 16);
The loop is necessary because read(byte[],int,int), unlike readNBytes(int), does not guarantee that the buffer is completely filled (considering here the non-EOF and non-error case).
If you omit the loop (which means using the equivalent read(byte[])), the code will still run for those JVMs which also fill the buffer completely. Since this applies to the most common JVMs for small buffer sizes the code will mostly work, see the comment by dave_thompson_085. However, this is not guaranteed for any JVM and is therefore less robust (though probably not by much).
I have file creation process in which we create a signature using RSAPKCS1Signature class for signing & validating in c#.
Now we are moving to Java, we are using the same algorithm used in c# but it's not validating the same file in java which is created in c#.
I have attached the sample code using. Please suggest need full.
Thanks..!
C#:
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
byte[] Hash = {59,4,248,102,77,97,142,201,210,12,224,93,25,41,100,197,213,134,130,135};
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(RSA);
RSAFormatter.SetHashAlgorithm("SHA1");
byte[] SignedHash = RSAFormatter.CreateSignature(Hash);
RSAPKCS1SignatureDeformatter RSADeformatter = new
RSAPKCS1SignatureDeformatter(RSA);
RSADeformatter.SetHashAlgorithm("SHA1");
Console.WriteLine(RSADeformatter.VerifySignature(Hash, SignedHash));
Java:
KeyStore ex = KeyStore.getInstance("JKS");
ex.load("c://sample.jks", "password");
PrivateKey privateKey = (PrivateKey) ex.getKey("1", "password");
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initSign(privateKey);
String line = null;
while ((line = reader.readLine()) != null) {
signature.update(line.getBytes());
}
signature.sign();
In reference to this discussion: Decode South African (ZA) Drivers License
Please assist I seem to be getting an error trying to create PublicKey instance in Java on android. I have pasted the error below:
java.lang.RuntimeException: error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
Here is the code snippet:
Cipher asymmetricCipher = null;
asymmetricCipher = Cipher.getInstance("RSA");
X509EncodedKeySpec publicKeySpec128 = new X509EncodedKeySpec(key128block);
X509EncodedKeySpec publicKeySpec74 = new X509EncodedKeySpec(key74block);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Key key = keyFactory.generatePublic(publicKeySpec128);
asymmetricCipher.init(Cipher.DECRYPT_MODE, key);
byte[] plainText = asymmetricCipher.doFinal(topBlocksData[0]);
The encoded public keys you're trying to read are not of the format expected by X509EncodedKeySpec. Instead they are of an even simpler form which, unfortunately, is not supported by Java. However, you can use the Bouncycastle or Spongycastle library to code up a small java routine to parse the values. The code below is copied from my answer to this question.
import java.io.IOException;
import java.math.BigInteger;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DLSequence;
public class RsaAsn1Example {
// ...
public static BigInteger [] parseASN1RsaPublicKey(byte [] encoded) throws IOException {
ASN1InputStream asn1_is = new ASN1InputStream(encoded);
DLSequence dlSeq = (DLSequence) asn1_is.readObject();
ASN1Integer asn1_n = (ASN1Integer) dlSeq.getObjectAt(0);
ASN1Integer asn1_e = (ASN1Integer) dlSeq.getObjectAt(1);
asn1_is.close();
return new BigInteger[]{ asn1_n.getPositiveValue(), asn1_e.getPositiveValue()};
}
// ....
}
The values returned by parseASN1RsaPublicKey can be supplied to an RsaPublicKeySpec constructor to create the public key.
Also examine divanov's answer to the same questions for an alternative.
I am using OpenSSL RSA1_5 for decrypting the CEK (Content Encryption Key).
My aim is to decrypt the JWK (JSON Web Key) by which I will be getting CEK, so by using CEK I can decrypt my ciphertext which is actually the encrypted data.
After using Base64Decode, JWE Header is
{"alg":"RSA1_5","enc":"A128CBC-HS256","typ":"JOSE"}
where "alg" is the algorithm used to decrypt the CEK. Please help me decrypting the CEK first, after this I need to decrypt the Cipher.
My Java class is:
package com.decryption;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
public class RSADecrypt
{
public RSADecrypt(String inFileName, String outFileName) {
try {
System.out.println("Inside TRY");
/* Get the encrypted message from file. */
FileInputStream cipherfile = new FileInputStream(inFileName);
byte[] ciphertext = new byte[cipherfile.available()];
cipherfile.read(ciphertext);
cipherfile.close();
System.out.println("Inside 1");
/* Get the private key from file. */
//PrivateKey privatekey = readPrivateKey("D://sso//mmdevnopass.key");
PrivateKey privatekey = readPrivateKey("D://sso//mmdevJWE.key");
System.out.println("Inside 2");
/* Create cipher for decryption. */
Cipher decrypt_cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
decrypt_cipher.init(Cipher.DECRYPT_MODE, privatekey);
System.out.println("Inside 3");
/* Reconstruct the plaintext message. */
byte[] plaintext = decrypt_cipher.doFinal(ciphertext);
FileOutputStream plainfile = new FileOutputStream(outFileName);
plainfile.write(plaintext);
plainfile.close();
} catch (Exception e) {
System.out.println("catch1");
e.printStackTrace();
}
}
public static PrivateKey readPrivateKey(String filename) throws Exception {
System.out.println("readPrivateKey()");
FileInputStream file = new FileInputStream(filename);
byte[] bytes = new byte[file.available()];
file.read(bytes);
file.close();
System.out.println("readPrivateKey() 1");
PKCS8EncodedKeySpec privspec = new PKCS8EncodedKeySpec(bytes);
// X509EncodedKeySpec privspec= new X509EncodedKeySpec(bytes);
//RSAPrivateKeySpec privspec = new RSAPrivateKeySpec(modulus, privateExponent)
System.out.println("readPrivateKey() 2");
KeyFactory factory = KeyFactory.getInstance("RSA");
System.out.println("readPrivateKey() 3");
PrivateKey privkey = factory.generatePrivate(privspec);
System.out.println("readPrivateKey() 4");
return privkey;
}
public static void main(String[] arg) {
/*if (arg.length != 2) {
System.err.println("Usage: java RSADecrypt <src file> <dest file>");
} else {*/
System.out.println("Welcome");
String inFileName="D://sso//myJEK.txt";
String outFileName="D://sso//out.txt";
new RSADecrypt(inFileName,outFileName);
// }
}
}
I am getting output as
Welcome
Inside TRY
Inside 1
readPrivateKey()
readPrivateKey() 1
readPrivateKey() 2
readPrivateKey() 3
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:175)
at java.security.KeyFactory.generatePrivate(KeyFactory.java:322)
at com.decryption.RSADecrypt.readPrivateKey(RSADecrypt.java:85)
at com.decryption.RSADecrypt.<init>(RSADecrypt.java:46)
at com.decryption.RSADecrypt.main(RSADecrypt.java:102)
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:324)
at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:350)
at sun.security.rsa.RSAPrivateCrtKeyImpl.<init>(RSAPrivateCrtKeyImpl.java:74)
at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(RSAPrivateCrtKeyImpl.java:58)
at sun.security.rsa.RSAKeyFactory.generatePrivate(RSAKeyFactory.java:274)
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:171)
... 4 more
catch1
Please help me to decrypt the CEK and solve this exception.
Your problem is caused by your private key file. Firstly, your method of reading the bytes is error-prone:
FileInputStream file = new FileInputStream(filename);
byte[] bytes = new byte[file.available()];
file.read(bytes);
file.close();
This may not read the entire file. The available() method does not indicate how many bytes are in the file. Please search for a better way of reading this file (perhaps from this question: File to byte[] in Java).
Once this is fixed, you may still have errors unless your file is a DER-encoded PKCS #8 object. A common mistake is to try and use a PEM-encoded file (e.g. containing ----- BEGIN PRIVATE KEY ---- headers and base64-encoded data).
Given the following example:
String f="A000000000000000";
FileInputStream fis = new FileInputStream("C:\\Users\\original.txt");
byte[] bytes = DatatypeConverter.parseHexBinary(f);
SecretKey key = new SecretKeySpec(bytes, 0, bytes.length, "DES");
String strDataToEncrypt = new String();
String strCipherText = new String();
String strDecryptedText = new String();
try{
Cipher desCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
desCipher.init(Cipher.ENCRYPT_MODE,key);
//read from file and transform to String
try{
builder = new StringBuilder();
int ch;
while((ch = fis.read()) != -1){
builder.append((char)ch);
}
}catch (IOException e){
}
byte[] byteDataToEncrypt = builder.toString().getBytes();
byte[] byteCipherText = desCipher.doFinal(byteDataToEncrypt);
strCipherText = new BASE64Encoder().encode(byteCipherText);
System.out.println(strCipherText);
the encrypted data is different everytime I compile with the same key value i , i tried different codes and the encrypted data was always the same , what's wrong here ?
The documentation for javax.crypto.Cipher.init says, in part:
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)
DES CBC (Cipher Block Chaining) mode requires an Initialization Vector (IV). If you do not provide one (and you should not, because it opens you up to dictionary attacks), a random one will be generated.
But if you want the encrypted data to be the same every time, you need to specify the IV using an IvParameterSpec:
byte[] iv = DatatypeConverter.parseHexBinary("0000000000000000");
IvParameterSpec ips = new IvParameterSpec(iv);
desCipher.init(Cipher.ENCRYPT_MODE, key, ips);
If you do let it generate a random IV, you can retrieve the generated IV with desCipher.getIV().