I'm converting a method that I have working in both NodeJS/Java to Swift but I'm having trouble getting it to work.
Hoping someone can help me covert this to Swift
NodeJS Code:
//the public_key param here is from a different device.
sign: function(public_key)
{
//dummy values
var PRE_SALT_VALUE = 'f0f0f0f0f0';
var POST_SALT_VALUE = '0101010101';
const crypto = require('crypto');
var sha512 = crypto.createHash("sha512");
var EC = require('elliptic').ec;
var ec = new EC('p256');
// Generate keys
var key1 = ec.genKeyPair(); //key1 is gen before pub key
var key2 = ec.keyFromPublic(public_key, 'hex') //pub key gen from saved cert
var derived_secret = key1.derive(key2.getPublic());
var derived_secret = Buffer.from(derived_secret.toString(16), 'hex')
var public_key_client = key1.getPublic('hex')
var pre_salt = Buffer.from(PRE_SALT_VALUE, 'hex')
var post_salt = Buffer.from(POST_SALT_VALUE, 'hex')
derived_secret = Buffer.from(pre_salt.toString('hex')+derived_secret.toString('hex')+post_salt.toString('hex'), 'hex') // finalyze shared secret
// Hash shared secret
var sha = sha512.update(derived_secret);
derived_secret = sha.digest();
return {
public_key: public_key_client.toString('hex').slice(2), //dropping first byte of compression bits
secret: derived_secret.toString('hex')
}
}
The same code is also working in Java: See the answer to a similar question I asked a few years ago here if you would like to see the java implementation. This was what originally helped me port it to java.
How do I add the same logic in Swift (I am a Swift beginner). So far I have tried
Step 1
// certData will be the external cert byte array in der format
let certData = Data(bytes: self.cert!, count: self.cert_length!)
// load the cert so I can read the data
guard let certificate = SecCertificateCreateWithData(nil, certData as CFData) else {
// todo handle error
print("explode")
return
}
// log public key from cert
let publicKey = SecCertificateCopyKey(certificate)!
let pubKeyExternRep = SecKeyCopyExternalRepresentation(publicKey, nil)
let pubKeyExternRepData:Data = pubKeyExternRep! as Data
print(pubKeyExternRepData.bytes) // printed pub key looks reasonable
Step 2 Next I have to generate a new random public/private keypair, so I tried this
// Attempt 1 - generate our own random public/private keypair
let attributes: [String: Any] = [
kSecAttrType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true
]
]
var error: Unmanaged<CFError>?
let ouPrivateKeyAttempt1 = SecKeyCreateRandomKey(attributes as CFDictionary, &error)!
let ourPubKeyAttempt1 = SecKeyCopyPublicKey(ouPrivateKeyAttempt1)
let ourPubKeyExternRep = SecKeyCopyExternalRepresentation(ourPubKeyAttempt1!, &error)
print("our generated EC public key (65 bytes)")
print("\(pubKeyExternRep!)")
// =-=-=--=-=-=-=-==--==-=-=-=- End attempt1
Step 3 Next I have to derive the shared secret given the random keypair above the the public external cert. I am pretty lost at this point. I tried something like
// our private key agreement (does this generate a new private key, guess we dont need 'Attempt1' above?)
let ourPrivateKey = P256.KeyAgreement.PrivateKey()
let ourPubKey = ourPrivateKey.publicKey
// external device public key agreement given our generated private agreement
let externalPubKeyAg = try! P256.KeyAgreement.PublicKey(
rawRepresentation: pubKeyExternRepData.bytes) // <-- var from step 1, crashes here
// get shared secret
let sharedSecret = try! ourPrivateKey.sharedSecretFromKeyAgreement(
with: externalPubKeyAg)
print(sharedSecret)
The above will crash with CryptoKit.CryptoKitError.incorrectParameterSize
.
Anyway, hoping someone can help me convert the function that's in NodeJS/Java above into Swift. Thanks! :)
The result should be a Swift method that gets a public key in hex, internally generates its own key pair, and determines the shared secret in the same way as the Node program.
Test Scenario
A test could then look like the following:
If the two determined shared secrets match, the test was successful.
This fine answer https://stackoverflow.com/a/26502285/2331445 provides utility methods for converting a hex String to Data and back and are used in the test.
Swift
Step 1 could be the calculation of the common secret (without pre- and post-salting):
Since
sharedSecretFromKeyAgreement
return a structSharedSecret
that conforms to the protocolContiguousBytes
we can define an extension like this:The private key could be created like this:
And here we call it and apply pre- and postSaltValue, convert it to data and calculate the SHA512 digest:
Finally the whole program for an easy test on iOS:
Test
In the upper area is the Node program, in the lower area you can see the console of Xcode with the Swift program:
The shared secrets are identical, so the test was successful.
Please note: Like the Node program, the Swift program expects an "04" prefix at the beginning of the other public key, indicating that it is an uncompressed key. This must be added if required.