Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift RSA Encryption Public Key to Java Server is failing

I am trying to create public base64 key from RSA Private key using Security framework. Here is snippet.

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

This is the output

MIIBCgKCAQEA1ZafTYboquQbCTZMEb1IqHKIr8wiDjdn6e0toRajZCQo9W5zuTlEuctrjJJQ08HcOuK3BPFRaFTUP1RBFvnba/T2S1Mc6WVX81b0DmKS8aPJ83TvvQlH3bZjVqFzndXJHJatcXRkZKlbidNQYxV9OYFCRLwgR5PBoJ1P5tp8f8735vIADOBL/93nFywODSjAWLXcyG5tUyRlRGX7eDodL7jqVOFxVMB7K9UOJehPuJQiheykyPSbBSLE6raZbpCHlranTLdihWYFs2tYbxzNrVbXzgKIxDDjrhDLVFvo3beudKQcLQkSO+m2LJIDT91zAnxVQ075AIn80ZHh5kdyQQIDAQAB

But this public key is not getting accepted by our Java server. It is throwing exception for the same.

Here is java snippet

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

Here is the exception

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)

But the online PEM parser website - https://8gwifi.org/PemParserFunctions.jsp is accepting this public key, which is using bouncycastle library in the background to validate this base64 encoded public key.

enter image description here

like image 904
Ankit Thakur Avatar asked Jun 17 '19 11:06

Ankit Thakur


People also ask

How do you generate an RSA KeyPair in Java?

Generate RSA Key PairKeyPairGenerator generator = KeyPairGenerator. getInstance("RSA"); generator. initialize(2048); KeyPair pair = generator. generateKeyPair();

Does RSA use public key?

RSA private and public keys. An RSA key pair includes a private and a public key. The RSA private key is used to generate digital signatures, and the RSA public key is used to verify digital signatures. The RSA public key is also used for key encryption of DES or AES DATA keys and the RSA private key for key recovery.


2 Answers

The exception is thrown because the ASN.1 DER encoding of an RSA public key generated on iOS is represented with the RSAPublicKey type as defined by PKCS#1, while Java (and many other languages and tools) expect the DER encoding to be represented with the SubjectPublicKeyInfo type as defined by X.509. There are of course two sides where this problem can be solved. And if you choose to convert the DER encoding of the RSA public key at the iOS side, you could use this project I recently published on GitHub. The structure you may be interested in is RSAPublicKeyExporter, which uses the SimpleASN1Writer for converting the DER encoding. The code snippet below shows how to use it:

import RSAPublicKeyExporter

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

let x509EncodedKeyData = RSAPublicKeyExporter().toSubjectPublicKeyInfo(publicKeyData)

The answer I posted here contains some information that may be useful in case the exported key is fetched from the keychain.

like image 104
Next Increment Avatar answered Oct 03 '22 02:10

Next Increment


Thanks guys. Due to some issues with bouncycastle library, we did not used it in backend service. So in iOS, we are including 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)
    }
}

Then called this during generating public key of RSA in swift.

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
}

So, in this way, I had fixed this issue.

like image 35
Ankit Thakur Avatar answered Oct 03 '22 00:10

Ankit Thakur