I try to get sha384 with ECDSA keys. I follow up with this article: https://digitalleaves.com/blog/2015/10/asymmetric-cryptography-in-swift/?replytocom=52690#respond
but I change RSA to EC, of course.
and keys are generated, but encryptMessageWithPublicKey doesn't work :(
Variable "status" in method: encryptMessageWithPublicKey return -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)
})
}
})
}
}
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
So the answer is really simple..
Apple's implementation only supports ECDSA, which can be used for signing but not encryption.
errorStatus of -50 means your key size is too large. For a reference, look for "errSecKeySizeNotAllowed" here: https://www.osstatus.com/search/results?platform=all&framework=all&search=-50
Apples kSecAttrKeyTypeECSECPrimeRandom and kSecAttrKeyTypeEC only supports a key size of 256, inside and outside of the secure enclave, https://developer.apple.com/library/content/documentation/Security/Conceptual/CertKeyTrustProgGuide/SecureKeyGen.html, so you need to make the following change 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.
Make sure you have added the "Security.framework" in your project under General > Linked Frameworks and Libraries.
来源:https://stackoverflow.com/questions/43893045/ksecattrkeytypeec-swift