4

I'm in the process of implementing AES encryption in swift. The encryption decryption for java and C# is working properly.

In swift I am getting different results than the actual one. While debugging, I noticed Java uses sign int by default. So I Implemented the same way, with that I am able to verify derivedKey is same in both application(Java and Swift). But while creating the keyData and ivData, it looses the signed data.Not sure thats creating the issue.

I have tried the below code explained in AES Encryption .net to swift

func decrypt(encryptedText: String, keys :String) -> String{ let encryptedData = encryptedText.data(using: .utf16LittleEndian) let derivedKey = generateDerivedKey(keyString: keys) let key = Array(derivedKey[0..<32]) let iv = Array(derivedKey[32..<48]) let keyData = Data(bytes: key, count: key.count) let ivData = Data(bytes: iv, count: iv.count) let decryptedData = testDeCrypt(data: encryptedData!, keyData: keyData, ivData: ivData, operation: kCCDecrypt) return String(bytes: decryptedData, encoding: .unicode)! } func generateDerivedKey(keyString :String) -> [Int8] { let salt: [UInt8] = [0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76] var key = [UInt8](repeating: 0, count: 48) CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), keyString, keyString.utf8.count, salt, salt.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), 1000, &key, 48) let derivedKey : [Int8] = key.map {Int8(bitPattern: $0)} return derivedKey } func testDeCrypt(data: Data, keyData: Data, ivData: Data, operation: Int) -> Data { assert(keyData.count == Int(kCCKeySizeAES128) || keyData.count == Int(kCCKeySizeAES192) || keyData.count == Int(kCCKeySizeAES256)) var decryptedData = Data(count: data.count) var num_bytes_decrypted: size_t = 0 let operation = CCOperation(operation) let algoritm = CCAlgorithm(kCCAlgorithmAES) let options = CCOptions(kCCOptionPKCS7Padding) let decryptedDataCount = decryptedData.count let cryptoStatus = keyData.withUnsafeBytes {keyDataBytes in ivData.withUnsafeBytes {ivDataBytes in data.withUnsafeBytes {dataBytes in decryptedData.withUnsafeMutableBytes {decryptedDataBytes in CCCrypt(operation, algoritm, options, keyDataBytes, keyData.count, ivDataBytes, dataBytes, data.count, decryptedDataBytes, decryptedDataCount, &num_bytes_decrypted) } } } } if cryptoStatus == CCCryptorStatus(kCCSuccess) { decryptedData.count = num_bytes_decrypted return decryptedData } else { return Data() } } 

Java Code

public static String aesDecrypt(String text, String key) { byte[] decValue = null; try { byte[] salt = new byte[] { 0x49, 0x76, 0x61, 0x6E, 0x20, 0x4D, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }; SecretKeyFactory factory = SecretKeyFactory .getInstance("PBKDF2WithHmacSHA1"); PBEKeySpec pbeKeySpec = new PBEKeySpec(key.toCharArray(), salt, 1000, 384); Key secretKey = factory.generateSecret(pbeKeySpec); byte[] keys = new byte[32]; byte[] iv = new byte[16]; System.arraycopy(secretKey.getEncoded(), 0, keys, 0, 32); System.arraycopy(secretKey.getEncoded(), 32, iv, 0, 16); SecretKeySpec secretSpec = new SecretKeySpec(keys, "AES"); AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); try { cipher.init(Cipher.DECRYPT_MODE, secretSpec, ivSpec); } catch (InvalidKeyException e) { } catch (InvalidAlgorithmParameterException e) { } org.apache.commons.codec.binary.Base64 decoder = new org.apache.commons.codec.binary.Base64(); byte[] decodedValue = decoder.decode(text.getBytes()); decValue = cipher.doFinal(decodedValue); } catch (Exception e) { } if (decValue != null) { return new String(decValue, Charset.forName("UTF_16LE")); } else { return null; } } 

Test Data Key: ”ThisIsATestPassword444Encryption" text : "TestStringToEncrypt"

Java Output encoded cipher Text : [97, 47, 77, 79, 118, 111, 79, 70, 47, 87, 90, 67, 81, 98, 51, 74, 83, 88, 97, 68, 84, 105, 72, 71, 67, 121, 122, 86, 81, 116, 106, 104, 117, 78, 108, 118, 49, 48, 65, 77, 69, 53, 114, 43, 120, 104, 89, 120, 50, 98, 80, 66, 50, 77, 87, 80, 103, 110, 117, 118, 118, 97, 78, 106]

encrypted text : a/MOvoOF/WZCQb3JSXaDTiHGCyzVQtjhuNlv10AME5r+xhYx2bPB2MWPgnuvvaNj

Decrypt text.getbytes : [97, 47, 77, 79, 118, 111, 79, 70, 47, 87, 90, 67, 81, 98, 51, 74, 83, 88, 97, 68, 84, 105, 72, 71, 67, 121, 122, 86, 81, 116, 106, 104, 117, 78, 108, 118, 49, 48, 65, 77, 69, 53, 114, 43, 120, 104, 89, 120, 50, 98, 80, 66, 50, 77, 87, 80, 103, 110, 117, 118, 118, 97, 78, 106]

Decoded Decryptted text : [107, -13, 14, -66, -125, -123, -3, 102, 66, 65, -67, -55, 73, 118, -125, 78, 33, -58, 11, 44, -43, 66, -40, -31, -72, -39, 111, -41, 64, 12, 19, -102, -2, -58, 22, 49, -39, -77, -63, -40, -59, -113, -126, 123, -81, -67, -93, 99]

Swift Output: encryptedText : a/MOvoOF/WZCQb3JSXaDTiHGCyzVQtjhuNlv10AME5r+xhYx2bPB2MWPgnuvvaNj

decryptedText : ۽瑒왿᪰߆牷ྐྵ䐫徺ꋴ鐧ݐ斑ﷃ翴㦦જ㤉ꄕ䞴櫘勐鼍ᐏ┓ീ學䥏㿖칵鬥솽ᢼ铡鴷⤃ꗞ䛂䋗쿠蒻⯨䍊䂷篥럟⤫俷違둘๔Ꞵ‵

Swift and java encryption matches.

Any help is much appreciated.

8
  • What is text.getBytes()? Commented Nov 7, 2018 at 22:34
  • You should better include such info into the text of your question. Someone would find your question more easily by updating your code. Commented Nov 9, 2018 at 10:36
  • One more, without key, readers cannot test the code. Create a sample key which can be public, and generate a sample encrypted data with the key. And please show us the sample encrypted text and the sample key. Please do not forget to include them in the text of your question. Commented Nov 10, 2018 at 4:10
  • @OOPer, I have added the test data to the question. Now I am able to match the encrypt data in swift and java. But something wrong with my decryption . Commented Nov 10, 2018 at 9:23
  • Thanks for updating, but I cannot decrypt a/MOvoOF/WZCQb3JSXaDTiHGCyzVQtjhuNlv10AME5r+xhYx2bPB2MWPgnuvvaNj with your Java code with the key ThisIsATestPassword444Encryption. Please show actual output from your Java code encrypted with the key. Commented Nov 10, 2018 at 11:07

2 Answers 2

2

The worst two parts in your Swift code are:

#1

let encryptedData = encryptedText.data(using: .utf16LittleEndian) 

and:

#2

return String(bytes: decryptedData, encoding: .unicode)! 

#1

In your Java code, you are decoding the text as Base-64, but in your Swift code, you are just getting the byte representation of .utf16LittleEndian, which has nothing to do with Base-64.

You may need something like this:

guard let encryptedData = Data(base64Encoded: encryptedText) else { print("Data is not a valid base-64") return nil } 

(Your decrypt(encryptedText:keys:) should return String? rather than String, as decryption may fail.)


#2

In your Java code, you use new String(decValue, Charset.forName("UTF_16LE")) to convert decrypted bytes into String. UTF_16LE stands for UTF-16 Little Endian. The equivalent in String.Encoding of Swift is utf16LittleEndian.

The line should be as follows:

return String(bytes: decryptedData, encoding: .utf16LittleEndian) 

And your generateDerivedKey(keyString:) can be simplified, when you use [UInt8] for its return type. (You should better use UInt8 to represent intermediate byte type in Swift.)

All such things combined, your Swift code should be:

func decrypt(encryptedText: String, keys: String) -> String? { //### `String?` rather than `String` //### Decode `encryptedText` as Base-64 guard let encryptedData = Data(base64Encoded: encryptedText) else { print("Data is not a valid Base-64") return nil } let derivedKey = generateDerivedKey(keyString: keys) //### A little bit shorter, when `derivedKey` is of type `[UInt8]` let keyData = Data(bytes: derivedKey[0..<32]) let ivData = Data(bytes: derivedKey[32..<48]) if let decryptedData = testDeCrypt(data: encryptedData, keyData: keyData, ivData: ivData, operation: kCCDecrypt) { //### Use `utf16LittleEndian` return String(bytes: decryptedData, encoding: .utf16LittleEndian) } else { //### return nil, when `testDeCrypt` fails return nil } } func generateDerivedKey(keyString: String) -> [UInt8] { //### `[UInt8]` let salt: [UInt8] = [0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76] var key = [UInt8](repeating: 0, count: 48) CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), keyString, keyString.utf8.count, salt, salt.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), 1000, &key, 48) //### return the Array of `UInt8` directly return key } func testDeCrypt(data: Data, keyData: Data, ivData: Data, operation: Int) -> Data? { //### make it Optional assert(keyData.count == Int(kCCKeySizeAES128) || keyData.count == Int(kCCKeySizeAES192) || keyData.count == Int(kCCKeySizeAES256)) var decryptedData = Data(count: data.count) var numBytesDecrypted: size_t = 0 let operation = CCOperation(operation) let algoritm = CCAlgorithm(kCCAlgorithmAES) let options = CCOptions(kCCOptionPKCS7Padding) let decryptedDataCount = decryptedData.count let cryptoStatus = keyData.withUnsafeBytes {keyDataBytes in ivData.withUnsafeBytes {ivDataBytes in data.withUnsafeBytes {dataBytes in decryptedData.withUnsafeMutableBytes {decryptedDataBytes in CCCrypt(operation, algoritm, options, keyDataBytes, keyData.count, ivDataBytes, dataBytes, data.count, decryptedDataBytes, decryptedDataCount, &numBytesDecrypted) } } } } if cryptoStatus == CCCryptorStatus(kCCSuccess) { decryptedData.count = numBytesDecrypted return decryptedData } else { return nil //### returning `nil` instead of `Data()` } } 

With the new Swift code above, I could have generate the same result as your Java code:

let test = "a/MOvoOF/WZCQb3JSXaDTiHGCyzVQtjhuNlv10AME5r+xhYx2bPB2MWPgnuvvaNj" let keys = "ThisIsATestPassword444Encryption" if let result = decrypt(encryptedText: test, keys: keys) { print(result) //->TestStringToEncrypt } else { print("*Cannot decrypt*") } 

(I needed to update my old Java environment to compare intermediate results between Java and Swift, but that's another story...)

Sign up to request clarification or add additional context in comments.

2 Comments

Work like a charm. Thanks a lot OOper for the detailed explanation and quick resolution. I really appreciate your help!!!
@SJMC, I could have written the answer with your responses to my comments (you know some of them were unclear and contained misunderstanding). You are the co-writer of this answer.
0

i was facing the same problem and it's fixed by checking the first item in the buffers uint array if it is "0x00", if not add "0x00" at index 0 here is example of receiving encrypted data and send it again after decrypt

 let rsaKeyValue = xmlRep["RSAKeyValue"] let modulus = rsaKeyValue["Modulus"].element?.text let exponent = rsaKeyValue["Exponent"].element?.text var modBuffer: [UInt8] = [UInt8](Data(base64Encoded: modulus!)!) let expBuffer: [UInt8] = [UInt8](Data(base64Encoded: exponent!)!) if let prefix = modBuffer.first, prefix != 0x00 { modBuffer.insert(0x00, at: 0) } let modulusEncoded: [UInt8] = modBuffer.encodeAsInteger() let exponentEncoded: [UInt8] = expBuffer.encodeAsInteger() let sequenceEncoded: [UInt8] = (modulusEncoded + exponentEncoded).encodeAsSequence() let keyData = Data(bytes: sequenceEncoded) let keySize = (modBuffer.count * 8) let attributes: [String: Any] = [ kSecAttrKeyType as String: kSecAttrKeyTypeRSA, kSecAttrKeyClass as String: kSecAttrKeyClassPublic, kSecAttrKeySizeInBits as String: keySize, kSecAttrIsPermanent as String: false ] var err : Unmanaged<CFError>? let publicKey = SecKeyCreateWithData(keyData as CFData, attributes as CFDictionary, &err) guard let tokenData = Authentication.getUserToken()?.data(using: .utf8) else { return } let chunks = tokenData.toUInt8Array().chunked(into: 200) var encryptedChunks = [[UInt8]]() for chunk in chunks { var encryptionError: Unmanaged<CFError>? let cipher = SecKeyCreateEncryptedData(publicKey!, .rsaEncryptionPKCS1, Data(bytes: chunk) as CFData, &encryptionError) encryptedChunks.append([UInt8](cipher! as Data)) } var str = "[" for chunk in encryptedChunks { for byte in chunk { str.append("\(byte),") } str.remove(at: String.Index(encodedOffset: str.count - 1)) str.append(";") } str.append("]") let finalStr = str.replacingOccurrences(of: ";]", with: "]") 

here is the extensions that for encrypting in swift

internal extension Array where Element == UInt8 { func encodeAsInteger() -> [UInt8] { var tlvTriplet: [UInt8] = [] tlvTriplet.append(0x02) tlvTriplet.append(contentsOf: lengthField(of: self)) tlvTriplet.append(contentsOf: self) return tlvTriplet } func encodeAsSequence() -> [UInt8] { var tlvTriplet: [UInt8] = [] tlvTriplet.append(0x30) tlvTriplet.append(contentsOf: lengthField(of: self)) tlvTriplet.append(contentsOf: self) return tlvTriplet } func chunked(into size: Int) -> [[Element]] { return stride(from: 0, to: count, by: size).map { Array(self[$0 ..< Swift.min($0 + size, count)]) } } 

}

4 Comments

Thanks for your suggestion. Just wondering I can use your solution in my code since both are different algorithms. Please correct me if I am wrong?
swift has a different way for encoding i really don't know why and made a lot of search but i can't reach any results it's not about encryption or decryption it's about swift how to read the byte array it should start with "0x00" and if not you have to insert in index 0
for the algorithms it will affect the data encrypted and decrypted make sure the data you sent and receive is in the right way like android
I tried to mimic the same code in java but no luck :(

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.