I am using SecureEnclave on iOS to encrypt/decrypt my confidential key and save it in UserPreferences. It has been setup and encrypting the data successfully. But whenever I try to decrypt the data, it gives me the following error:
Error Domain=NSOSStatusErrorDomain Code=-50 \"ECIES: Failed to aes-gcm decrypt data\" UserInfo={NSDescription=ECIES: Failed to aes-gcm decrypt data}
After a lot of searching, I found some links but they are not of any help. This Github issue talks about the issue. It states,
Additionally, on 10.3 there was a problem with decrypting large amounts of data with kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM. I filed a bug report for it, and it got fixed in iOS 11. :)
But I am using iPhone 8 with iOS 12.2 and there is still a problem.
These two SO questions, here and here provide some details but I am not able to decipher it. Plus, I am using Swift 4.
Below is the relevant code which I am using to encrypt/decrypt the data.
To Generate Key Pair
func generateKeyPair(accessControl: SecAccessControl) throws -> (`public`: SecureEnclaveKeyReference, `private`: SecureEnclaveKeyReference) {
let publicKeyParameters: [String: AnyObject] = [
kSecAttrIsPermanent as String: false as AnyObject,
kSecAttrApplicationTag as String: "com.xxx.xxx" as AnyObject,
kSecAttrLabel as String: "PublicKey" as AnyObject
]
let privateKeyParameters: [String: AnyObject] = [
(kSecAttrCanDecrypt as CFString) as String: true as CFBoolean,
kSecAttrIsPermanent as String: true as AnyObject,
kSecAttrAccessControl as String: accessControl,
kSecAttrApplicationTag as String: "com.xxx.xxx" as AnyObject,
kSecAttrLabel as String: "PrivateKey" as AnyObject
]
let parameters: [String: AnyObject] = [
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrKeySizeInBits as String: 256 as AnyObject,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPublicKeyAttrs as String: publicKeyParameters as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParameters as AnyObject
]
var publicKey, privateKey: SecKey?
let status = SecKeyGeneratePair(parameters as CFDictionary, &publicKey, &privateKey)
print("Result = \(status) - Public Key = \(publicKey) - Private Key = \(privateKey)")
guard status == errSecSuccess else {
throw SecureEnclaveHelperError(message: "Could not generate keypair", osStatus: status)
}
return (public: SecureEnclaveKeyReference(publicKey!), private: SecureEnclaveKeyReference(privateKey!))
}
Encryption
#available(iOS 10.3, *)
func encrypt(_ digest: Data, publicKey: SecureEnclaveKeyReference) throws -> Data {
var error : Unmanaged<CFError>?
let result = SecKeyCreateEncryptedData(publicKey.underlying, SecKeyAlgorithm.eciesEncryptionStandardX963SHA256AESGCM, digest as CFData, &error)
if result == nil {
throw SecureEnclaveHelperError(message: "\(error)", osStatus: 0)
}
return result as! Data
}
Decryption
#available(iOS 10.3, *)
func decrypt(_ digest: Data, privateKey: SecureEnclaveKeyReference) throws -> Data {
var error : Unmanaged<CFError>?
let result = SecKeyCreateDecryptedData(privateKey.underlying, SecKeyAlgorithm.eciesEncryptionStandardX963SHA256AESGCM, digest as CFData, &error)
if result == nil {
throw SecureEnclaveHelperError(message: "\(error)", osStatus: 0)
}
return result as! Data
}
Any help is highly appreciated. Thanks.
I got the same error and found out that I generated two different key pairs where I encrypted my data with one public key but performed decryption using the other wrong private key. So make sure that the decryption operation uses the right private key corresponding to the public key used in encryption.
Related
I am facing an issue with SecureEnclave encryption in SecKeyGeneratePair method.
App crash on SecKeyGeneratePair method, I don't know what's missing or something wrong. I am referring https://github.com/trailofbits/SecureEnclaveCrypto
See the following code snippet.
func generateKeyPair(accessControl: SecAccessControl) throws -> (`public`: SecureEnclaveKeyReference, `private`: SecureEnclaveKeyReference) {
let privateKeyParams: [String: Any] = [
kSecAttrLabel as String: privateLabel,
kSecAttrIsPermanent as String: true,
kSecAttrAccessControl as String: accessControl,
]
let params: [String: Any] = [
kSecAttrKeyType as String: attrKeyTypeEllipticCurve,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: privateKeyParams
]
var publicKey, privateKey: SecKey?
let status = SecKeyGeneratePair(params as CFDictionary, &publicKey, &privateKey)
guard status == errSecSuccess else {
throw SecureEnclaveHelperError(message: "Could not generate keypair", osStatus: status)
}
return (public: SecureEnclaveKeyReference(publicKey!), private: SecureEnclaveKeyReference(privateKey!))
}
How can i use the Diffie-Hellman key exchange to encrypt and decrypt messages?
I'am able to generate the shared keys (for both bob and alice) but SecKeyCopyKeyExchangeResult returns me a Data...how can i get SecKey to use with SecKeyCreateDecryptedData and SecKeyCreateEncryptedData ?
So i think i should extract the SecKey somehow from the shared data so i can make symettrical encryption/decryption.
The code so far is:
let bob_shared_secret: NSData = generateSharedKey_ecdh(publicKey: alicePublicKey, privateKey: bobPrivateKey)!
let alice_shared_secret: NSData = generateSharedKey_ecdh(publicKey: bobPublicKey, privateKey: alicePrivateKey)!
print("equals? \(bob_shared_secret == alice_shared_secret)!") //true
let clearText = "Hello From Alice"
let algorithm: SecKeyAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
let cipherTextData: Data? = SecKeyCreateEncryptedData(alicePublicKey, algorithm,
clearTextData as CFData,
&error) as Data?
let clearTextData = SecKeyCreateDecryptedData(???? as SecKey, //what to put here??
algorithm,
cipherTextData as CFData,
&error) as Data?
private func generateSharedKey_ecdh(publicKey: SecKey, privateKey: SecKey) -> NSData?
{
var error: Unmanaged<CFError>?
let keyPairAttr:[String : Any] = [
kSecAttrKeySizeInBits as String: 256, //retro compatibility
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, //Elliptic curve algorithm.
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false],
kSecPublicKeyAttrs as String:[kSecAttrIsPermanent as String: false],
SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 256
]
let algorithm:SecKeyAlgorithm = SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256
let shared:CFData? = SecKeyCopyKeyExchangeResult(privateKey, algorithm, publicKey, keyPairAttr as CFDictionary, &error)
return shared
}
Key Pair generation... keypair it's a class i created to contain keys
class KeyPair {
var publicKey: SecKey
var privateKey: SecKey
init(publicKey: SecKey, privateKey: SecKey) {
self.publicKey = publicKey
self.privateKey = privateKey
}
}
private func generateKeyPair() -> KeyPair? {
let attributes: [String: Any] = [kSecAttrKeySizeInBits as String: 256,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false],
kSecPublicKeyAttrs as String:[kSecAttrIsPermanent as String: false]]
var error: Unmanaged<CFError>?
if let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error),
let publicKey = SecKeyCopyPublicKey(privateKey){
return KeyPair(publicKey: publicKey, privateKey: privateKey)
}
return nil
}
SecKeyCreateEncryptedData and SecKeyCreateDecryptedData is for asymetric encryption (using ECIES or RSA)
To perform a symetric encryption / decryption operation you should use CommonCrypto or a pure Swift alternative of it like CryptoSwift and an AES algorithm.
I was trying to store two keys private and public in KeyChain and when I try to do so my result valuable return 0 which I am assuming that mean it was store ,but when I try to get back to decrypt a message I am getting it back as nil so if there is a way to check if the keys were store based SecItemCopyMatching? but I am not getting any error while creating them
let tagName = "PrivateKeyTag"
let privkey = "key"
let privkeyData = Data(privkey!.utf8)
let privateFilter: [String : Any] = [
(kSecClass as String) : kSecClassKey,
(kSecAttrKeyType as String) : kSecAttrKeyTypeRSA,
(kSecAttrApplicationTag as String) : tagName,
(kSecValueData as String) : privkeyData,
(kSecAttrKeyClass as String) : kSecAttrKeyClassPrivate,
// kSecAttrKeySizeInBits as String: 2048,
(kSecReturnPersistentRef as String): true,
] as [String : Any]
let result = SecItemAdd(privateFilter as CFDictionary, nil)
if ((result != noErr) && (result != errSecDuplicateItem)) {
NSLog("Cannot add key to keychain, status \(result).")
}
let getquery: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
]
var item: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &item)
guard status == errSecSuccess else {
print("key not found")
return
}
let key = item as! SecKey
When you create a cryptographic key, you can set the parameter kSecAttrIsPermanent to true which will automatically store the key in the default keychain. This will clean your code a bit so you no longer have to deal with the SecItemAdd() and all the error handling with that. So here is a simpler way to do what you're trying to do.
To create a key and query a key
let tag = "com.example.keys.mykey".data(using: .utf8)!
let attributes: [String: Any] =
[kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String:
[kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag]
]
let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, nil)
let query: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecReturnRef as String: true]
var item: CFTypeRef?
let status2 = SecItemCopyMatching(query as CFDictionary, &item)
guard status2 == errSecSuccess else { print("error1"); return }
let key = item as! SecKey
Running this code I believe will accomplish what you are trying to do in the code you provided in the question. I tried running the code you gave and the version of Xcode and swift I am using gives compiler errors.
If you run the code in this answer you will notice that no errors are printed to the console, indicating that the key was successfully found in the default keychain.
So this is my first question on Stack Overflow and it's about keychain so, I read apple documentation about keychain and they mention that the key can be generated or obtain by other means "which is my case" then I created "query dictionary" like they said ,but when I tried to add it I get an error and my status is equal to -50 I don't know what does than mean also the reason I am trying to store my key is so I can use it with "SecKeyDecrypt" to decrypt messages which require SecKey as parameter
UPDATE: I found that what the return code means -50 errSecParam which mean
"One or more parameters passed to the function are not valid." then I try to remove my kSecValueRef as String: privateKey
and it works but still, I want that key to be stored ??
let privateKey = "myKey"
let tag = "mybunlde.com".data(using: .utf8)!
let addPrivateKey: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecValueRef as String: privateKey]
let status = SecItemAdd(addPrivateKey as CFDictionary, nil)
guard status == errSecSuccess else { print("error while creating the key")
return
}
let getPrivateKey: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecReturnRef as String: true]
print("getPrivateKey \(getPrivateKey)")
} else {
print("no key found ")
}
https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_keychain?language=objc
https://apple.stackexchange.com/questions/8993/how-can-i-add-a-private-key-to-my-keychain
Adding private key into iOS Keychain
use above link.....
you can use this lib for store & retrieve data from keychain
https://github.com/jrendel/SwiftKeychainWrapper
e.g
let save: Bool = KeychainWrapper.standard.set("hello keychain", forKey: "key_name")
let get: String? = KeychainWrapper.standard.string(forKey: "key_name")
I'm attempting to do the following in my iOS application:
Generate a key pair using SecKeyGeneratePair, storing the private key in the Secure Enclave
Sign some data using the private key
It works if I hang on to the private key reference when keys are first generated, but does not work if I attempt to retrieve the reference from the Keychain after discarding the initial pointer.
The keys are generated like this:
func generateKeyPair() -> Bool {
if let access = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage],
nil) {
let privateKeyAttr = [kSecAttrIsPermanent : 1,
kSecAttrApplicationTag : privateTag,
kSecAttrAccessControl as String: access
] as NSDictionary
let publicKeyAttr = [kSecAttrIsPermanent : 0,
kSecAttrApplicationTag : publicTag
] as NSDictionary
let keyPairAttr = [kSecAttrKeySizeInBits : 256,
kSecAttrKeyType : kSecAttrKeyTypeEC,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs : privateKeyAttr,
kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary
let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)
return err == noErr
}
The signing method is below:
func signData(plainText: Data) -> NSData? {
guard privateKey != nil else {
print("Private key unavailable")
return nil
}
let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data
let signature = UnsafeMutablePointer<UInt8>.allocate(capacity: 128)
var signatureLength = 128
let err = SecKeyRawSign(privateKey!,
.PKCS1SHA1,
[UInt8](digestToSign),
Int(CC_SHA1_DIGEST_LENGTH),
signature,
&signatureLength)
print("Signature status: \(err)")
let sigData = NSData(bytes: signature, length: Int(signatureLength))
return sigData
}
func sha1DigestForData(data: NSData) -> NSData {
let len = Int(CC_SHA1_DIGEST_LENGTH)
let digest = UnsafeMutablePointer<UInt8>.allocate(capacity: len)
CC_SHA1(data.bytes, CC_LONG(data.length), digest)
return NSData(bytesNoCopy: UnsafeMutableRawPointer(digest), length: len)
}
This asks for my fingerprint and works flawlessly. I then use another method to get the key reference from the Keychain:
func getPrivateKeyRef() -> SecKey? {
let parameters = [
kSecClass as String: kSecClassKey,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrApplicationTag as String: privateTag,
kSecReturnRef as String: true,
] as [String : Any]
var ref: AnyObject?
let status = SecItemCopyMatching(parameters as CFDictionary, &ref)
print("Get key status: \(status)")
if status == errSecSuccess { return ref as! SecKey? } else { return nil }
}
The SecItemCopyMatching returns a success status, but attempting to use the resulting SecKey item as a private key in SecKeyRawSign results in error -25293 Authorization/Authentication failed. This status only appears after I provide my fingerprint, so the actual fingerprint verification succeeds, but the key somehow remains unusable.
What is the correct way to use a key stored in Secure Enclave to sign data?
Looks like using kSecAttrLabel attribute is necessary for correct fetching of the key. It must be specified when generating the keys, and when fetching them through SecItemCopyMatching.
My working solution for fetching the private key:
func getPrivateKey() -> SecKey? {
let parameters = [
kSecClass as String: kSecClassKey,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrApplicationTag as String : "privateTag",
kSecAttrLabel as String : "privateTag",
kSecReturnRef as String: true,
] as [String : Any]
var ref: AnyObject?
let status = SecItemCopyMatching(parameters as CFDictionary, &ref)
if status == errSecSuccess {
return (ref as! SecKey)
}
return nil
}
Where the key pair was generated like this:
var publicKey:SecKey?
var privateKey:SecKey?
if let access = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage],
nil) {
let privateKeyAttr = [kSecAttrIsPermanent : 1,
kSecAttrApplicationTag as String : "privateTag",
kSecAttrLabel as String : "privateTag",
kSecAttrAccessControl as String: access
] as NSDictionary
let publicKeyAttr = [kSecAttrIsPermanent : 0,
kSecAttrApplicationTag as String : "publicTag",
kSecAttrLabel as String : "publicTag",
] as NSDictionary
// only 256 bit EC keys are supported in the Secure Enclave
let keyPairAttr = [kSecAttrKeySizeInBits : 256,
kSecAttrKeyType : kSecAttrKeyTypeEC,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs : privateKeyAttr,
kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary
let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)