【问题标题】:Reading Public Key Sent by Java Server in Swift在 Swift 中读取 Java 服务器发送的公钥
【发布时间】:2021-04-01 06:50:31
【问题描述】:

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

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

这就是我回到 iOS 端的内容。为了处理它,我需要从输入流中读取它,然后将其转换为可用的公钥。这就是我现在在 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) 的公钥,以在 iOS 客户端中生成用于加密的对称密钥。

【问题讨论】:

    标签: java swift encryption ecdh public-key-exchange


    【解决方案1】:

    完整的流程如下所示:

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

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

    这个很好的答案https://stackoverflow.com/a/26502285/2331445 提供了将十六进制字符串转换为数据并返回的实用方法。

    iOS Swift 代码

    以下代码假定使用 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 仅在调试控制台上以十六进制输出密钥。对于测试,可以通过复制/粘贴将其传输到 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)
    }
    

    有了自己的私钥和服务器的公钥,就可以计算出共享的秘密。

    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 程序的输出。共同的秘密是相同的。所以测试成功了。

    【讨论】:

    • 哇,非常感谢您的详细回答!!!真的很感激!我设法理解了您在此处发布的大部分代码,但我在如何读取服务器端的 iOS 公钥方面仍然存在问题。现在我正在做: new X509EncodedKeySpec(Base64.getDecoder().decode(clientPubKeyEnc));这基本上意味着它需要一个 64 字节编码的公钥数组(因为它给出了 InvalidKeyException)。
    • 我上面的示例使用十六进制编码,而不是 Base64。可以使用例如 org.apache.commons.codec 库在 Java 端完成十六进制解码。然后它看起来像这样:new X509EncodedKeySpec(Hex.decodeHex(clientPubKeyEnc))。或者,在 Swift 端,您可以使用 base64 编码将密钥发送到服务器。
    • 你好,很抱歉回复晚了(复活节周末妨碍了我),但我已经尝试过对我的数据进行 base64 编码,并将其附加到标题中。基本上我发送“let pkWithHeader = secp256r1Header + data.base64EncodedString()”。在服务器端,我使用 X509EncodedKeySpec var = new "X509EncodedKeySpec(Base64.getDecoder().decode(clientPubKeyEnc));" 接收它.这总是给我 InvalidKeyException。我错过了什么吗?非常感谢您的帮助!
    • 好的,设法通过使用十六进制编码来修复它。服务器端出现了一些问题,但我设法解决了它。现在我只需要检查两者的共享密钥是否相同。再次感谢您的帮助!真的很感激!
    猜你喜欢
    • 1970-01-01
    • 2019-10-31
    • 2023-04-09
    • 2021-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-02
    • 2019-05-10
    相关资源
    最近更新 更多