Java 17与Swift iOS生成的PKCS#1公钥差异及适配问询
- *Swift(iOS)*生成PKCS#1格式的密钥
- Java 17生成的密钥外观相似,但采用遵循RFC 5280的**SubjectPublicKeyInfo(简称SPKI)**规范,格式略有不同
生成格式一致的PKCS#1格式公钥
通用方案
分别在以下环境生成4096位RSA公钥:
- Java 17
- iOS模拟器(iPhone 14)中的Swift
生成的密钥示例可查看附录。
示例代码:生成密钥对(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)
示例代码:生成密钥对(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()); }
与预期的格式一致相反,Java端代码仅能解析自身生成的密钥。
Java 17生成密钥的解析方案
解析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(); } }
问题:解析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)
非预期方案:Swift(iOS)生成密钥的临时解决办法
引入外部依赖:
import org.bouncycastle.asn1.ASN1Sequence;
可实现iOS密钥的解析,之后可正常加密数据。
解析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 - 预期算法: RSA, 实际: " + e.getMessage(); sendToErrorQueue(e, errorInfo); return Optional.empty(); } catch (InvalidKeySpecException | IllegalArgumentException | NullPointerException e) { sendToErrorQueue(e, "iOS公钥不符合预期规范。"); return Optional.empty(); } }
但这是一种不同的密钥读取方式,说明两者密钥存在差异。
总结
两个系统生成的密钥均为PKCS#1格式(可通过在线工具验证),但存在细微差异。
- 两者密钥的具体差异是什么?
- 主要问题:Swift(iOS)能否生成与Java 17格式一致的公钥?
- 次要问题:Java 17能否不依赖BouncyCastle解析Swift(iOS)生成的公钥?
- 目标:减少代码中针对不同输入路径的分支判断。
可通过在线工具分析所有密钥,提供多组密钥示例以避免偶然情况。
示例密钥 - Swift iOS
第一组:
MIICCgKCAgEAl0HYn9IdRmexAR9dSh3SxmFazWF0RV2TKmtRxGySvCA+aHZ/Dp+8tC0YDWpQzP+tZdlDWNOBj/D7i1z4b+UZk8F9ZEoi+w61kYQz+KjMkRuYrPBFi4A/gsS2qGeFJ4OvLQzGObZ0C+5/E+wunUWyQCVjgjTHKH8yqfVXeWYFKmaSSZblSZJAObE8Ao5J38DMINF1oWG+GdZt8RTLf35ULLEa8zKvFXrfBkDjiQH90WKPwDxWwvFInqZhjpsFxLytiKW+iBLqa4+0P7uwXVodFXa38RW9dk9mEsLvj6764BKtPAaWym9XZfEZNBu0jwlfO07k0augynGps6bsSIyYglDImlTRagdA/e+1AMBlSxEFVO4NpCjEpMT7rNX/LYPeEaJ8fTC3axOM1yEntcwUfSSCDulVPGlWlFbPxt29y1RrGtMmwfbTlbOQf2okMf0mGx9Ytd9HWadIdWh4FqDVWA8MF82PP1YsOW5TA0hkprK1qJwfkaLRKnmpfL5a7gWn4HWzpdDqrezhbc/zTCeHbX5c61mgWsAIWnB3fyXsJHpR62+QtBEznwhDxafVCQyUmsDqbncBxilEnFIn08G+6ox2PQC8GhNdpFVGsSoqpDfctsm51/tUYLpyeIBdzw/3nMLL1J5jL3yWr7ZKgnzNwjpC8l6c6gpsF3NRodeVhp8CAwEAAQ==
第二组:
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlSbN7rlX6EMCDGuxQm9G6qqXIWshDvI5VaN4QPNCFcFzPjI/FyaHqi43+mxTyKGycT6TNqvanSu67yoIA80tSlFH2LndWv6sPQeHKxNwwFKU98Y/uL3M6hKIVXDwa6kZGM58dfY9qMWEOPXB4AwZtVaxJ4ePHFAR0kEOAHLCPpQxFkNlkg9FlCwlBzZbhHkI5uhCmlGEiLpLaJNJzjn7yo33m65189J6tIh3vopZ2ahFHb/vo6SVSbtS7Y0YlinJwH3MCGLm5+pKBpE64t5lgEhDPYSF7pKpLhEVqC5ETaya3mYTkXPSCiY/9Mo5zddniUGrxAJaY940jY3QNYeFmWPEEdhhIW2tUbuiM3IlHAqk4Du5p/2o1QcSVZdBsHStSGdcWqs00Z+ooxXwrOpMOUnZeb8Jt5dGDyHwjOmPf0N8dDQwz+5pgOoTkxd/z5cTK9tMv/nC+Mbbt8LRsct16M3LYrRAYnA8unX1ZD3eXTJPOSB/fM9Hmf1ObxQro9tatC5RaJjS0Xg5aepUsey62BW9lj4kHCkRPD4w1OmjDWGUCOSEhwy0KwqFyRBppYUMndEbJb3GKEjpekcQYwjERPtH/vEVxaN5gGYaCORancUaNH+d03zAmi0G830QXQvyohyKUkGw5qljEBH5R5Wqu7YLTlUes0rQEx8DpLVclaMCAwEAAQ==
示例密钥 - Java 17
第一组:
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqwMRLS3hhNuVBQTqRT4714fbl4h3PtrSZQ8Ry1qMCefFOLzmLmLD/VdnFoHj3QiqlZMlajvVuJonprhOy911aNW2Qs2VZ+zoMkLRjah9gqg00dLVtdWa2FXjAX6bh/rSR6jmF1hzfLPs/CqOKyDpX003AUF/WvgHkuAizQjZeskEDKawbExs6RSSFHNycrGwB9hBhAwGvyZpC2+fqPN/93W2NZ8xyb5H6CmM7pCpG+sTcsEEYOZjjq2HV8vYdM9KHi7O+nmY0MJxjLJ5JOv5oLqPXFt4Epezz4v2C3oE3Leu4gIy1r1D2xHuxu0pyQ9F2nI7a+6mGZbRAYDA8Yq2oidqBDC9pKogoHcMYhXj/G0WNEhE9ODuHg+GkiiGc3zysuKwfKu1Z5ZmUtlI1kVFqUEEi81smylTXtkU/oEf7P0OX94XcNTq1N3iO+nuFayAhINlvLUXwyO4JQUsmCyu0bH17XuNUfbnSCmOfGhFSjpyU0PthW9Qtf9yY9MmyZz9c9T2kfxoUxBnNRhVUZ+Kg8oA6kkNJp/ac+AdZzkPZFev91ZKYCbyI+rcakJ4KBQ5YoprDtpHmAQZ5E3/rWcq1Tis0yiomE2+natrzaO4aQbrljOHEgB6arbMkeu7/sZHEvYfK7ilH7tsZfDdU2DVOV0yxNReYwOHAFgFMTNQvBsCAwEAAQ==
第二组:
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAs9ywiZnmv9Ewf+dIsmwV8ZlZKQNg4lbVLpNptb3pftnoukvBjak1rDqb6GcxXtM+UuBgr4Wigi0TI+vItzWv6QlBB8HShA4WImKrOCD3+U1fFdDpwCxQO+oHqTqeNKhRcKcM0e0C/c88hJo8l9G2OCjIANW3O+pGeMirB/XjPdcQ+d37TFGpRs83wPR8gUFQsH8qQMkS7uOtTCZHfcK6UzbvJB3Gf/llcCSfPYKUMY9SXIc5K9hpI0E1v5EdEUvdbhiilZI0esntr5BzeO5AxH/m9fFPH9ekZZyc9nXsHY7tPOJMD+wsTad4D9NBGi9x+lzxZRdxyDLote4ix+aHSqMgcdtOyJ69WFym2Ty1VfCgrmG8fhC/PZBEzk6obK5uJD4mO3owrKgYsmJgZHO3CbVkmv04+EEPpQLippQu8uCl+2KO480EvjSco5ryVH3XEsAGokBLCsZviBtntY/Ia0OLXPmy7WWXigxsVQkgPZ1SyZa3FUp/Zuwtoo0BWDI/9JdUZPfKhbQkcxtnKbCe8QXf51YUgIfKXPTBlmk/Zj931EzXdQCZFMvgK+I5p79DqCsD9yKbiUnfK9tXJ4SzMfuiLd4pqB0UIMVMQEj5SZPYn7eHRZyje4v96FDwtj56NQ/kZPYXKqLoOv5nvMALZngzJ6aFaGjkWZX9RNQLfA8CAwEAAQ==
内容的提问来源于stack exchange,提问作者d.braun1991

