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);
Related
I am consuming a SOAP API that sends a response that is encrypted with AES. I too have the secret key from the API provider. However I am a bit confused on how to decrypt the response.
All guides that describe how I can decrypt the message tell me I need SecretKeySpec when using javax.crypto.Cipher. However I have no idea what is actually expected there?
Here is an example what I am trying to do:
final String encryptedResponse = "F9nwhTquiEcRY3wfwCGVH1yvZ1fl28VnBXQ3vo6fyCzdV0MnOmeeHg8ea/7c/9ZT0AeEywnR06r5eUoeq4Swf/bFIixc9JJEYB7/fJ0h6I7blQbiOuks7QOUBoSMNaAum1NYTgTm0MHbM3GYLHDPlb8PkBFTL0XxZalKqcqRuhr3BQxPfITeSXjqSvPvy5Wt1Jq";
final String secretKey = "ijsdfgDJJff42h3412";
BASE64Decoder myDecoder = new BASE64Decoder();
byte[] crypted = myDecoder.decodeBuffer(secretKey);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] cipherData = cipher.doFinal(crypted);
String decryptedResponse = new String(cipherData);
here I receive the following error
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
The key length should be 16,get the true key first;
mayby the key length should be 16,get the true key first;
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.
I have to encrypt some parameters for a web portal that recibe encrypted parameters via POST with AES/CBC/PKCS5Padding, the encryption method was provided by the web portal creator(another team), but when I use their method the result is not accepted by ther web portal. I assume that the parameters are not well provided to the method. Am I missing somethig?
I requested some fixed IV value but the another teams told me that they used random IV's.
public static byte[] Encrypt(byte[] plaintext, SecretKey key, byte[] IV) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(IV);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] cipherText = cipher.doFinal(plaintext);
return cipherText;
}
//this is the way I build the parameters
String input_text = txtPlainText.getText();
byte[] plaintext = input_text.getBytes("UTF-8");
String encodedKey = "6qp4Y?kLmaY8+Fsd";
byte[] decodedKey = encodedKey.getBytes();
SecretKey key = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
byte[] IV = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(IV);
byte[] cipherText = Encrypt(plaintext,key,IV);
String Encrypted_text = java.util.Base64.getEncoder().encodeToString(cipherText);
txtEncryptedText.setText(Encrypted_text );
Every timeI use this encription method i get a diferent value, for the plain text 'System Administrator' I get vlaues like 'U+za3baTIvMpiFgCPCfqoKe+ljDutraUrUTJhroqHPg=', 'lHT0Kq6GlyzYx2TEyQmqdPp4KfVi5sdlC0udtSlZ5uk='
I am creating a app where i need a decryption key to decrypt videos and other data . i need to play videos offline so i need to store the key in the app . So if i use shared pref to store my key or directly in the string it can be easily hacked . and my data will not be secured any more . So where should i keep my key so that no one can find my key on decompiling the app or rooting phone to get to the key.
I am thinking about where should i store data
sqlite
shared prefrence
text file
string file
static variable
If the decryption key is at any point accessible to the application, it's accessible to any potential evildoer. This is a fact.
If your requirements are:
Videos encrypted, i.e. only playable through your app
Playable offline
Secured so you can't decrypt or view the videos through other means
Then what you have are impossible requirements.
There is a way to secure your encryption key in NDK.
Step 1
private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception
{
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception
{
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
Step 2
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] b = baos.toByteArray();
byte[] keyStart = "encryption key".getBytes();
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(keyStart);
kgen.init(128, sr);
SecretKey skey = kgen.generateKey();
byte[] key = skey.getEncoded();
// encrypt
byte[] encryptedData = encrypt(key,b);
// decrypt
byte[] decryptedData = decrypt(key,encryptedData);
Step 3
static {
System.loadLibrary("library-name");
}
public native String getSecretKey();
Step 4
And save in a file using NDK the following function:
Java_com_example_exampleApp_ExampleClass_getSecretKey(
JNIEnv* env, jobject thiz )
{
return (*env)->NewStringUTF(env, "mySecretKey".");
}
Step 4
Now we can easily retrieve our key and use it to encrypt our data.
byte[] keyStart = getSecretKey().getBytes();
Reference : How to store the Credentials securely in Android
Create your own directory under the main Android default directory
In your directory create multiple directories and hide the file in one of it.
Other than the that there is no really secure way
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.