kSecAttrKeyTypeEC causing encryptMessageWithPublicKey() to fail
Asked Answered
A

5

2

I'm trying to get sha384 with ECDSA keys. I'm following this article but I changed RSA to EC, of course.

The keys are successfully generated, but encryptMessageWithPublicKey() doesn't work :(

Variable "status" in method encryptMessageWithPublicKey() returns -50 ...

Here is my code:

import UIKit
import Security

private let _singletonInstance = AsymmetricCryptoManager()


private let kAsymmetricCryptoManagerApplicationTag = "com.AsymmetricCrypto.keypair"
private let kAsymmetricCryptoManagerKeyType = kSecAttrKeyTypeEC//kSecAttrKeyTypeEC
private let kAsymmetricCryptoManagerKeySize = 384

// private key parameters
let privateKeyParams: [String: AnyObject] = [
    kSecAttrIsPermanent as String: true as AnyObject,
    kSecAttrApplicationTag as String: "com.AsymmetricCrypto.keypair" as AnyObject
]

// private key parameters
let publicKeyParams: [String: AnyObject] = [
    kSecAttrIsPermanent as String: true as AnyObject,
    kSecAttrApplicationTag as String: "com.AsymmetricCrypto.keypair" as AnyObject
]

enum AsymmetricCryptoException: Error {
    case unknownError
    case duplicateFoundWhileTryingToCreateKey
    case keyNotFound
    case authFailed
    case unableToAddPublicKeyToKeyChain
    case wrongInputDataFormat
    case unableToEncrypt
    case unableToDecrypt
    case unableToSignData
    case unableToVerifySignedData
    case unableToPerformHashOfData
    case unableToGenerateAccessControlWithGivenSecurity
    case outOfMemory
}



class AsymmetricCryptoManager: NSObject {

    /** Shared instance */
    class var sharedInstance: AsymmetricCryptoManager {
        return _singletonInstance
    }


    func createSecureKeyPair(_ completion: ((_ success: Bool, _ error: AsymmetricCryptoException?) -> Void)? = nil) {
        // private key parameters
        let privateKeyParams: [String: AnyObject] = [
            kSecAttrIsPermanent as String: true as AnyObject,
            kSecAttrApplicationTag as String: kAsymmetricCryptoManagerApplicationTag as AnyObject
        ]

        // private key parameters
        let publicKeyParams: [String: AnyObject] = [
            kSecAttrIsPermanent as String: true as AnyObject,
            kSecAttrApplicationTag as String: kAsymmetricCryptoManagerApplicationTag as AnyObject
        ]

        // global parameters for our key generation
        let parameters: [String: AnyObject] = [
            kSecAttrKeyType as String:          kAsymmetricCryptoManagerKeyType,
            kSecAttrKeySizeInBits as String:    kAsymmetricCryptoManagerKeySize as AnyObject,
            kSecPublicKeyAttrs as String:       publicKeyParams as AnyObject,
            kSecPrivateKeyAttrs as String:      privateKeyParams as AnyObject,
            ]

        // asynchronously generate the key pair and call the completion block
        DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { () -> Void in
            var pubKey, privKey: SecKey?
            let status = SecKeyGeneratePair(parameters as CFDictionary, &pubKey, &privKey)

            if status == errSecSuccess {
                DispatchQueue.main.async(execute: { completion?(true, nil) })
            } else {
                var error = AsymmetricCryptoException.unknownError
                switch (status) {
                case errSecDuplicateItem: error = .duplicateFoundWhileTryingToCreateKey
                case errSecItemNotFound: error = .keyNotFound
                case errSecAuthFailed: error = .authFailed
                default: break
                }
                DispatchQueue.main.async(execute: { completion?(false, error) })
            }
        }
    }



    private func getPublicKeyReference() -> SecKey? {
        let parameters = [
            kSecClass as String: kSecClassKey,
            kSecAttrKeyType as String: kSecAttrKeyTypeEC,
            kSecAttrApplicationTag as String: kAsymmetricCryptoManagerApplicationTag,
            kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
            kSecReturnRef as String: true,
            ] as [String : Any]
        var ref: AnyObject?
        let status = SecItemCopyMatching(parameters as CFDictionary, &ref)
        if status == errSecSuccess { return ref as! SecKey? }
        else { return nil }
    }


    func encryptMessageWithPublicKey(_ message: String, completion: @escaping (_ success: Bool, _ data: Data?, _ error: AsymmetricCryptoException?) -> Void) {
        DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { () -> Void in

            if let publicKeyRef = self.getPublicKeyReference() {
                // prepare input input plain text
                guard let messageData = message.data(using: String.Encoding.utf8) else {
                    completion(false, nil, .wrongInputDataFormat)
                    return
                }


                let plainText = (messageData as NSData).bytes.bindMemory(to: UInt8.self, capacity: messageData.count)
                let plainTextLen = messageData.count

                // prepare output data buffer
                var cipherData = Data(count: SecKeyGetBlockSize(publicKeyRef))
                let cipherText = cipherData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<UInt8>) -> UnsafeMutablePointer<UInt8> in
                    return bytes
                })


                var cipherTextLen = cipherData.count

                let status = SecKeyEncrypt(publicKeyRef, .PKCS1SHA384, plainText, plainTextLen, cipherText, &cipherTextLen)

                // analyze results and call the completion in main thread
                DispatchQueue.main.async(execute: { () -> Void in
                    completion(status == errSecSuccess, cipherData, status == errSecSuccess ? nil : .unableToEncrypt)
                    cipherText.deinitialize()
                })
                return
            } else {


                DispatchQueue.main.async(execute: { completion(false, nil, .keyNotFound) }) }
        }
    }


    func decryptMessageWithPrivateKey(_ encryptedData: Data, completion: @escaping (_ success: Bool, _ result: String?, _ error: AsymmetricCryptoException?) -> Void) {
        DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { () -> Void in

            if let privateKeyRef = self.getPublicKeyReference() {
                // prepare input input plain text
                let encryptedText = (encryptedData as NSData).bytes.bindMemory(to: UInt8.self, capacity: encryptedData.count)
                let encryptedTextLen = encryptedData.count

                // prepare output data buffer
                var plainData = Data(count: 1024)
                let plainText = plainData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<UInt8>) -> UnsafeMutablePointer<UInt8> in
                    return bytes
                })
                var plainTextLen = plainData.count

                let status = SecKeyDecrypt(privateKeyRef, .PKCS1SHA384, encryptedText, encryptedTextLen, plainText, &plainTextLen)

                // analyze results and call the completion in main thread
                DispatchQueue.main.async(execute: { () -> Void in
                    if status == errSecSuccess {
                        // adjust NSData length
                        plainData.count = plainTextLen
                        // Generate and return result string
                        if let string = NSString(data: plainData as Data, encoding: String.Encoding.utf8.rawValue) as String? {
                            completion(true, string, nil)
                        } else { completion(false, nil, .unableToDecrypt) }
                    } else { completion(false, nil, .unableToDecrypt) }
                    plainText.deinitialize()
                })
                return
            } else { DispatchQueue.main.async(execute: { completion(false, nil, .keyNotFound) }) }
        }
    }


}



class ViewController: UIViewController {

    override func viewDidLoad() {
        AsymmetricCryptoManager.sharedInstance.createSecureKeyPair({ success, err in

            if success {

                AsymmetricCryptoManager.sharedInstance.encryptMessageWithPublicKey("thisShouldWork", completion: { (success, data, error) in
                    print(success)
                })

            }

            })

    }

}
Aundreaaunson answered 10/5, 2017 at 12:51 Comment(7)
"doesn't work" means what?Ligni
It is mean, method "encryptMessageWithPublicKey" should return me true, but for now return me false. Also, I figured out, when is everything right variable "status" in this method return 0, but when I change from RSA, to EC and change 256 to 384, return me -50 :/Aundreaaunson
check resulting cipherTextLen. is it 0?Bertiebertila
@Bertiebertila Thank you for your interest. cipherTextLen return 32Aundreaaunson
paramErr = -50, /*error in user parameter list*/Bertiebertila
check this https://mcmap.net/q/754048/-what-is-the-padding-type-for-elliptic-curve-cryptoBertiebertila
see the answer, how to encrypt / decrypt with your key pairBertiebertila
F
3

errorStatus of -50 means your key size is too large. For a reference, look for "errSecKeySizeNotAllowed" here.

According to Apple's Documentation, kSecAttrKeyTypeECSECPrimeRandom and kSecAttrKeyTypeEC (now deprecated) only support a key size of 256, inside and outside of the Secure Enclave, so you need to make the following change for it to work:

kAsymmetricCryptoManagerKeySize = 256

Larger key sizes are allowed for RSA, which is why you are not getting an error. A 256 bit EC key is said to be as secure as a 3072 bit RSA key.

Faceharden answered 2/10, 2017 at 0:1 Comment(2)
keysize of 384 working for me in iOS12 - don't know if its release specific.Payable
You can change the keySize to be greater than 256 bits if you're storing it outside of the Secure Enclave. The 256 bit limitation only applies when storing inside of the Secure Enclave, according to the documentation: "Stores only 256-bit elliptic curve private keys."Isidor
A
1

So the answer is really simple..

Apple's implementation only supports ECDSA, which can be used for signing but not encryption.

Aundreaaunson answered 10/5, 2017 at 20:14 Comment(0)
B
1

1) read please first https://www.apple.com/business/docs/iOS_Security_Guide.pdf 2) use generated ECDSA key for signing

import Foundation
import Security

private let kAsymmetricCryptoManagerApplicationTag = "com.AsymmetricCrypto.keypair"
private let kAsymmetricCryptoManagerKeyType = kSecAttrKeyTypeEC
private let kAsymmetricCryptoManagerKeySize = 384

// private key parameters
let privateKeyParams: [String: AnyObject] = [
    kSecAttrIsPermanent as String: false as AnyObject,
    kSecAttrApplicationTag as String: "com.AsymmetricCrypto.keypair" as AnyObject
]


// private key parameters
let publicKeyParams: [String: AnyObject] = [
    kSecAttrIsPermanent as String: false as AnyObject,
    kSecAttrApplicationTag as String: "com.AsymmetricCrypto.keypair" as AnyObject
]

// parameters
let parameters: [String: AnyObject] = [
    kSecAttrKeyType as String:          kAsymmetricCryptoManagerKeyType,
    kSecAttrKeySizeInBits as String:    kAsymmetricCryptoManagerKeySize as AnyObject,
    kSecPublicKeyAttrs as String:       publicKeyParams as AnyObject,
    kSecPrivateKeyAttrs as String:      privateKeyParams as AnyObject,
]

var pubKey, privKey: SecKey?
let status = SecKeyGeneratePair(parameters as CFDictionary, &pubKey, &privKey)

if let privKey = privKey, let pubKey = pubKey  {

    let message = "qwerty"
    let messageData = message
        .utf8CString
        .map{ UInt8(bitPattern: $0) }

    var sign = [UInt8](repeating: 0, count:  256)
    var length = sign.count

    let status = SecKeyRawSign(privKey, .PKCS1SHA384, messageData, messageData.count, &sign, &length)
    print("message: \"\(message)\" was sign with status:", status == errSecSuccess ? "OK": "KO")

    let verified = SecKeyRawVerify(pubKey, .PKCS1SHA384, messageData, messageData.count, sign, length)
    print("message: \"\(message)\" was", verified == errSecSuccess ? "unmodified\t" : "modified  \t", messageData)

    // modified messageData
    var modified = messageData
    modified[0] = messageData[0] + 1

    let modifiedMessage = String(cString: modified)

    let verified2 = SecKeyRawVerify(pubKey, .PKCS1SHA384, modified, modified.count, sign, length)
    print("message: \"\(modifiedMessage)\" was", verified2 == errSecSuccess ? "unmodified" : "modified  \t", modified)
}

it prints

message: "qwerty" was sign with status: OK
message: "qwerty" was unmodified     [113, 119, 101, 114, 116, 121, 0]
message: "rwerty" was modified       [114, 119, 101, 114, 116, 121, 0]

In real applications the

  • a) message is encrypted with some symmetric key (AES, ...)
  • b) the key is encrypted with RSA (SecKeyEncrypt)
  • c) encrypted message + encryptedKey are signed with ECDSA
  • d) everything is delivered to receiver
  • e) receiver check signature
  • f) decrypt encryptedKey (SecKeyDecrypt)
  • g) decrypt the message (AES, ...)

to check what is supported, use

SecKeyIsAlgorithmSupported(pubKey, .verify, .ecdsaSignatureRFC4754) == true

Another way to sign and verify data

if let cdata = CFDataCreate(kCFAllocatorDefault, messageData, messageData.count),
        // create signature
        let csign = SecKeyCreateSignature(privKey, .ecdsaSignatureRFC4754, cdata, nil) {

        // transfer messageData, csign

        // verify signature
        let verified = SecKeyVerifySignature(pubKey, .ecdsaSignatureRFC4754, cdata, csign, nil)
        print(cdata, csign, verified)
    }

For Encryption / Decryption use ECIES or RSA

SecKeyIsAlgorithmSupported(pubKey, .encrypt, .eciesEncryptionCofactorX963SHA384AESGCM)
SecKeyIsAlgorithmSupported(privKey, .decrypt, .eciesEncryptionCofactorX963SHA384AESGCM)

return true with your key pair. Encrypt / Decrypt with your key pair

if let cdata = CFDataCreate(kCFAllocatorDefault, messageData, messageData.count) {
        print("data", cdata)
        if let coded = SecKeyCreateEncryptedData(pubKey, .eciesEncryptionCofactorX963SHA384AESGCM, cdata, nil) {
            print("\ncoded",coded)

            if let decoded = SecKeyCreateDecryptedData(privKey, .eciesEncryptionCofactorX963SHA384AESGCM, coded, nil)
            {
                print("\ndecoded", decoded)
            }
        }
    }

prints

data <71776572 747900>

coded <04ebbf3a 4bb5b767 3251f4dc d131f7be 11516a92 4c8b4f66 23d08751 5ab45cc2 a97e908d 0e689e02 81b74ba0 f9181b24 5a90dea0 052ef3ef 4be6027d 1b67ab71 cf123561 4dfdac16 837f3b6c 195f5122 985bbedb 9dd2e52e e1828f08 4ce6ecb8 6b089205 c820bf0f f8dc6620 24fa53ed 75431006 a6f14c9d>

decoded <71776572 747900>

for more info read https://www.ietf.org/rfc/rfc5289.txt

Bertiebertila answered 10/5, 2017 at 20:54 Comment(0)
I
0

To anyone who encounters this issue while using the Secure Enclave, the issue is that

These keys can only be used for creating and verifying cryptographic signatures, or for elliptic curve Diffie-Hellman key exchange (and by extension, symmetric encryption).

meaning that you can't encrypt with it.

Isidor answered 18/6, 2020 at 23:26 Comment(0)
P
-1

Make sure you have added the "Security.framework" in your project under General > Linked Frameworks and Libraries.

Precept answered 10/5, 2017 at 13:0 Comment(1)
Thanks, I added, but this didn't solve my problem :(Aundreaaunson

© 2022 - 2024 — McMap. All rights reserved.