Matthieu Bouxin
Matthieu Bouxin

Reputation: 127

SecKeyCopyKeyExchangeResult does not work as nodejs ecdh in iOS 13

I have an application where I generate shared secret between iOS / android / nodejs using ECDH over secp521r1 curve. To ensure that it works I wrote a test that uses data generated with nodejs and that worked until iOS 13.

The source code of the test was:

let publicKey = "BABBvZ56c4bj1Zo73LIt/bBVa3jvGTA1fceoOG/M9TeXHx5ffCggRteEVS+bwrgQWPOwJPHhevNenaVn32ZnhztS0QFBqKGZTF1pKNSvuj+PDKQ625TauNroq+LQdeS+Pn6GVHL0iW5pp84NZ06L97VZ9HYm+g2lMnlUFV8hco2CmwBqHQ=="
let privateKey = "AXn994UN59QCEqmCmXmmNZ3hVZPlMwzTIeBupJGG4CqDWfWLuCTui7qiBfQtCFcQ1ks4NNB/tHEZUJ+bB97+pkJ3"
let otherBase64 = "BAAzWyzdh2e+ZNUCFt4oDADURb8+m9WA7gbWtTo57ZP3U23VuvMnRHf+12GpTSV8A5pt+vZfaR2cT02P+LPRc/kGzgAT2IYIgDz/cKbzMi520ZLa0GYk1xzCuNqFhdBZmrB5w0ymsPLdJzIG1QZ3xu7OufEipm5D41abphLLnbH+OyTX6w=="
let expectedShared = "AQkTOOHPcvlXufR2dm1FHaIJRlTgmxTJMI+h0kJ+nMVNopIP+opSqUNmflsgnJzT8JTodd/eehaaq5vvYdDVciIQ"

// iOS secKey is reconstructed by concatenating public and private key
let otherDataKey = Data.init(base64Encoded: otherBase64)!
var concatenatedKey = Data.init(base64Encoded: publicKey)!
concatenatedKey.append(Data.init(base64Encoded: privateKey)!)

// generate private key
var attributes: [String:Any] =
  [
    kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
    kSecAttrKeySizeInBits as String:      521,
    kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
]
var error: Unmanaged<CFError>?
guard let secKey = SecKeyCreateWithData(concatenatedKey as CFData, attributes as CFDictionary, &error) else {
  XCTAssertTrue(false)
  return
}

// generate other public key
attributes[kSecAttrKeyClass as String] = kSecAttrKeyClassPublic
guard let otherKey = SecKeyCreateWithData(otherDataKey as CFData, attributes as CFDictionary, nil) else {
  XCTAssertTrue(false)
  return
}

// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256, otherKey, exchangeOptions as CFDictionary, &error) else {
  XCTAssertTrue(false)
  return
}

// generate shared secret
XCTAssertEqual((shared as Data).base64EncodedString(), expectedShared);

With iOS 13 I was forced to modify the content of my exchangeOptions dictionary as discussed here (SecKeyCopyKeyExchangeResult() function return an error, "kSecKeyKeyExchangeParameterRequestedSize is missing")

let exchangeOptions: [String: Any] = [SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 66]

The problem is that with this option, the result of SecKeyCopyKeyExchangeResult does not match anymorewith nodejs one (which is also true on iOS 12)

Upvotes: 1

Views: 590

Answers (1)

Matthieu Bouxin
Matthieu Bouxin

Reputation: 127

I finally found a solution... In iOS <= 12, leaving exchange parameters empty when trying to use ecdhKeyExchangeStandardX963SHA256 algorithm was falling back to using SecKeyAlgorithm.ecdhKeyExchangeCofactor.

Therefore the fix to reproduce previous behavior is to modify the SecKeyCopyKeyExchangeResult with

// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeCofactor, otherKey, exchangeOptions as CFDictionary, &error) else {
  XCTAssertTrue(false)
  return
}

This works at least for iOS 10 to 13

Upvotes: 4

Related Questions