iOS CryptoKit in Java
Asked Answered
B

1

8

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.

Beware answered 3/1, 2020 at 10:56 Comment(15)
Both the codes (Java and SWIFT) have key, IV and source text elements. Key is a SHA256 hash of a string (I call it the password). IV is a random set of bytes. However, both use a different approach to use the key and IV elements to encrypt. In Java, there is the SecretKeySpec, GCMParameterSpec which take bytes as input (key and IV). Similarly, in SWIFT CryptoAPI uses a SymmetricKey and Nonce as key and IV elements. However, the outputs of both encryption mechanisms is different for a same set of key and IV and hence data encrypted in Java or SWIFT cannot be decrypted on the other side.Beware
Unfortunately it will be hard to see what happens without the full input / output to the GCM fucntions, and the input / output seems to be missing. Currently I don't see you set a nonce in the CryptoKit commands, which probably means it is random. Now if you'd add the input / output in hex it would look a lot more like you tried.Cyrille
Code updated with the details.Beware
The authentication tag is considered part of the ciphertext in Java. Could you print out the base 64 output of mySelaedBox.Data?Cyrille
There is no Data property in mySealedBox. It has cipherText, nonce, tag and combined which is (nonce || cipherText || tag). Based on this combined property's description, I passed the IV Base64 string and cipherText Base64 string concatenated to CryptoAPI to create a new sealed box and it was success. I was able to create a sealedbox in CryptoAPI using the fact you stated that Java CipherText has tag included in it and the IV which i had. I was able to open this box using the predefined key. Thank you so much for this. The only thing remains now is how do i take a sealed box & decrypt in JavaBeware
Uh, sorry, now I'm lost. There is a means of getting the sealed box as encrypted data, right? Or is it just a collection of 3 things you have to encode yourself? Glad to have been of help, of course :)Cyrille
Thank you @Maarten-reinstateMonica for all your time and quick pointers. Looks like i was not taking the proper outputs from SWIFT and passing them to Java. The combined property of the sealedbox when extracted as Base64 string and passed to Java as encrypted text is now getting properly decrypted with the correct key!!Beware
Ah, right. Could you please self-answer your question and show how to retrieve the combined property as well? I think I looked up the wrong property. Happy to upvote such an answer!Cyrille
This is the final set of code lines in SWIFT:Beware
let pass = "Password"; let data = "Text To Encrypt".data(using: .utf8)!; let key = SymmetricKey(data: SHA256.hash(data: pass.data(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 a google drive API to save this to google drive).Beware
Read the file in Java and decrypt using the below code: byte[] IV = Base64.getDecoder().decode(text.substring(0,16)); byte[] cipherTextIV = Base64.getDecoder().decode(text.substring(16)); byte[] key = md.digest(pass.getBytes(StandardCharsets.UTF_8)); String decryptedTextIV = decrypt(cipherTextIV, key, IV);Beware
@Maarten-reinstateMonica One last question - how do i self answer?Beware
Try again, you should have enough rep now!Cyrille
When i browse the site from the browser it does not show the "Answer" button anywhere. However, when i logged in from my mobile app of StackOverFlow, it allowed me to post an answer. It was a little time consuming to type the answer on the mobile but finally i did it :-). Thank you @Maarten-reinstateMonicaBeware
A lot of these things are JS generated to relieve the busy servers at SO, a refresh may have helped there :)Cyrille
B
5

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);
Beware answered 9/1, 2020 at 7:13 Comment(5)
Ah, so it is just a concatenation of nonce, ciphertext and tag that we are dealing with. Slightly boring but certainly good to know! Thanks for taking the time to post an answer and welcome to SO.Cyrille
Don't forget to take a look at PBKDF2 and other password based key derivation mechanisms, if you want to keep your passwords secure. A single iteration of SHA-256 doesn't add any security, and generally passwords don't offer that much security by themselves.Cyrille
Sure. Thank you for all your time and support.Beware
@AmitBajaj Was that worked for you in AES256 GCM in CryptoKit / (Bouncy Castle for Java). I'm also following android Java code and JS code for my end, unable to get it what i wanted.Bonkers
And how do you decrypting text in iOS using sealBox?Frenchy

© 2022 - 2024 — McMap. All rights reserved.