Does iOS expose API for key generation, and secret key derivation using ECDH?
From what I see, apple are using it (and specifically x25519) internally but I don't see it exposed as public API by common crypto or otherwise.
Thanks,
Z
Done in playground with Xcode 8.3.3, generates a private/public key using EC for Alice, Bob, then calculating the shared secret for Alice using Alice's private and Bob's public, and share secret for Bob using Bob's private and Alice's public and finally asserting that they're equal.
import Security
import UIKit
let attributes: [String: Any] =
[kSecAttrKeySizeInBits as String: 256,
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecPrivateKeyAttrs as String:
[kSecAttrIsPermanent as String: false]
]
var error: Unmanaged<CFError>?
if #available(iOS 10.0, *) {
// generate a key for alice
guard let privateKey1 = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
let publicKey1 = SecKeyCopyPublicKey(privateKey1)
// generate a key for bob
guard let privateKey2 = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
let publicKey2 = SecKeyCopyPublicKey(privateKey2)
let dict: [String: Any] = [:]
// alice is calculating the shared secret
guard let shared1 = SecKeyCopyKeyExchangeResult(privateKey1, SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256, publicKey2!, dict as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
// bob is calculating the shared secret
guard let shared2 = SecKeyCopyKeyExchangeResult(privateKey2, SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256, publicKey1!, dict as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
print(shared1==shared2)
} else {
// Fallback on earlier versions
print("unsupported")
}
Thanks #Mats for sending me in the right direction..3
Here is the latest code with swift 5 and changes in parameters.
import Security
import UIKit
var error: Unmanaged<CFError>?
let keyPairAttr:[String : Any] = [kSecAttrKeySizeInBits as String: 256,
SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 32,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false],
kSecPublicKeyAttrs as String:[kSecAttrIsPermanent as String: false]]
let algorithm:SecKeyAlgorithm = SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256//ecdhKeyExchangeStandardX963SHA256
do {
guard let privateKey = SecKeyCreateRandomKey(keyPairAttr as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
let publicKey = SecKeyCopyPublicKey(privateKey)
print("public ky1: \(String(describing: publicKey)),\n private key: \(privateKey)\n\n")
guard let privateKey2 = SecKeyCreateRandomKey(keyPairAttr as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
let publicKey2 = SecKeyCopyPublicKey(privateKey2)
print("public ky2: \(String(describing: publicKey2)),\n private key2: \(privateKey2)\n\n")
let shared:CFData? = SecKeyCopyKeyExchangeResult(privateKey, algorithm, publicKey2!, keyPairAttr as CFDictionary, &error)
let sharedData:Data = shared! as Data
print("shared Secret key: \(sharedData.hexEncodedString())\n\n")
let shared2:CFData? = SecKeyCopyKeyExchangeResult(privateKey2, algorithm, publicKey!, keyPairAttr as CFDictionary, &error)
let sharedData2:Data = shared2! as Data
print("shared Secret key 2: \(sharedData2.hexEncodedString())\n\n")
// shared secret key and shared secret key 2 should be same
} catch let error as NSError {
print("error: \(error)")
} catch {
print("unknown error")
}
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
return self.map { String(format: format, $0) }.joined()
}
}
Circa - 2020 and iOS13. In Zohar's code snippet below, also specify a key size in the dictionary before attempting to get the shared secrets.
let dict: [String: Any] = [SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 32]
Or there would be an error saying this.
kSecKeyKeyExchangeParameterRequestedSize is missing
Related
I have a byte array and want to sign it with an Asymmetric private key in Swift code. I have java code below and want same thing in iOS swift.
try {
final java.security.Signature instance = provider == null ?
java.security.Signature.getInstance(algorithm.getJvmName()) :
java.security.Signature.getInstance(algorithm.getJvmName(), provider);
if (signature.getParameterSpec() != null) {
instance.setParameter(signature.getParameterSpec());
}
instance.initSign(key);
instance.update(signingStringBytes);
return instance.sign();
} catch (final NoSuchAlgorithmException e) {
throw new UnsupportedAlgorithmException(algorithm.getJvmName());
} catch (final Exception e) {
throw new IllegalStateException(e);
}
Tried the below code in Swift
public func sign(value: String, base64EncodingOptions:Data.Base64EncodingOptions = []) -> String? {
do {
let keyData = Data(base64Encoded: privateKey)!
let key = SecKeyCreateWithData(keyData as CFData,
[kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,] as NSDictionary, nil)!
var data = value.data(using: .utf8)!
var error: Unmanaged<CFError>?
guard let signedData = SecKeyCreateSignature(key, .rsaSignatureDigestPSSSHA256, data as CFData, &error) as Data?
else {
return nil
}
let signData = signedData as Data
let signedString = signData.base64EncodedString(options: [])
return signedString
}
catch {
//handle error
}
return ""
}
I am using this method to sign a session key:
func signature(sessionKeyBase64: String, privateKey: String) throws -> String? {
let sk = try extractSessionKey(encryptedSessionKeyBase64: sessionKeyBase64)
let privateKeyDer = try SwKeyConvert.PrivateKey.pemToPKCS1DER(privateKey)
let signature = try? CC.RSA.sign(sk, derKey: privateKeyDer, padding: .pkcs15, digest: .sha256, saltLen: 0);
return signature?.base64EncodedString()
}
func extractSessionKey(encryptedSessionKeyBase64: String, privateKey: String) throws -> Data {
let privateKeyDer = try SwKeyConvert.PrivateKey.pemToPKCS1DER(privateKey)
let encryptedData = Data(base64Encoded: encryptedSessionKeyBase64)!
return try CC.RSA.decrypt(encryptedData, derKey: privateKeyDer, tag: Data(), padding: .pkcs1, digest: .sha1).0
}
and a below method to check a signature:
func isVerified(encryptedKeyBase64: String, signatureBase64: String, signerPublicKeyBase64: String) throws -> Bool {
let sk = try extractSessionKey(encryptedSessionKeyBase64: encryptedKeyBase64)
let pk = try SwKeyConvert.PublicKey.pemToPKCS1DER(signerPublicKeyBase64)
let signatureBytes = Data(base64Encoded: signatureBase64)
return (try? CC.RSA.verify( sk, derKey: pk, padding: .pkcs15, digest: .sha256, saltLen: 0, signedData: signatureBytes!))!
}
with the help of the third party library SwCrypt
I am recieving Bad Access when trying to create a signature with xcode SecKeyCreateSignature.
This flows with biometric enrollment in a webview. When the user hits the enrollment page the device sends to the webview the device Id and a public key.
To generate the key I have...
private let tag = "com.CustomTagName.private",
deviceId = UIDevice.current.identifierForVendor!.uuidString,
privateKeyAttr: [NSObject: NSObject] = [
kSecAttrIsPermanent:true as NSObject,
kSecAttrApplicationTag: "com.CustomTagName.private".data(using: .utf8)! as NSObject,
kSecClass: kSecClassKey,
kSecAttrType: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits : 2048 as NSObject,
kSecReturnData: kCFBooleanTrue
],
privateKey : SecKey?,
privateKeyStr = ""
;
...
private func generateKeys() throws {
var err: Unmanaged<CFError>?
do {
guard let prk = SecKeyCreateRandomKey(privateKeyAttr as CFDictionary, &err) else {
throw err!.takeRetainedValue() as Error
}
// After creating a random private key It appears we have to unwrap it...?
guard let unWrappedKey = SecKeyCopyExternalRepresentation(prk, &err) as Data? else {
throw err!.takeRetainedValue() as Error
}
self.privateKeyStr = unWrappedKey.base64EncodedString()
self.privateKey = prk;
} catch {
}
}
From here I use
let publicKey = SecKeyCopyPublicKey(self.privateKey!);
If I was going to call SecKeyCreateSignature the I have no issue. But I dont call the signature until the user needs to login. So I retrieve the key using...
let message = "HereIAm";
let statusPrivateKey = SecItemCopyMatching(privateKeyAttr as CFDictionary, &resultPrivateKey)
if statusPrivateKey != noErr || resultPrivateKey == nil{
fatalError("Error getting private key")
}
self.privateKey = resultPrivateKey as! SecKey?;
self.privateKeyStr = (privateKey as! Data).base64EncodedString()
// Bad Access Error Here \\
guard let signFingerPrint = SecKeyCreateSignature(privateKey!, SecKeyAlgorithm.rsaSignatureMessagePKCS1v15SHA512, message.data(using: .utf8)! as CFData, &err) else {
fatalError("Signing error")
}
I do notice that the SecKey does not need to be unwrapped with SecKeyCopyExternalRepresentation.
I dont understand the difference in the to Sec Keys when the data is the same.
How do I retrieve the private key to that I can create the signature off it?
I found the answer... kinda, In my privateAttrs I have
kSecReturnData: kCFBooleanTrue
and should be
kSecReturnRef: kCFBooleanTrue
Im my case I was returning Data as the type but I needed to return the type of the original reference.
I have created a private key in the Keychain with SecKeyCreateRandomKey. When I attempt to access the key to perform a signing operation, the Touch ID or FaceID dialog will never appear. I get the sign string but without TouchID or FaceID. I tried with BiometryAny and TouchIdAny but it doesn't work.
static func createKey(keyName:String){
DispatchQueue.main.async{
var error : Unmanaged<CFError>?
print("Key is generating for \(keyName)")
let tag = (keyName + "PrivateKey").data(using: .utf8)!
// private key parameters
var privateKeyParams: [String: Any] = [:]
let accessControlError:UnsafeMutablePointer<Unmanaged<CFError>?>? = nil
// ^ Already a 'pointer'
if #available(iOS 10 , *) {
let allocator:CFAllocator! = kCFAllocatorDefault
let protection:AnyObject! = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
let flags:SecAccessControlCreateFlags = SecAccessControlCreateFlags.userPresence
let accessControlRef = SecAccessControlCreateWithFlags(
allocator,
protection,
flags,
accessControlError // <- Notice the lack of '&'
)
privateKeyParams = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag,
kSecAttrAccessControl as String : accessControlRef!,
]
} else {
// Fallback on earlier versions
}
// global parameters for our key generation
let parameters: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String: privateKeyParams
]
if #available(iOS 10.0, *) {
do{
guard let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, nil) else {
print("\(keyName)PrivateKey generator Error!")
throw error!.takeRetainedValue() as Error
}
}
}
}
and signture function:
static func SigntureWithPrivateKey(keyName: String, message : String) -> String {
//print("sign started .........")
guard let messageData = message.data(using: String.Encoding.utf8) else {
print("bad message to sign")
return ""
}
if #available(iOS 10.0, *) {
guard let privateKeyLocal: SecKey = getPrivateKey("\(keyName)PrivateKey") else
{
return ""
}
guard let signData = SecKeyCreateSignature(privateKeyLocal,SecKeyAlgorithm.rsaSignatureDigestPKCS1v15SHA512,messageData as CFData, nil) else {
print("priv ECC error signing")
return ""
}
let convertedSignData = signData as Data
let convertedString = convertedSignData.base64EncodedString()
return convertedString
} else {
return ""
}
}
and getPrivateKey function :
fileprivate static func getPrivateKey(_ name: String) -> SecKey?
{
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrApplicationTag as String: name,
kSecReturnRef as String: true
]
var item: CFTypeRef? = nil
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else
{
if status == errSecUserCanceled
{
print("\tError: Accessing private key failed: The user cancelled (%#).", "\(status)")
}
else if status == errSecDuplicateItem
{
print("\tError: The specified item already exists in the keychain (%#).", "\(status)")
}
else if status == errSecItemNotFound
{
print("\tError: The specified item could not be found in the keychain (%#).", "\(status)")
}
else if status == errSecInvalidItemRef
{
print("\tError: The specified item is no longer valid. It may have been deleted from the keychain (%#).", "\(status)")
}
else
{
print("\tError: Accessing private key failed (%#).", "\(status)")
}
return nil
}
return (item as! SecKey)
}
Sorry your question is quite long so I thought I would give generic answer.
Make sure you have set NSFaceIDUsageDescription in your
info.plist
Without this key, the system won’t allow your app to use Face ID. The
value for this key is a string that the system presents to the user
the first time your app attempts to use Face ID. The string should
clearly explain why your app needs access to this authentication
mechanism. The system doesn’t require a comparable usage description
for Touch ID.
Make sure you have added both Security and LocalAuthentication
framework other than turning on keychain services
You have to specifically set Authentication param in
SecAccessControlCreateWithFlags class(Please go through this clearly it makes lots of difference)
Please find more information along with sample source code here
https://developer.apple.com/documentation/localauthentication/accessing_keychain_items_with_face_id_or_touch_id
Hope this helps.
Do not use the simulator, try it on a real device.
you need to set flag as
let flags:SecAccessControlCreateFlags =
[SecAccessControlCreateFlags.privateKeyUsage, SecAccessControlCreateFlags.touchIDCurrentSet]
I want to Encrypt a string(Plain Text) with my RSA public key. I have a public key, which sent from the server as a String and with that I created a RSA public key. now I want to use that key to Encrypt my text with padding PKACS12. how can I do that. I went through lots of stack overflow questions and I didn't get any success.
this is how I create the RSA public key,
let serverPublicKey = "Some text with key"
let data2 = Data.init(base64Encoded: serverPublicKey)
let keyDict:[NSObject:NSObject] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits: NSNumber(value: 2048),
kSecReturnPersistentRef: true as NSObject
]
let publickeysi = SecKeyCreateWithData(data2! as CFData, keyDict as CFDictionary, nil)
this creates a RSA public key successfully. now I want to use this key to encrypt my another Plain Text. how can I do that.
Hope this will help you:
let serverPublicKey = "Some text with key"
let data2 = Data.init(base64Encoded: serverPublicKey)
let keyDict:[NSObject:NSObject] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits: NSNumber(value: 2048),
kSecReturnPersistentRef: true as NSObject
]
let publickeysi = SecKeyCreateWithData(data2! as CFData, keyDict as CFDictionary, nil)
//Encrypt a string with the public key
let message = "This is my message."
let blockSize = SecKeyGetBlockSize(publickeysi!)
var messageEncrypted = [UInt8](repeating: 0, count: blockSize)
var messageEncryptedSize = blockSize
var status: OSStatus!
status = SecKeyEncrypt(publickeysi!, SecPadding.PKCS1, message, message.characters.count, &messageEncrypted, &messageEncryptedSize)
if status != noErr {
print("Encryption Error!")
return
}
import Foundation
import Security
struct RSA {
static func encrypt(string: String, publicKey: String?) -> String? {
guard let publicKey = publicKey else { return nil }
let keyString = publicKey.replacingOccurrences(of: "-----BEGIN RSA PUBLIC KEY-----\n", with: "").replacingOccurrences(of: "\n-----END RSA PUBLIC KEY-----", with: "")
guard let data = Data(base64Encoded: keyString) else { return nil }
var attributes: CFDictionary {
return [kSecAttrKeyType : kSecAttrKeyTypeRSA,
kSecAttrKeyClass : kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits : 2048,
kSecReturnPersistentRef : true] as CFDictionary
}
var error: Unmanaged<CFError>? = nil
guard let secKey = SecKeyCreateWithData(data as CFData, attributes, &error) else {
print(error.debugDescription)
return nil
}
return encrypt(string: string, publicKey: secKey)
}
static func encrypt(string: String, publicKey: SecKey) -> String? {
let buffer = [UInt8](string.utf8)
var keySize = SecKeyGetBlockSize(publicKey)
var keyBuffer = [UInt8](repeating: 0, count: keySize)
// Encrypto should less than key length
guard SecKeyEncrypt(publicKey, SecPadding.PKCS1, buffer, buffer.count, &keyBuffer, &keySize) == errSecSuccess else { return nil }
return Data(bytes: keyBuffer, count: keySize).base64EncodedString()
}
}
Use like this
var pemString = "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl/zjMK4w1XZAnpIqLeTAMW7cEUNIifP3HjmUavvc2+oPG1QjNCfxQM6LulZSl6qRim2JGxbc3yvnbMRJqch6IhJ/ysbTekVSqOjskIRGxq0pg0J8PqF3ZZQK6D7BYHi6iaJUMVV0ISB5LogJouyOWqsZyiEjgPz3jj0HIrh14Q6wPZVMpVbIwQR9nZp5gU5minseCyZfQs3PArgXgnzRPdw7Hb0/NY5OVE2Rz1SFTnda6w12SEu1IsVhVhJz1QteNrwNwJAT6WgZd+xnOZhU3Ei+EQK2SijfEGqmWNt1utJygK/0APy8w7VTol7ygbqfuHevGcg90QEXjxZKCjkXkQIDAQAB\n-----END RSA PUBLIC KEY-----"
let password = "abcdefg"
let encryptedPassword = RSA.encrypt(string: password, publicKey: pemString)
print(encryptedPassword)
I am trying to sync up private asymmetric keys between my iOS app and its watchOS equivalent. I have tried using SecKeyCopyExternalRepresentation to export it out as CFData and then send it to the watch using WatchConnectivity. However when it gets to the watch I have no way of converting the Data back into a SecKey. I tried using SecKeyCreateWithData in an attempt to recreate it, but it seems that that only works with symmetric keys, for when I tried it it crashed the watch app. Any ideas?
iOS Code:
func sendSharedKeyPair(keyPair: (publicKey: SecKey, privateKey: SecKey)) {
var error: Unmanaged<CFError>?
let publicKeyData = SecKeyCopyExternalRepresentation(keyPair.publicKey, &error)
if let error = error {
return print("Error sending shared key: \(error)")
}
let privateKeyData = SecKeyCopyExternalRepresentation(keyPair.privateKey, &error)
if let error = error {
return print("Error sending shared key: \(error)")
}
if let publicKeyData = publicKeyData, let privateKeyData = privateKeyData {
session.sendMessage(["requestedCommand": WatchControllerCommands.sendSharedKeyPair.rawValue, "keyPair": ["publicKey": publicKeyData, "privateKey": privateKeyData]], replyHandler: nil, errorHandler: { error in
print(error)
})
}
}
watchOS Code:
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
guard let requestedCommand = (message["requestedCommand"] as? String).flatMap({ WatchControllerCommands(rawValue: $0) }), requestedCommand == .sendSharedKeyPair else { return }
guard let publicKeyData = (message["keyPair"] as? [String: Any])?["publicKey"].flatMap({ $0 as? Data }), let privateKeyData = (message["keyPair"] as? [String: Any])?["privateKey"].flatMap({ $0 as? Data }) else { return print("Couldn't parse keys") }
let publicTag = "myAppTag"
let privateTag = publicTag + ".private"
let privateAttributes = [String(kSecAttrIsPermanent): true,
String(kSecAttrApplicationTag): privateTag] as [String : Any]
let publicAttributes = [String(kSecAttrIsPermanent): true,
String(kSecAttrApplicationTag): publicTag] as [String : Any]
var error: Unmanaged<CFError>?
let publicCFData = publicKeyData as CFData
let privateCFData = privateKeyData as CFData
let publicCFDict = publicAttributes as CFDictionary
let privateCFDict = privateAttributes as CFDictionary
SecKeyCreateWithData(publicCFData, publicCFDict, &error)
if let error = error {
print(error)
}
SecKeyCreateWithData(privateCFData, privateCFDict, &error)
if let error = error {
print(error)
}
}
From headerdocs around SecKeyCreateWithData:
#param attributes Dictionary containing attributes describing the key
to be imported. The keys in this dictionary are kSecAttr* constants
from SecItem.h. Mandatory attributes are: * kSecAttrKeyType *
kSecAttrKeyClass * kSecAttrKeySizeInBits
Your code only defines kSecAttrIsPermanent and kSecAttrApplicationTag attributes.