Encrypt using AES key?

Hi,


I'm trying to figure out how to encrypt some data using an AES key. I also need to generate the AES key. My project is in Swift so I'd prefer to keep the code in Swift if possible.


My research suggests that an AES key is simply 256 random bits, which can be created this way:

  var aesKeyBytes = [UInt8](repeating: 0, count: 32)
  _ = SecRandomCopyBytes(kSecRandomDefault, 32, &aesKeyBytes)


So far so good, that code seems to fill the array with random bytes.


What happens next is very hazy. I think I need a SecKey object so I can call the SecKeyEncrypt function. I tried creating one this way:

  let aesKeyData = Data(aesKeyBytes)
  let aesKeyDict:[NSObject:NSObject] = [
       kSecAttrKeyType: kSecAttrKeyTypeRSA,
       kSecAttrKeyClass: kSecAttrKeyClassSymmetric,
       kSecAttrKeySizeInBits: NSNumber(value: 256),
       kSecReturnPersistentRef: true as NSObject
  ]
  let aesKey = SecKeyCreateWithData(aesKeyData as CFData, aesKeyDict as CFDictionary, nil)


It doesn't work though. The value of aesKey is nil after the function call. If I add the error parameter, the error reads:

"Unsupported symmetric key type: 42"


I suspect that kSecAttrKeyType should be something other than RSA, but I can't figure out what. In the documentation, it says I can use kSecAttrKeyTypeAES, but no such symbol actually exists in the code.


Frank

Replies

After digging around in the documentation, I think the function I need is SecKeyCreateFromData rather than SecKeyCreateWithData. But when I type that into my app, it isn't recognized as a valid function.


The documentation seems to suggest that SecKeyCreateFromData and kSecAttrKeyTypeAES are only available in Mac OS, which I can only guess is some kind of typo? I can't believe that AES encryption isn't available in iOS.

You are, alas, completely off in the weeds )-:

SecKeyEncrypt
is for use with asymmetric encryption, like RSA or EC. For symmetric encryption, like AES, you’ll need to use Common Crypto. The CryptoCompatibility sample code shows the way.

My project is in Swift so I'd prefer to keep the code in Swift if possible.

This is quite challenging. Common Crypto is not a nice API to call from Swift in general. Moreover, prior to Xcode 10 (currently in beta), even importing the module is un-fun. My recommendation is that you take the code from CryptoCompatibility, wrap it into a nice Objective-C class method that’s easy to call from Swift, and call that.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for the help. I managed to get something working.


I hate Swift anyway. I only use it so people won't think I'm a troglodyte.

/**

Gets application (alice) PublicKey for exchange

- returns: the public key that need to be sent to Bob

*/

public func getApplicationPublicKeyForExchange() throws -> SecKey? {

if #available(iOS 10.0, *) {

// generate a key for application

let attributes: [String: Any] =

[kSecAttrKeySizeInBits as String: 256,

kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,

kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,

kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false]

]

guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {

throw error!.takeRetainedValue() as Error

}

self.appPrivateKey = privateKey

self.appPublicKey = SecKeyCopyPublicKey(privateKey)

return self.appPublicKey

} else {

return nil

}

}



/**

Converts a base64 string into a SecKey

- parameter b64Key: String encoded base64

- returns: SecKey

*/

func secKey(from b64Key: String) throws -> SecKey? {

if #available(iOS 10.0, *) {

let attributes: [String: Any] = [

kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,

kSecAttrKeyClass as String: kSecAttrKeyClassPublic,

]

guard let data = Data(base64Encoded: b64Key),

let key = SecKeyCreateWithData(data as CFData,

attributes as CFDictionary,

&error) else {

throw error!.takeRetainedValue() as Error

}

return key

}

return nil

}


/**

Generates the shared secret using Bob's public key and the app private key

- parameter publicKey: SecKey the publickKey received from Bob

- parameter sharedSecret: The secret received from Bob. This was created by using the Alice's public key

*/

public func getSharedSecret(for publicKey: SecKey?) throws -> CFData? {

if #available(iOS 10.0, *) {

guard let publicKey = publicKey, let privateKey = self.appPrivateKey else {

return nil

}

let attributes: [String: Any] =

[kSecAttrKeySizeInBits as String: 256,

kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,

kSecAttrKeyClass as String: kSecAttrKeyClassPublic,

kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false]

]

guard let sharedSecret = SecKeyCopyKeyExchangeResult(privateKey,

SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256,

publicKey,

attributes as CFDictionary, &error) else {

throw error!.takeRetainedValue() as Error

}

return sharedSecret

} else {

return nil

}

}


// MARK: - AES256


enum AESError: Swift.Error {

case keyGeneration(status: Int)

case cryptoFailed(status: CCCryptorStatus)

case badKeyLength

case badInputVectorLength

}


func encrypt(_ digest: Data, key: Data) throws -> Data {

return try crypt(input: digest, key: key, operation: CCOperation(kCCEncrypt))

}


func decrypt(_ encrypted: Data, key: Data) throws -> Data {

return try crypt(input: encrypted, key: key, operation: CCOperation(kCCDecrypt))

}


private func crypt(input: Data, key: Data, operation: CCOperation) throws -> Data {

// should be the same???

var outLength = Int(0)

var outBytes = [UInt8](repeating: 0, count: input.count + kCCBlockSizeAES128)

var status: CCCryptorStatus = CCCryptorStatus(kCCSuccess)

input.withUnsafeBytes { (encryptedBytes: UnsafePointer<UInt8>!) -> () in

// iv.withUnsafeBytes { (ivBytes: UnsafePointer<UInt8>!) in


key.withUnsafeBytes { (keyBytes: UnsafePointer<UInt8>!) -> () in

status = CCCrypt(operation,

CCAlgorithm(kCCAlgorithmAES), // algorithm

CCOptions(kCCOptionPKCS7Padding), // options

keyBytes, // key

key.count, // keylength

nil, // iv

encryptedBytes, // dataIn

input.count, // dataInLength

&outBytes, // dataOut

outBytes.count, // dataOutAvailable

&outLength) // dataOutMoved

}

// }

}

guard status == kCCSuccess else {

throw AESError.cryptoFailed(status: status)

}

return Data(bytes: UnsafePointer<UInt8>(outBytes), count: outLength)

}



extension String {

func fromBase64() -> String? {

guard let data = Data(base64Encoded: self) else {

return nil

}

return String(data: data, encoding: .utf8)

}

func toBase64() -> String {

return Data(self.utf8).base64EncodedString()

}

}