Elliptic Curve Diffie Hellman in ios/swift
Asked Answered
H

4

13

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

Hauger answered 19/9, 2017 at 13:1 Comment(5)
Have you had a look at SecKeyCopyKeyExchangeResult?Shepperd
@Shepperd thanks - I did, and it seems relevant, but unfortunately the documentation is so poor and cryptic that it's hard to tell how to use it and if it does what I want it to do.Hauger
The SecKey.h header file may have some additional info that is not in the online documentation.Shepperd
@Shepperd I got it. Finally. Thanks for your help.Hauger
If you have the time, please consider writing a more elaborate answer to your question. It could be a small code example demonstrating how you use the API.Shepperd
H
31

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

Hauger answered 21/9, 2017 at 10:59 Comment(3)
Is there any way to compute public and private for specific domain parameter ('p', 'b' and 'a')Tendril
How can i crypt and decrypt messages using the shared key data? The function SecKeyCreateEncryptedData asks a SecKey as parameter...but i get dataSneer
@Zohar Etzioni can I use SecKeyCopyKeyExchangeResult for public JWK keys ? and if so is there any example code ?Rameriz
F
6

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()
    }
}
Funest answered 3/8, 2020 at 5:54 Comment(0)
H
2

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

Helico answered 9/7, 2020 at 19:23 Comment(0)
F
0

You can achieve the same thing with CryptoKit (iOS13+)

import CryptoKit
import Foundation

var protocolSalt = "Hello, playground".data(using: .utf8)!


// generate key pairs
let sPrivateKey = Curve25519.KeyAgreement.PrivateKey()
let sPublicKey = sPrivateKey.publicKey


let rPrivateKey = Curve25519.KeyAgreement.PrivateKey()
let rPublicKey = rPrivateKey.publicKey

// sender derives symmetric key
let sSharedSecret = try! sPrivateKey.sharedSecretFromKeyAgreement(with: rPublicKey)
let sSymmetricKey = sSharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self,
                                                          salt: protocolSalt,
                                                          sharedInfo: Data(),
                                                          outputByteCount: 32)

let sSensitiveMessage = "The result of your test is positive".data(using: .utf8)!

// sender encrypts data
let encryptedData = try! ChaChaPoly.seal(sSensitiveMessage, using: sSymmetricKey).combined

// receiver derives same symmetric key
let rSharedSecret = try! rPrivateKey.sharedSecretFromKeyAgreement(with: sPublicKey)
let rSymmetricKey = rSharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self,
                                                          salt: protocolSalt,
                                                          sharedInfo: Data(),
                                                          outputByteCount: 32)

// receiver decrypts data
let sealedBox = try! ChaChaPoly.SealedBox(combined: encryptedData)
let decryptedData = try! ChaChaPoly.open(sealedBox, using: rSymmetricKey)
let rSensitiveMessage = String(data: decryptedData, encoding: .utf8)!

// assertions
sSymmetricKey == rSymmetricKey
sSensitiveMessage == decryptedData

Source

Article

Fetishist answered 9/2 at 11:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.