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
}
CodePudding user response:
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.