Spring微服务读取varbinary存储的RSA密钥报InvalidKeySpecException
RSA密钥从数据库读取恢复时InvalidKeySpecException异常解决
相关实现代码
@Autowired private UsuarioRepository usuarioRepository; public String prueba(String data) { generateKeys(data); byte[] text=encryptPrivate("texto de prueba lala",data); String texto=decryptPublic(text,data); return texto; } public String generateKeys(String username) { KeyPairGenerator keyPairGenerator = null; try { keyPairGenerator = KeyPairGenerator.getInstance("RSA"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } keyPairGenerator.initialize(512); // 64的倍数,取值范围512 ~ 65536 KeyPair keyPair = keyPairGenerator.generateKeyPair(); RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); System.out.println("Private: " + rsaPrivateKey); RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); System.out.println("Public: " + rsaPublicKey); byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); System.out.println("Encoded: " + publicKeyBytes); usuarioRepository.add(publicKeyBytes, privateKeyBytes, username); return "ok"; } public byte[] encryptPrivate(String data, String username) { // 从数据库读取密钥 byte[] keyBytes = getPrKey(username); EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); try { KeyFactory kf = KeyFactory.getInstance("RSA"); RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) kf.generatePrivate(spec); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, rsaPrivateKey); byte[] cipherText = cipher.doFinal(data.getBytes()); return cipherText; } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); } return null; } public String decryptPublic(byte[] data, String username) { byte[] keyBytes = getPuKey(username); X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); try { KeyFactory kf = KeyFactory.getInstance("RSA"); RSAPublicKey rsaPublicKey = (RSAPublicKey) kf.generatePublic(spec); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey); byte[] plainText = cipher.doFinal(data); return new String(plainText); } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); } return "fail"; }
问题描述
异常触发代码行:RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) kf.generatePrivate(spec);
密钥存储在数据库varbinary类型字段,运行时抛出如下异常:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : DerValue.getBigIntegerInternal, not expected 48 at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:252) ... Caused by: java.security.InvalidKeyException: IOException : DerValue.getBigIntegerInternal, not expected 48 at java.base/sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:133) ... java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:240) ... Caused by: java.security.InvalidKeyException: IOException: algid parse error, not a sequence at java.base/sun.security.x509.X509Key.decode(X509Key.java:397)
排查步骤
- 校验密钥字节一致性:存储密钥前打印公/私钥字节数组的长度、前10位字节值,读取后再打印相同参数,对比是否完全一致。该类异常90%以上都是因为读写过程中字节被篡改导致。
- 校验数据库字段长度:512位RSA的PKCS8编码私钥长度约300字节,X509公钥约100字节,若varbinary字段长度设置过小会导致存储时字节被截断,无法正常解析。
- 校验编码转换逻辑:排查是否存在将密钥字节数组通过
new String()、字符串转字节等操作处理的逻辑,字符集编码会破坏原始密钥字节结构。
解决方案
- 修正持久层映射逻辑:确保数据库varbinary字段对应的实体类属性直接声明为
byte[]类型,不要通过String类型中转,避免框架自动做字符集转换破坏字节结构。 - 调整数据库字段长度:将存储公钥、私钥的varbinary字段长度调整为至少2048,兼容后续升级到2048位及以上长度的RSA密钥。
- 兼容方案:如果存在跨系统读取密钥的需求,可以将密钥字节数组先通过Base64编码为字符串,存储在varchar字段中,读取后先做Base64解码再生成密钥对象,示例逻辑如下:
// 存储前编码 String pubKeyStr = Base64.getEncoder().encodeToString(publicKeyBytes); String prKeyStr = Base64.getEncoder().encodeToString(privateKeyBytes); // 读取后解码 byte[] pubKeyBytes = Base64.getDecoder().decode(pubKeyStrFromDb); byte[] prKeyBytes = Base64.getDecoder().decode(prKeyStrFromDb);
内容的提问来源于stack exchange,提问作者Rafael Muñoz
相关产品推荐
相关产品推荐

