You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()、字符串转字节等操作处理的逻辑,字符集编码会破坏原始密钥字节结构。

解决方案

  1. 修正持久层映射逻辑:确保数据库varbinary字段对应的实体类属性直接声明为byte[]类型,不要通过String类型中转,避免框架自动做字符集转换破坏字节结构。
  2. 调整数据库字段长度:将存储公钥、私钥的varbinary字段长度调整为至少2048,兼容后续升级到2048位及以上长度的RSA密钥。
  3. 兼容方案:如果存在跨系统读取密钥的需求,可以将密钥字节数组先通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 21:54:01