在 Swift 中读取 Java 服务器发送的 Public 密钥

Reading Public Key Sent by Java Server in Swift

我正在尝试从 Java 服务器读取 public 密钥(x509 格式编码)以完成我的椭圆曲线 Diffie Hellman Exchange。我可以毫无问题地将 Public 密钥发送到服务器,但现在我想读取服务器已发送到 iOS 客户端的 public 密钥。

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

这是我return给iOS的一面。为了处理它,我需要从输入流中读取它,然后将它变成可用的 public 键。这就是我现在在 iOS 方面要阅读的关键:

 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!
        }

在这里我可以读到“publicKey”,我知道它里面有一些价值。我怎样才能把它变成一个可用的密钥来生成共享密钥?

TLDR:我想读取来自 Java 服务器 (ECDH) 的 public 密钥,以生成用于在 iOS 客户端中加密的对称密钥。

完整的流程如下所示:

  • 从服务器端使用 public 密钥接收 91 个字节
  • 使用 SecKeyCreateWithData 创建一个 SecKey
  • 使用 SecKeyCreateRandomKey
  • 在 iOS 上创建密钥对
  • 将自己的 public 密钥发送到服务器端
  • 服务器端可以使用该信息计算共享密钥
  • 客户端使用 SecKeyCopyKeyExchangeResult
  • 计算共享密钥
  • 如果一切正确,它应该在 iOS 和 Java 端给出相同的共享密钥

因此,要得到一个完整的测试用例,可以编写一个Java生成密钥对的程序。为简单起见,可以 copy/paste Java 和 iOS 应用程序之间的 public 键进行测试,而不是使用网络连接。 Java 程序将 public 键写入控制台。此密钥被复制到 Swift 源代码中。 Swift 程序被编译并生成一个密钥对。 public 键被复制/粘贴到 Java 程序,该程序在控制台上读取它。然后两个程序都输出计算出的共享秘密,由于显而易见的原因,它应该是相同的,因为它用于进一步的对称加密。

这个很好的答案 提供了将十六进制字符串转换为数据并返回的实用方法。

iOSSwift代码

以下代码假定使用 secp256r1 曲线,密钥大小为 256 位。

描述的流程可以实现如下:

    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")
    }
    

使用的函数:

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
}

此函数send 仅在调试控制台上以十六进制输出密钥。对于测试,它可以通过 copy/paste 传输到 Java 程序。在实际程序中,它将通过网络连接传输到服务器。

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)
}

利用自己的私钥和服务器的public密钥,就可以计算出共享密钥。

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
}

测试

在上部区域您可以看到 Xcode 控制台,在下部区域可以看到 Java 程序的输出。共同的秘密是一样的。所以测试成功。