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:
- What is the difference?
- Major - is it possible to generate public keys in Swift (iOS) alike those in Java 17?
- 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==