0

I'm trying to read a public key (x509 format encoded) from a Java Server to finalize my Elliptic Curve Diffie Hellman Exchange. I can send the Public Key to the server with no problem, but now I want to read the public Key that the server has sent to the iOS Client.

byte[] serverPubKeyEnc = serverKpair.getPublic().getEncoded(); (This is on the server)

This is what I return to the iOS side. For me to deal with it, I need to read it from the input stream and then turn it into a usable public key. This is what I have right now on the iOS side of things to read the key:

 var error: Unmanaged<CFError>? = nil
    
    let mutableData = CFDataCreateMutable(kCFAllocatorDefault, CFIndex(0))
        if mutableData != nil
        {
            let headerSize = 26
         
            //For importing Java key data
            CFDataAppendBytes(mutableData, CFDataGetBytePtr(data as CFData), CFDataGetLength(data as CFData))
            CFDataDeleteBytes(mutableData, CFRangeMake(CFIndex(0), headerSize))

            //Use the mutableData here (SecKeyCreateWithData)
            let publicKey = SecKeyCreateWithData(
                mutableData!,
                [
                    kSecAttrKeyType: kSecAttrKeyTypeEC,
                    kSecAttrKeyClass: kSecAttrKeyClassPublic,
                ] as NSDictionary,
                &error)
            
            let fullKey = SecKeyCopyExternalRepresentation(publicKey!, &error)
            
            return fullKey!
        }

Here I can read the "publicKey", I know it has some value inside it. How can I turn this into a usable key for me to generate the shared secret?

TLDR: I want to read a public key that comes from the Java server (ECDH) to generate a symmetric key for encryption in the iOS client.

JCCA
  • 5
  • 2

1 Answers1

3

The complete flow would look like:

  • receiving 91 bytes with the public key from server side
  • create a SecKey with SecKeyCreateWithData
  • create a key pair on iOS with SecKeyCreateRandomKey
  • send own public key to server side
  • server side can compute the shared secret with that information
  • client computes a shared secret with SecKeyCopyKeyExchangeResult
  • if everything is correct, it should give the same shared secret on iOS and Java side

Therefore, to get a complete test case, one can write a Java program that generates a key pair. For simplicity, one can copy/paste the public key between the Java and iOS app for a test instead of using a network connection. The Java program writes the public key to the console. This key is copied into the Swift source code. The Swift program is compiled and generates a key pair as well. The public key is copied / pasted to the Java program, which reads it on the console. Both programs then output the calculated shared secret, which should be the same for obvious reasons, since it is used for further symmetric encryption.

This fine answer https://stackoverflow.com/a/26502285/2331445 provides utility methods for converting a hex String to Data and back.

iOS Swift Code

The following code assumes that a secp256r1 curve is used with a key size of 256 bit.

The described flow could be implemented as follows:

    let otherKey = "3059301306072a8648ce3d020106082a8648ce3d03010703420004df96b3c0c651707c93418781b91782319f6e798550d954c46ac7318c7eac130f96380991a93049059e03e4190dd147b64d6ebc57320938f026844bda3de22352".hexadecimal!
    
    guard let otherPublicKey = otherPublicKey(data: otherKey) else { return }
    guard let ownPrivateKey = createOwnKey() else { return }
    guard let ownPublicKey = SecKeyCopyPublicKey(ownPrivateKey) else { return }
    
    send(ownPublicKey: ownPublicKey)
    
    if let sharedSecret = computeSharedSecret(ownPrivateKey: ownPrivateKey, otherPublicKey: otherPublicKey) {
        print("shared secret: \(sharedSecret.hexadecimal)")
    } else {
        print("shared secret computation failed")
    }
    

The used functions:

private func otherPublicKey(data: Data) -> SecKey? {
    var error: Unmanaged<CFError>? = nil
    
    let cfData = data.dropFirst(26) as CFData
    
    let attributes =  [
        kSecAttrKeyType: kSecAttrKeyTypeEC,
        kSecAttrKeyClass: kSecAttrKeyClassPublic,
    ] as CFDictionary
    
    if let publicKey = SecKeyCreateWithData(cfData, attributes, &error) {
        return publicKey
    }
    print("other EC public: \(String(describing: error))")
    return nil
}

private func createOwnKey() -> SecKey? {
    var error: Unmanaged<CFError>? = nil
    let keyPairAttr: [String : Any] = [kSecAttrKeySizeInBits as String: 256,
                                       kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
                                       kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false]
    ]
    guard let key = SecKeyCreateRandomKey(keyPairAttr as CFDictionary, &error) else {
        print("key creation: \(String(describing: error))")
        return nil
    }
    return key
}

This function send only outputs the key in hex on the debug console. For the test it can be transferred to the Java program via copy/paste. In a real program it would be transferred to the server via a network connection.

private func send(ownPublicKey: SecKey) {
    guard let data = SecKeyCopyExternalRepresentation(ownPublicKey, nil) as Data? else {
        print("SecKeyCopyExternalRepresentation failed")
        return
    }
    let secp256r1Header = "3059301306072a8648ce3d020106082a8648ce3d030107034200"
    let pkWithHeader = secp256r1Header + data.hexadecimal
    print("ownPublicKeyHexWithHeader \(pkWithHeader.count / 2) bytes: " + pkWithHeader)
}

With the own private key and the public key of the server, the shared secret can be computed.

private func computeSharedSecret(ownPrivateKey: SecKey, otherPublicKey: SecKey) -> Data? {
    let algorithm:SecKeyAlgorithm = SecKeyAlgorithm.ecdhKeyExchangeStandard
    let params = [SecKeyKeyExchangeParameter.requestedSize.rawValue: 32, SecKeyKeyExchangeParameter.sharedInfo.rawValue: Data()] as [String: Any]
    
    var error: Unmanaged<CFError>? = nil
    if let sharedSecret: Data = SecKeyCopyKeyExchangeResult(ownPrivateKey, algorithm, otherPublicKey, params as CFDictionary, &error) as Data? {
        return sharedSecret
    } else {
        print("key exchange: \(String(describing: error))")
    }
    return nil
}

Test

In the upper area you can see the Xcode console and in the lower area the output of the Java program. The common secret is the same. So the test was successful.

Test ECDH Java/Swift

Stephan Schlecht
  • 26,556
  • 1
  • 33
  • 47
  • Wow, thank you so much for the detailed answer!!! Really appreciate it! I managed to understand most of the code you posted here, but I still have a problem with how I read the iOS public key on the server side. Right now I'm doing: new X509EncodedKeySpec(Base64.getDecoder().decode(clientPubKeyEnc)); This basically means that it's expecting a 64 encoded byte array of the public key (as it gives InvalidKeyException). – JCCA Apr 02 '21 at 03:59
  • My example above uses hex encoding, not Base64. Hex decoding could be done on the Java side using, for example, the org.apache.commons.codec library. Then it would look like this: `new X509EncodedKeySpec(Hex.decodeHex(clientPubKeyEnc))`. Alternatively, on the Swift side, you could send the key to the server using base64 encoding. – Stephan Schlecht Apr 02 '21 at 09:17
  • Hello there, sorry for the late reply (easter weekend got in the way), but I already tried by encoding base64 my data, appending it to the header. Basically I send "let pkWithHeader = secp256r1Header + data.base64EncodedString()". On the server side of things I receive it with X509EncodedKeySpec var = new "X509EncodedKeySpec(Base64.getDecoder().decode(clientPubKeyEnc));" . This always gives me InvalidKeyException. Am I missing something? Thank you very much for your help! – JCCA Apr 06 '21 at 01:32
  • Ok, managed to fix it by using hexadecimal encoding. There was some problems on the server side but I managed to fix it. Now I just need to check if the shared secret is the same on both. Thank you once again for your help! Really appreciate it! – JCCA Apr 06 '21 at 02:33