Swift RSA 加密 Public Java 服务器的密钥失败

Swift RSA Encryption Public Key to Java Server is failing

我正在尝试使用 Security 框架从 RSA Private key 创建 public base64 密钥 。这是片段。

let tag = "com.example.keys.mykey"
public extension SecKey {
    static func generateBase64Encoded2048BitRSAKey() throws -> (private: String, public: String) {
        let type = kSecAttrKeyTypeRSA
        let attributes: [String: Any] =
            [kSecAttrKeyType as String: type,
             kSecAttrKeySizeInBits as String: 2048
        ]

        var error: Unmanaged<CFError>?
        guard let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error),
            let data = SecKeyCopyExternalRepresentation(key, &error) as Data?,
            let publicKey = SecKeyCopyPublicKey(key),
            let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
                throw error!.takeRetainedValue() as Error
        }
        return (private: data.base64EncodedString(), public: publicKeyData.base64EncodedString())
    }
}

do {
    let (pvtKey, pubKey) = try SecKey.generateBase64Encoded2048BitRSAKey()
    print(pubKey)
} catch let error {
    print(error)
}

这是输出

MIIBCgKCAQEA1ZafTYboquQbCTZMEb1IqHKIr8wiDjdn6e0toRajZCQo9W5zuTlEuctrjJJQ08HcOuK3BPFRaFTUP1RBFvnba/T2S1Mc6WVX81b0DmKS8aPJ83TvvQlH3bZjVqFzndXJHJatcXRkZKlbidNQYxV9OYFCRLwgR5PBoJ1P5tp8f8735vIADOBL/93nFywODSjAWLXcyG5tUyRlRGX7eDodL7jqVOFxVMB7K9UOJehPuJQiheykyPSbBSLE6raZbpCHlranTLdihWYFs2tYbxzNrVbXzgKIxDDjrhDLVFvo3beudKQcLQkSO+m2LJIDT91zAnxVQ075AIn80ZHh5kdyQQIDAQAB

但是这个 public 密钥没有被我们的 Java 服务器接受。它正在抛出相同的异常。

这里是java片段

public static void main(String[] args) {
        String pubKey = "MIIBCgKCAQEA1ZafTYboquQbCTZMEb1IqHKIr8wiDjdn6e0toRajZCQo9W5zuTlEuctrjJJQ08HcOuK3BPFRaFTUP1RBFvnba/T2S1Mc6WVX81b0DmKS8aPJ83TvvQlH3bZjVqFzndXJHJatcXRkZKlbidNQYxV9OYFCRLwgR5PBoJ1P5tp8f8735vIADOBL/93nFywODSjAWLXcyG5tUyRlRGX7eDodL7jqVOFxVMB7K9UOJehPuJQiheykyPSbBSLE6raZbpCHlranTLdihWYFs2tYbxzNrVbXzgKIxDDjrhDLVFvo3beudKQcLQkSO+m2LJIDT91zAnxVQ075AIn80ZHh5kdyQQIDAQAB";
        PublicKey key = getPublic(pubKey);
    }

    public static PublicKey getPublic(String key)  {
        PublicKey pbKey = null; 
        try {
            byte[] keyBytes = Base64.getDecoder().decode(key);
            System.out.println(keyBytes.length);
            X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
            KeyFactory factory = KeyFactory.getInstance("RSA");
            pbKey = factory.generatePublic(spec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return pbKey;
    }

这里是例外

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence
    at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:205)
    at java.security.KeyFactory.generatePublic(KeyFactory.java:334)
    at Main.getPublic(Main.java:40)
    at Main.main(Main.java:28)

但是在线 PEM 解析器网站 - https://8gwifi.org/PemParserFunctions.jsp 正在接受这个 public 密钥,它在后台使用 bouncycastle 库来验证这个 base64 编码的 public 密钥。

谢谢大家。由于 bouncycastle 库的一些问题,我们没有在后端服务中使用它。所以在 iOS 中,我们包括 ASN1 header.

struct ASN1 {
    let type: UInt8
    let length: Int
    let data: Data

    init?(type: UInt8, arbitraryData data: Data) {
        guard data.count > 4 else {
            return nil
        }

        var result = data

        let byteArray = [UInt8](result)

        for (_, v) in byteArray.enumerated() {
            if v == type { // ASN1 SEQUENCE Type
                break
            }
            result = Data(result.dropFirst())
        }
        guard result.count > 4 else {
            return nil
        }
        guard
            let first = result.advanced(by: 0).first, // advanced start from 7.0
            let second = result.advanced(by: 1).first,
            let third = result.advanced(by: 2).first,
            let fourth = result.advanced(by: 3).first
            else {
                return nil
        }

        var length = 0
        switch second {
        case 0x82:
            length = ((Int(third) << 8) | Int(fourth)) + 4
            break
        case 0x81:
            length = Int(third) + 3
            break
        default:
            length = Int(second) + 2
            break
        }

        guard result.startIndex + length <= result.endIndex else { // startIndex, endIndex start from 7.0
            return nil
        }
        result = result[result.startIndex..<result.startIndex + length]
        self.data = result
        self.length = length
        self.type = first
    }

    var last: ASN1? {
        get {
            var result: Data?
            var dataToFetch = self.data
            while let fetched = ASN1(type: self.type, arbitraryData: dataToFetch) {

                if let range = data.range(of: fetched.data) {
                    if range.upperBound == data.count {
                        result = fetched.data
                        dataToFetch = Data(fetched.data.dropFirst())
                    } else {
                        dataToFetch = Data(data.dropFirst(range.upperBound))
                    }
                } else {
                    break
                }
            }

            return ASN1(type: type, arbitraryData: result!)
        }
    }

    static func wrap(type: UInt8, followingData: Data) -> Data {
        var adjustedFollowingData = followingData
        if type == 0x03 {
            adjustedFollowingData = Data([0]) + followingData // add prefix 0
        }
        let lengthOfAdjustedFollowingData = adjustedFollowingData.count
        let first: UInt8 = type
        var bytes = [UInt8]()
        if lengthOfAdjustedFollowingData <= 0x80 {
            let second: UInt8 = UInt8(lengthOfAdjustedFollowingData)
            bytes = [first, second]
        } else if lengthOfAdjustedFollowingData > 0x80 && lengthOfAdjustedFollowingData <= 0xFF {
            let second: UInt8 = UInt8(0x81)
            let third: UInt8 = UInt8(lengthOfAdjustedFollowingData)
            bytes = [first, second, third]
        } else {
            let second: UInt8 = UInt8(0x82)
            let third: UInt8 = UInt8(lengthOfAdjustedFollowingData >> 8)
            let fourth: UInt8 = UInt8(lengthOfAdjustedFollowingData & 0xFF)
            bytes = [first, second, third, fourth]
        }
        return Data(bytes) + adjustedFollowingData
    }

    static func rsaOID() -> Data {
        var bytes = [UInt8]()
        bytes = [0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00]
        return Data(bytes)
    }
}

然后在 swift 中生成 RSA 的 public 密钥时调用它。

class func RSAPublicKeyBitsFromKey(_ secKey:SecKey) -> Data? {

    var queryPublicKey:[String:AnyObject] = [:]
    queryPublicKey[kSecClass as String] = kSecClassKey as NSString
    queryPublicKey[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA as NSString

    if let publicKeyData = SwiftCrypto.publicKeyInData(queryPublicKey, secKey: secKey) {
        let bitstringSequence = ASN1.wrap(type: 0x03, followingData: publicKeyData)
        let oidData = ASN1.rsaOID()
        let oidSequence = ASN1.wrap(type: 0x30, followingData: oidData)
        let X509Sequence = ASN1.wrap(type: 0x30, followingData: oidSequence + bitstringSequence)
        return X509Sequence
    }
    return nil
}

所以,通过这种方式,我解决了这个问题。

抛出异常是因为 iOS 上生成的 RSA public 密钥的 ASN.1 DER 编码用 PKCS#1 定义的 RSAPublicKey 类型表示,而 Java(以及许多其他语言和工具)期望 DER 编码用 X.509 定义的 SubjectPublicKeyInfo 类型表示。这个问题当然有两个方面可以解决。如果您选择在 iOS 端转换 RSA public 密钥的 DER 编码,则可以使用 this project I recently published on GitHub. The structure you may be interested in is RSAPublicKeyExporter, which uses the SimpleASN1Writer 转换 DER 编码。下面的代码片段显示了如何使用它:

import RSAPublicKeyExporter

let publicKeyData = ... // Get external representation of RSA public key some how

let x509EncodedKeyData = RSAPublicKeyExporter().toSubjectPublicKeyInfo(publicKeyData)

我发布的答案 here 包含一些信息,如果从钥匙串中获取导出的密钥,这些信息可能会有用。