简体   繁体   中英

Why do PKCS#1 PublicKeys from 'Java17 KeyFactory' and Swift (iOS) differ?

Update (tl;dr)

  • Swift (iOS) produces PKCS#1-formatted keys
  • Java 17 produces keys which look alike but are slightly different using the SubjectPublicKeyInfo (or SPKI) specification, following RFC 5280

Objective

Creating equally formatted public keys in PKCS#1-format

General approach

I am generating 4096 RSA public keys, in both...

  • Java 17
  • Swift in iOS emulator (iPhone 14)

Samples of the subsequent generated and keys can be found in the Appendix .

Sample Code: Generating a KeyPair (Swift - iOS)

// Keypair attributes
let tag = "my_personal_keystore_keypair_alias".data(using: .utf8)!
let attributes: [String: Any] =
    [kSecAttrKeyType as String:            kSecAttrKeyTypeRSA,
     kSecAttrKeySizeInBits as String:      "4096",
     kSecPrivateKeyAttrs as String:
        [kSecAttrIsPermanent as String:    true,
         kSecAttrApplicationTag as String: tag]
]
var error: Unmanaged<CFError>?

// Generate PrivateKey / Keypair
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
    throw error!.takeRetainedValue() as Error
}

// Extract PublicKey
guard let publicKey = SecKeyCopyPublicKey(privateKey),
      let publicKeyOptional = SecKeyCopyExternalRepresentation(publicKey, nil) else {
    return "PublicKey could not be accessed."
}
let publicKeyData = publicKeyOptional as Data
let base64PublicKey = publicKeyData.base64EncodedString()
print("PublicKey, base64:   ", base64PublicKey)

Sample Code: Generating a KeyPair (Java 17)

private static String getPublicKey() {
    KeyPairGenerator generator;
    try {
        generator = KeyPairGenerator.getInstance("RSA");
    } catch (NoSuchAlgorithmException e) {
        throw new EncryptionException(e);
    }
    generator.initialize(4096);
    KeyPair pair = generator.generateKeyPair();
    return Base64.getEncoder().encodeToString(pair.getPublic().getEncoded());
}

Aim - Parsing the keys in Java 17

Contrary to the assumption that the keys are formatted equally, the Java-side code is only capable to read the keys generated by Java itself.

Solution for keys generated by Java 17

Parsing the key(s) generated by Java 17:

public Optional<PublicKey> validateKeyFromJava(String publicKey) {
    try {
        byte[] publicKeyBytes = Base64.getDecoder().decode(publicKey);
        KeyFactory keyFactory = KeyFactory.getInstance(EncryptionProperties.ALGORITHM);
        EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
        return Optional.ofNullable(keyFactory.generatePublic(publicKeySpec));
    } catch (NoSuchAlgorithmException | InvalidKeySpecException | IllegalArgumentException | NullPointerException e) {
        return Optional.empty();
    }
}

Problem: Parsing the keys generated via Swift (iOS)

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:241) at java.base/java.security.KeyFactory.generatePublic(KeyFactory.java:351)

NOT WANTED - Workaround for keys generated via Swift (iOS)

Using an external dependency..

import org.bouncycastle.asn1.ASN1Sequence;

.. it is possible to parse the iOS keys as well. The key can thereafter be used to properly encrypt 'payload'...

Parsing the key(s) generated via Swift (iOS):

public Optional<PublicKey> validateKeyFromIOS (String publicKeyANS1) {
    try {
        byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyANS1);

        ASN1Sequence sequence = ASN1Sequence.getInstance(publicKeyBytes);
        ASN1Integer modulus = ASN1Integer.getInstance(sequence.getObjectAt(0));
        ASN1Integer exponent = ASN1Integer.getInstance(sequence.getObjectAt(1));
        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus.getPositiveValue(),
                exponent.getPositiveValue());

        KeyFactory factory = KeyFactory.getInstance(EncryptionProperties.ALGORITHM);

        return Optional.ofNullable(factory.generatePublic(keySpec));
    } catch (NoSuchAlgorithmException e) {
        String errorInfo = "iOS-PublicKey - Erwarteter Algorithmus: RSA, Retrieved: " +
                e.getMessage();
        sendToErrorQueue(e, errorInfo);
        return Optional.empty();
    } catch (InvalidKeySpecException | IllegalArgumentException | NullPointerException e) {
        sendToErrorQueue(e, "Der iOS-PublicKey entspricht nicht der erwarteten Specification.");
        return Optional.empty();
    }
}

... Still it is a different way to read the keys. Therefore the keys have to be somehow different.

Wrapping up

Both systems produce keys which are PKCS#1-formatted. This can be examined (see Appendix) The keys are somewhat different.

Questions to be answered:

  1. What is the difference?
  2. Major - is it possible to generate public keys in Swift (iOS) alike those in Java 17?
  3. Minor - is it possible for Java 17 to read the keys generated by Swift (iOS) without the BouncyCastle dependency?
  • Aim: Reduction of switches in the code for different input paths.

Appendix

All keys may be analyzed:

Multiple keys are given to prevent 'lucky shots'.. ;-)

Sample keys - Swift iOS

First:

MIICCgKCAgEAl0HYn9IdRmexAR9dSh3SxmFazWF0RV2TKmtRxGySvCA+aHZ/Dp+8tC0YDWpQzP+tZdlDWNOBj/D7i1z4b+UZk8F9ZEoi+w61kYQz+KjMkRuYrPBFi4A/gsS2qGeFJ4OvLQzGObZ0C+5/E+wunUWyQCVjgjTHKH8yqfVXeWYFKmaSSZblSZJAObE8Ao5J38DMINF1oWG+GdZt8RTLf35ULLEa8zKvFXrfBkDjiQH90WKPwDxWwvFInqZhjpsFxLytiKW+iBLqa4+0P7uwXVodFXa38RW9dk9mEsLvj6764BKtPAaWym9XZfEZNBu0jwlfO07k0augynGps6bsSIyYglDImlTRagdA/e+1AMBlSxEFVO4NpCjEpMT7rNX/LYPeEaJ8fTC3axOM1yEntcwUfSSCDulVPGlWlFbPxt29y1RrGtMmwfbTlbOQf2okMf0mGx9Ytd9HWadIdWh4FqDVWA8MF82PP1YsOW5TA0hkprK1qJwfkaLRKnmpfL5a7gWn4HWzpdDqrezhbc/zTCeHbX5c61mgWsAIWnB3fyXsJHpR62+QtBEznwhDxafVCQyUmsDqbncBxilEnFIn08G+6ox2PQC8GhNdpFVGsSoqpDfctsm51/tUYLpyeIBdzw/3nMLL1J5jL3yWr7ZKgnzNwjpC8l6c6gpsF3NRodeVhp8CAwEAAQ==

Second:

MIICCgKCAgEAlSbN7rlX6EMCDGuxQm9G6qqXIWshDvI5VaN4QPNCFcFzPjI/FyaHqi43+mxTyKGycT6TNqvanSu67yoIA80tSlFH2LndWv6sPQeHKxNwwFKU98Y/uL3M6hKIVXDwa6kZGM58dfY9qMWEOPXB4AwZtVaxJ4ePHFAR0kEOAHLCPpQxFkNlkg9FlCwlBzZbhHkI5uhCmlGEiLpLaJNJzjn7yo33m65189J6tIh3vopZ2ahFHb/vo6SVSbtS7Y0YlinJwH3MCGLm5+pKBpE64t5lgEhDPYSF7pKpLhEVqC5ETaya3mYTkXPSCiY/9Mo5zddniUGrxAJaY940jY3QNYeFmWPEEdhhIW2tUbuiM3IlHAqk4Du5p/2o1QcSVZdBsHStSGdcWqs00Z+ooxXwrOpMOUnZeb8Jt5dGDyHwjOmPf0N8dDQwz+5pgOoTkxd/z5cTK9tMv/nC+Mbbt8LRsct16M3LYrRAYnA8unX1ZD3eXTJPOSB/fM9Hmf1ObxQro9tatC5RaJjS0Xg5aepUsey62BW9lj4kHCkRPD4w1OmjDWGUCOSEhwy0KwqFyRBppYUMndEbJb3GKEjpekcQYwjERPtH/vEVxaN5gGYaCORancUaNH+d03zAmi0G830QXQvyohyKUkGw5qljEBH5R5Wqu7YLTlUes0rQEx8DpLVclaMCAwEAAQ==

Sample keys - Java 17

First:

MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqwMRLS3hhNuVBQTqRT4714fbl4h3PtrSZQ8Ry1qMCefFOLzmLmLD/VdnFoHj3QiqlZMlajvVuJonprhOy911aNW2Qs2VZ+zoMkLRjah9gqg00dLVtdWa2FXjAX6bh/rSR6jmF1hzfLPs/CqOKyDpX003AUF/WvgHkuAizQjZeskEDKawbExs6RSSFHNycrGwB9hBhAwGvyZpC2+fqPN/93W2NZ8xyb5H6CmM7pCpG+sTcsEEYOZjjq2HV8vYdM9KHi7O+nmY0MJxjLJ5JOv5oLqPXFt4Epezz4v2C3oE3Leu4gIy1r1D2xHuxu0pyQ9F2nI7a+6mGZbRAYDA8Yq2oidqBDC9pKogoHcMYhXj/G0WNEhE9ODuHg+GkiiGc3zysuKwfKu1Z5ZmUtlI1kVFqUEEi81smylTXtkU/oEf7P0OX94XcNTq1N3iO+nuFayAhINlvLUXwyO4JQUsmCyu0bH17XuNUfbnSCmOfGhFSjpyU0PthW9Qtf9yY9MmyZz9c9T2kfxoUxBnNRhVUZ+Kg8oA6kkNJp/ac+AdZzkPZFev91ZKYCbyI+rcakJ4KBQ5YoprDtpHmAQZ5E3/rWcq1Tis0yiomE2+natrzaO4aQbrljOHEgB6arbMkeu7/sZHEvYfK7ilH7tsZfDdU2DVOV0yxNReYwOHAFgFMTNQvBsCAwEAAQ==

Second:

MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAs9ywiZnmv9Ewf+dIsmwV8ZlZKQNg4lbVLpNptb3pftnoukvBjak1rDqb6GcxXtM+UuBgr4Wigi0TI+vItzWv6QlBB8HShA4WImKrOCD3+U1fFdDpwCxQO+oHqTqeNKhRcKcM0e0C/c88hJo8l9G2OCjIANW3O+pGeMirB/XjPdcQ+d37TFGpRs83wPR8gUFQsH8qQMkS7uOtTCZHfcK6UzbvJB3Gf/llcCSfPYKUMY9SXIc5K9hpI0E1v5EdEUvdbhiilZI0esntr5BzeO5AxH/m9fFPH9ekZZyc9nXsHY7tPOJMD+wsTad4D9NBGi9x+lzxZRdxyDLote4ix+aHSqMgcdtOyJ69WFym2Ty1VfCgrmG8fhC/PZBEzk6obK5uJD4mO3owrKgYsmJgZHO3CbVkmv04+EEPpQLippQu8uCl+2KO480EvjSco5ryVH3XEsAGokBLCsZviBtntY/Ia0OLXPmy7WWXigxsVQkgPZ1SyZa3FUp/Zuwtoo0BWDI/9JdUZPfKhbQkcxtnKbCe8QXf51YUgIfKXPTBlmk/Zj931EzXdQCZFMvgK+I5p79DqCsD9yKbiUnfK9tXJ4SzMfuiLd4pqB0UIMVMQEj5SZPYn7eHRZyje4v96FDwtj56NQ/kZPYXKqLoOv5nvMALZngzJ6aFaGjkWZX9RNQLfA8CAwEAAQ==

Maybe not WHY , but at least HOW can be answered...

... the subsequent analysis stems from Java 17. The information provided by Java is seen here as the 'ground truth'. ... ;-)

Analyze keys generated by Java itself

// Static inputs
String algorithm = "RSA";
int keySize = 4096;

// Key generation
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm);
generator.initialize(keySize);
KeyPair pair = generator.generateKeyPair();

// Analysis
System.out.println("----- PublicKey information -----");
System.out.println("PublicKey algorithm: " + pair.getPublic().getAlgorithm());
System.out.println("PublicKey format: " + pair.getPublic().getFormat());

System.out.println("----- PrivateKey information -----");
System.out.println("PrivateKey algorithm: " + pair.getPrivate().getAlgorithm());
System.out.println("PrivateKey format: " + pair.getPrivate().getFormat());

Output:

----- PublicKey information -----
PublicKey algorithm: RSA
PublicKey format: X.509
----- PrivateKey information -----
PrivateKey algorithm: RSA
PrivateKey format: PKCS#8

Conclusion

Java

  • Java generates its PublicKeys in the x.509 -format.
  • PrivateKeys are generated in the PKCS#8-format.

Swift

  • Swift generates its PublicKeys in the PKCS#1 -format.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM