Aes Encryption in Swift

AES encryption in swift

Be sure to use the same parameters which seem to be AES with CBC mode with iv, PKCS5Padding (actually PKCS#7) padding and a 16-byte (128-bit) key.

PKCS#5 padding and PKCS#7 padding are essentially the same, sometimes for historic reasons PKCS#5 padding is specified for use with AES but the actual padding is PKCS#7.

Make sure the encodings of the key, iv and encrypted data all match. Hex dump them on both platforms to ensure they are identical. Encryption functions are not difficult to use, if all the input parameters are correct the output will be correct.

To make this more secure the iv should be random bytes and prepended to the encrypted data for use during decryption.

The Cross platform AES encryption uses a 256-bit key so will not work as-is.

Example:

Swift 2

// operation: kCCEncrypt or kCCDecrypt
func testCrypt(data data:[UInt8], keyData:[UInt8], ivData:[UInt8], operation:Int) -> [UInt8]? {
let cryptLength = size_t(data.count+kCCBlockSizeAES128)
var cryptData = [UInt8](count:cryptLength, repeatedValue:0)

let keyLength = size_t(kCCKeySizeAES128)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
let options: CCOptions = UInt32(kCCOptionPKCS7Padding)

var numBytesEncrypted :size_t = 0

let cryptStatus = CCCrypt(CCOperation(operation),
algoritm,
options,
keyData, keyLength,
ivData,
data, data.count,
&cryptData, cryptLength,
&numBytesEncrypted)

if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.removeRange(numBytesEncrypted..
} else {
print("Error: \(cryptStatus)")
}

return cryptData;
}

let message = "Don´t try to read this text. Top Secret Stuff"
let messageData = Array(message.utf8)
let keyData = Array("12345678901234567890123456789012".utf8)
let ivData = Array("abcdefghijklmnop".utf8)
let encryptedData = testCrypt(data:messageData, keyData:keyData, ivData:ivData, operation:kCCEncrypt)!
let decryptedData = testCrypt(data:encryptedData, keyData:keyData, ivData:ivData, operation:kCCDecrypt)!
var decrypted = String(bytes:decryptedData, encoding:NSUTF8StringEncoding)!

print("message: \(message)");
print("messageData: \(NSData(bytes:messageData, length:messageData.count))");
print("keyData: \(NSData(bytes:keyData, length:keyData.count))");
print("ivData: \(NSData(bytes:ivData, length:ivData.count))");
print("encryptedData: \(NSData(bytes:encryptedData, length:encryptedData.count))");
print("decryptedData: \(NSData(bytes:decryptedData, length:decryptedData.count))");
print("decrypted: \(String(bytes:decryptedData,encoding:NSUTF8StringEncoding)!)");

Output:


message: Don´t try to read this text. Top Secret Stuff
messageData: 446f6ec2 b4742074 72792074 6f207265 61642074 68697320 74657874 2e20546f 70205365 63726574 20537475 6666
keyData: 31323334 35363738 39303132 33343536 37383930 31323334 35363738 39303132
ivData: 61626364 65666768 696a6b6c 6d6e6f70
encryptedData: b1b6dc17 62eaf3f8 baa1cb87 21ddc35c dee803ed fb320020 85794848 21206943 a85feb5b c8ee58fc d6fb664b 96b81114
decryptedData: 446f6ec2 b4742074 72792074 6f207265 61642074 68697320 74657874 2e20546f 70205365 63726574 20537475 6666
decrypted: Don´t try to read this text. Top Secret Stuff

Swift 3 with [UInt8] type

func testCrypt(data:[UInt8], keyData:[UInt8], ivData:[UInt8], operation:Int) -> [UInt8]? {
let cryptLength = size_t(data.count+kCCBlockSizeAES128)
var cryptData = [UInt8](repeating:0, count:cryptLength)

let keyLength = size_t(kCCKeySizeAES128)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
let options: CCOptions = UInt32(kCCOptionPKCS7Padding)

var numBytesEncrypted :size_t = 0

let cryptStatus = CCCrypt(CCOperation(operation),
algoritm,
options,
keyData, keyLength,
ivData,
data, data.count,
&cryptData, cryptLength,
&numBytesEncrypted)

if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.removeSubrange(numBytesEncrypted..
} else {
print("Error: \(cryptStatus)")
}

return cryptData;
}

Swift 3 & 4 with Data type

func testCrypt(data:Data, keyData:Data, ivData:Data, operation:Int) -> Data {
let cryptLength = size_t(data.count + kCCBlockSizeAES128)
var cryptData = Data(count:cryptLength)

let keyLength = size_t(kCCKeySizeAES128)
let options = CCOptions(kCCOptionPKCS7Padding)


var numBytesEncrypted :size_t = 0

let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
data.withUnsafeBytes {dataBytes in
ivData.withUnsafeBytes {ivBytes in
keyData.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(operation),
CCAlgorithm(kCCAlgorithmAES),
options,
keyBytes, keyLength,
ivBytes,
dataBytes, data.count,
cryptBytes, cryptLength,
&numBytesEncrypted)
}
}
}
}

if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.removeSubrange(numBytesEncrypted..
} else {
print("Error: \(cryptStatus)")
}

return cryptData;
}

let message = "Don´t try to read this text. Top Secret Stuff"
let messageData = message.data(using:String.Encoding.utf8)!
let keyData = "12345678901234567890123456789012".data(using:String.Encoding.utf8)!
let ivData = "abcdefghijklmnop".data(using:String.Encoding.utf8)!

let encryptedData = testCrypt(data:messageData, keyData:keyData, ivData:ivData, operation:kCCEncrypt)
let decryptedData = testCrypt(data:encryptedData, keyData:keyData, ivData:ivData, operation:kCCDecrypt)
var decrypted = String(bytes:decryptedData, encoding:String.Encoding.utf8)!

Example from sunsetted documentation section:

AES encryption in CBC mode with a random IV (Swift 3+)

The iv is prefixed to the encrypted data

aesCBC128Encrypt will create a random IV and prefixed to the encrypted code.

aesCBC128Decrypt will use the prefixed IV during decryption.

Inputs are the data and key are Data objects. If an encoded form such as Base64 if required convert to and/or from in the calling method.

The key should be exactly 128-bits (16-bytes), 192-bits (24-bytes) or 256-bits (32-bytes) in length. If another key size is used an error will be thrown.

PKCS#7 padding is set by default.

This example requires Common Crypto

It is necessary to have a bridging header to the project:

#import

Add the Security.framework to the project.

This is example, not production code.

enum AESError: Error {
case KeyError((String, Int))
case IVError((String, Int))
case CryptorError((String, Int))
}

// The iv is prefixed to the encrypted data
func aesCBCEncrypt(data:Data, keyData:Data) throws -> Data {
let keyLength = keyData.count
let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
if (validKeyLengths.contains(keyLength) == false) {
throw AESError.KeyError(("Invalid key length", keyLength))
}

let ivSize = kCCBlockSizeAES128;
let cryptLength = size_t(ivSize + data.count + kCCBlockSizeAES128)
var cryptData = Data(count:cryptLength)

let status = cryptData.withUnsafeMutableBytes {ivBytes in
SecRandomCopyBytes(kSecRandomDefault, kCCBlockSizeAES128, ivBytes)
}
if (status != 0) {
throw AESError.IVError(("IV generation failed", Int(status)))
}

var numBytesEncrypted :size_t = 0
let options = CCOptions(kCCOptionPKCS7Padding)

let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
data.withUnsafeBytes {dataBytes in
keyData.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES),
options,
keyBytes, keyLength,
cryptBytes,
dataBytes, data.count,
cryptBytes+kCCBlockSizeAES128, cryptLength,
&numBytesEncrypted)
}
}
}

if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.count = numBytesEncrypted + ivSize
}
else {
throw AESError.CryptorError(("Encryption failed", Int(cryptStatus)))
}

return cryptData;
}

// The iv is prefixed to the encrypted data
func aesCBCDecrypt(data:Data, keyData:Data) throws -> Data? {
let keyLength = keyData.count
let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
if (validKeyLengths.contains(keyLength) == false) {
throw AESError.KeyError(("Invalid key length", keyLength))
}

let ivSize = kCCBlockSizeAES128;
let clearLength = size_t(data.count - ivSize)
var clearData = Data(count:clearLength)

var numBytesDecrypted :size_t = 0
let options = CCOptions(kCCOptionPKCS7Padding)

let cryptStatus = clearData.withUnsafeMutableBytes {cryptBytes in
data.withUnsafeBytes {dataBytes in
keyData.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmAES128),
options,
keyBytes, keyLength,
dataBytes,
dataBytes+kCCBlockSizeAES128, clearLength,
cryptBytes, clearLength,
&numBytesDecrypted)
}
}
}

if UInt32(cryptStatus) == UInt32(kCCSuccess) {
clearData.count = numBytesDecrypted
}
else {
throw AESError.CryptorError(("Decryption failed", Int(cryptStatus)))
}

return clearData;
}

Example usage:

let clearData = "clearData0123456".data(using:String.Encoding.utf8)!
let keyData = "keyData890123456".data(using:String.Encoding.utf8)!
print("clearData: \(clearData as NSData)")
print("keyData: \(keyData as NSData)")

var cryptData :Data?
do {
cryptData = try aesCBCEncrypt(data:clearData, keyData:keyData)
print("cryptData: \(cryptData! as NSData)")
}
catch (let status) {
print("Error aesCBCEncrypt: \(status)")
}

let decryptData :Data?
do {
let decryptData = try aesCBCDecrypt(data:cryptData!, keyData:keyData)
print("decryptData: \(decryptData! as NSData)")
}
catch (let status) {
print("Error aesCBCDecrypt: \(status)")
}

Example Output:

clearData:   <636c6561 72446174 61303132 33343536>
keyData: <6b657944 61746138 39303132 33343536>
cryptData: <92c57393 f454d959 5a4d158f 6e1cd3e7 77986ee9 b2970f49 2bafcf1a 8ee9d51a bde49c31 d7780256 71837a61 60fa4be0>
decryptData: <636c6561 72446174 61303132 33343536>

Notes:

One typical problem with CBC mode example code is that it leaves the creation and sharing of the random IV to the user. This example includes generation of the IV, prefixed the encrypted data and uses the prefixed IV during decryption. This frees the casual user from the details that are necessary for CBC mode.

For security the encrypted data also should have authentication, this example code does not provide that in order to be small and allow better interoperability for other platforms.

Also missing is key derivation of the key from a password, it is suggested that PBKDF2 be used is text passwords are used as keying material.

For robust production ready multi-platform encryption code see RNCryptor.

AES Encryption in Swift?

Finally coded up the solution :

import CommonCrypto
struct AES {

// MARK: - Value
// MARK: Private
private let key: Data
private let iv: Data


// MARK: - Initialzier
init?(key: String, iv: String) {
guard key.count == kCCKeySizeAES128 || key.count == kCCKeySizeAES256, let keyData = key.data(using: .utf8) else {
debugPrint("Error: Failed to set a key.")
return nil
}

guard iv.count == kCCBlockSizeAES128, let ivData = iv.data(using: .utf8) else {
debugPrint("Error: Failed to set an initial vector.")
return nil
}


self.key = keyData
self.iv = ivData
}


// MARK: - Function
// MARK: Public
func encrypt(string: String) -> String {
guard let cryptData = crypt(data: string.data(using: .utf8), option: CCOperation(kCCEncrypt)) else { return "" }
return cryptData.base64EncodedString()
}

func decrypt(data: Data?) -> String? {
guard let decryptedData = crypt(data: data, option: CCOperation(kCCDecrypt)) else { return nil }
return String(bytes: decryptedData, encoding: .utf8)
}

func crypt(data: Data?, option: CCOperation) -> Data? {
guard let data = data else { return nil }

let cryptLength = data.count + kCCBlockSizeAES128
var cryptData = Data(count: cryptLength)

let keyLength = key.count
let options = CCOptions(kCCOptionPKCS7Padding)

var bytesLength = Int(0)

let status = cryptData.withUnsafeMutableBytes { cryptBytes in
data.withUnsafeBytes { dataBytes in
iv.withUnsafeBytes { ivBytes in
key.withUnsafeBytes { keyBytes in
CCCrypt(option, CCAlgorithm(kCCAlgorithmAES), options, keyBytes.baseAddress, keyLength, ivBytes.baseAddress, dataBytes.baseAddress, data.count, cryptBytes.baseAddress, cryptLength, &bytesLength)
}
}
}
}

guard UInt32(status) == UInt32(kCCSuccess) else {
debugPrint("Error: Failed to crypt data. Status \(status)")
return nil
}

cryptData.removeSubrange(bytesLength.. return cryptData
}
}

To get the encrypted value as string:

let encryptedPassword128 = aes128?.encrypt(string: response1)

Swift CryptoKit AES Encryption and Javascript AES Decryption

CryptoJS uses CBC mode by default, and doesn’t support GCM at all. You shouldn’t use CryptoJS at the best of times (the native and better-designed Web Crypto API is to be preferred), but especially not on the server, where Node.js has always had a native crypto module.

First, include the GCM nonce and tag, which are essential components:

return val.combined!.base64EncodedString()

Then, in Node.js, using the layout as described in the documentation for the combined property:

The data layout of the combined representation is: nonce, ciphertext, then tag.

// where `sealedBox` is a buffer obtained with `Buffer.from(encryptedString, 'base64')`
let nonce = sealedBox.slice(0, 12);
let ciphertext = sealedBox.slice(12, -16);
let tag = sealedBox.slice(-16);
let decipher = crypto.createDecipheriv('aes-128-gcm', key, nonce);
decipher.setAuthTag(tag);
let decrypted =
Buffer.concat([decipher.update(ciphertext), decipher.final()])
.toString('utf8');

Once that’s working, don’t forget to fix your key, because .init(data: Array(key.utf8)) is very uncomfortable (your AES keys should not be valid UTF-8).

  • If you’re starting with a password (for a good reason, not just because it seemed convenient), use a PBKDF to get key bytes. (Unfortunately, no PBKDF implementations are built into CryptoKit.)

    … but if the good reason is that it’s a user-provided password and you’re claiming to provide security, please get someone experienced with use of cryptography to review your work.

  • Otherwise, generate a random key safely and decode it from a Base64 or hex string. No UTF-8.

    node -p 'crypto.randomBytes(16).toString("base64")'

    And get someone experienced with use of cryptography to review your work anyway.

iOS CryptoSwift AES Encryption to Python Decryption works - but not the inverse

In the encrypt() method the IV is not considered. As in aesEncrypt(), the IV must be passed and used when creating the AES object.

Furthermore there are bugs in the encoding: The plaintext must be UTF8 encoded and the ciphertext must be hex encoded:

from Crypto.Cipher import AES
import binascii

def encrypt(key, iv, plaintext):
cipher = AES.new(key, MODE, iv, segment_size=128)
plaintext_bytes = plaintext.encode("utf-8")
ciphertext = cipher.encrypt(plaintext_bytes)
ciphertext_hex = binascii.b2a_hex(ciphertext)
return ciphertext_hex

This function is the counterpart to decrypt(), i.e. encrypt() can be used to generate a ciphertext which can be decrypted with decrypt() (or aesDecrypt()).


In the iOS code there are two bugs, both concerning the encoding: The ciphertext must not be UTF8 encoded, but hex decoded. And the decrypted data must not be hex encoded, but UTF-8 decoded.

A possible fix is:

func aesDecrypt(stringToDecrypt: String, key: Array, iv: Array) throws -> String {
let data = Array(hex: stringToDecrypt)
let decrypted = try AES(key: key, blockMode: CFB(iv: iv), padding: .noPadding).decrypt(data)
return String(bytes: decrypted, encoding: .utf8)!
}

This function is the counterpart to aesEncrypt(), i.e. aesDecrypt() can be used to decrypt a ciphertext generated with aesEncrypt() (or encrypt()).


Regarding security: A static IV is insecure. Instead, the IV should be randomly generated for each encryption. Since the (non-secret IV) is needed for decryption, it is passed along with the ciphertext (typically concatenated).

Decryption AES/GCM/PKCS5Padding iOS Swift

I was able to decrypt the input using "CryptoSwift" framework, was wondering if we can solve the same using the apple iOS CommonCrypto framework.

Any leads with using "CommonCrypto" would be greatly appreciated

 class func decryptCode123(_ cipher:String)-> String{

let key = "SOMEKEY"

var keyBytes: [UInt8] = []
var codeBytes: [UInt8] = []
var code = ""

if let keyData = NSData(base64Encoded:key, options: .ignoreUnknownCharacters) {
keyBytes = [UInt8](keyData as Data)
}
if let codeData = NSData(base64Encoded: cipher, options: .ignoreUnknownCharacters) {
codeBytes = [UInt8](codeData as Data)
}

debugPrint(codeBytes)

let codeBytescount = [UInt8](codeBytes).count

let iv = Array([UInt8](codeBytes)[0 ..< 32])
let cipher = Array([UInt8](codeBytes)[iv.count ..< codeBytescount])
do{
let gcm = GCM(iv: iv, mode: .combined)
let derKey = createKey(password:Data(key.utf8), salt: Data(iv))!

keyBytes = [UInt8](derKey)

let aes = try AES(key: keyBytes, blockMode: gcm, padding: .pkcs5)

print("aes created")
let decrypted = try aes.decrypt(cipher)
print("decrypted completed")
if let decryptedString = String(bytes: decrypted, encoding: .utf8) {
code = decryptedString
}

debugPrint(code)

}catch let error as AES.Error {
debugPrint(error.localizedDescription)
return code
} catch {
return code
}
return code
}

AES Encrypt and Decrypt

I found the solution, it is a good library.

Cross platform 256bit AES encryption / decryption.

This project contains the implementation of 256 bit AES encryption which works on all the platforms (C#, iOS, Android). One of the key objective is to make AES work on all the platforms with simple implementation.

Platforms Supported:
iOS ,
Android ,
Windows (C#).

https://github.com/Pakhee/Cross-platform-AES-encryption

Swift AES encryption with Java

That is not how encryption works. Every time you encrypt the same plaintext, you MUST get a different output. With GCM this is achieved by using a unique random IV each time - a nonce (number used once).

GCM has its own requirements on IV, namely that an IV shall never be reused, and that is important.

String airSecretKey = "Wk+Uzyyn8991w/2V5OIqiQ==";  // this key is now public so you cannot use it
SecretKey key = new SecretKeySpec(Base64.getDecoder().decode(airSecretKey), "AES");

SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[12];
secureRandom.nextBytes(iv);
AlgorithmParameters params = new GCMParameterSpec(128, iv);

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, params, secureRandom);

cipher.updateAAD(...); // if used
byte[] encrypted = cipher.doFinal(plaintext);

The Swift version should perform the same operations. Java generates the tag and appends it to the ciphertext. It looks like in Swift you have to handle it manually instead.

SWIFT AES Encrypt and Decrypt - gets different results

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...)



Related Topics



Leave a reply



Submit