Creating shared secret using public and private keys using swift

639 views Asked by At

I am trying to convert an ephemeralKey which is a series of bytes to an Elliptic Curve public key and then use it to create a shared key using my private key.

I know how to do this in python (code below). But I cannot find a way to do this in Swift. My Swift code is also copied below but it is not correct. Do you see the problem with my Swift code?

My python code:

from cryptography.hazmat.primitives.asymmetric import ec

privateKey = ec.generate_private_key(ec.SECP256R1())
devicePublicKey = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), ephemeralKey)
sharedKey = privateKey.exchange(ec.ECDH(), devicePublicKey)

My Swift code:


let privateKeyParams: [String: Any] = [
    kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
    kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeySizeInBits as String: 256
            ]
var error: Unmanaged<CFError>?
let privateKey = SecKeyCreateRandomKey(privateKeyParams as CFDictionary, &error)
let attributes: [String:Any] =
        [
            kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrKeySizeInBits as String: 256,
        ]
let devicePublicKey = SecKeyCreateWithData(ephemeralKey as CFData, attributes as CFDictionary, nil)!
let sharedKey = ecdhSecretCalculation(publicKey: devicePublicKey, privateKey: privateKey) 

func ecdhSecretCalculation(publicKey: SecKey, privateKey: SecKey) -> Data? {
        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 = .ecdhKeyExchangeStandardX963SHA256
        
        let shared = SecKeyCopyKeyExchangeResult(privateKey, algorithm, publicKey, keyPairAttr as CFDictionary, &error) as Data?
        
        return shared
}

1

There are 1 answers

0
Topaco On BEST ANSWER

The reason for the different shared secrets is that ecdhKeyExchangeStandardX963SHA256 is applied in the posted Swift code. Instead, ecdhKeyExchangeStandard must be applied.

Also, bear in mind that SecKeyCreateWithData() in the Swift code expects the private key to be the concatenation of the uncompressed public key (0x04|x|y) and the raw private key: 0x04|x|y|private, while in the Python code only the raw private key is specified (this is only for the sake of completeness, since the posted codes do not show how the private key was imported).

If this is taken into account, both codes provide the same shared secret.


Test:

Python:

from cryptography.hazmat.primitives.asymmetric import ec
import base64

ephemeralKey = base64.b64decode("BK/Rckcy/PFSkdMCIYg6/CRTk3eSv4VQ2+7sNFQ8TjO1aqK5Wmb+UgjDPQ5cB1QGccATrtmgPDRnPzR5JCMPs/M=")
devicePublicKey = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), ephemeralKey)

privateKeyRaw = base64.b64decode("v5PriRLFXqqILYFZkX2LWEbUQ/y/NCZXD6il5S6KPus=")
privateKey = ec.derive_private_key(int.from_bytes(privateKeyRaw, 'big'), ec.SECP256R1())

sharedKey = privateKey.exchange(ec.ECDH(), devicePublicKey)
print(base64.b64encode(sharedKey)) # 8bOrCLEe1eCciEDbq710xbSXQnKzvSHnVjnXnhpJImE=

Swift:

import Foundation

let keyStringPub = "BK/Rckcy/PFSkdMCIYg6/CRTk3eSv4VQ2+7sNFQ8TjO1aqK5Wmb+UgjDPQ5cB1QGccATrtmgPDRnPzR5JCMPs/M="
let ephemeralKey = Data(base64Encoded: keyStringPub)!
let devicePublicKey = SecKeyCreateWithData(ephemeralKey as CFData, [kSecAttrKeyClass as String: kSecAttrKeyClassPublic, kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256] as CFDictionary, nil)!

let keyStringPriv = "BK/Rckcy/PFSkdMCIYg6/CRTk3eSv4VQ2+7sNFQ8TjO1aqK5Wmb+UgjDPQ5cB1QGccATrtmgPDRnPzR5JCMPs/O/k+uJEsVeqogtgVmRfYtYRtRD/L80JlcPqKXlLoo+6w=="
let privateKeyRaw = Data(base64Encoded: keyStringPriv)!
let privateKey = SecKeyCreateWithData(privateKeyRaw as CFData, [kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256,] as CFDictionary, nil)!

let sharedKey = ecdhSecretCalculation(publicKey: devicePublicKey, privateKey: privateKey) 
let sharedKeyB64 = sharedKey!.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
print(sharedKeyB64) // 8bOrCLEe1eCciEDbq710xbSXQnKzvSHnVjnXnhpJImE=

func ecdhSecretCalculation(publicKey: SecKey, privateKey: SecKey) -> Data? {
        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 = .ecdhKeyExchangeStandard // Fix!
        let shared = SecKeyCopyKeyExchangeResult(privateKey, algorithm, publicKey, keyPairAttr as CFDictionary, &error) as Data?
        return shared
}

Both codes provide the same shared secret.


If the ecdhKeyExchangeStandardX963SHA256 algorithm is used in the Swift code, the shared secret is additionally processed with ANSI-X9.63-KDF. The Cryptography library also provides this X963KDF:

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF
import base64

xkdf = X963KDF(
    algorithm=hashes.SHA256(),
    length=32,
    sharedinfo=None,
)
key = xkdf.derive(sharedKey)
print(base64.b64encode(key)) # ULQCW4bsStNQGg/avnlqaNALpAy2Z42SBZN97Qlnqa0=

For the reasons why a KDF is generally used afterwards, see e.g. here. Note that the X9.63-KDF is only one possibility, there are others, see e.g. here.