Java RSA客户端签名报错:DerInputStream.getLength(): lengthTag=109
RSA签名时抛出InvalidKeySpecException:DerInputStream.getLength(): lengthTag=109, too big
执行命令java Client localhost 1234 server sample.txt时,程序抛出如下错误:
Connected to server Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : DerInputStream.getLength(): lengthTag=109, too big. at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(Unknown Source) at java.security.KeyFactory.generatePrivate(Unknown Source) at Client.sign(Client.java:123) at Client.main(Client.java:54) Caused by: java.security.InvalidKeyException: IOException : DerInputStream.getLength(): lengthTag=109, too big. at sun.security.pkcs.PKCS8Key.decode(Unknown Source) at sun.security.pkcs.PKCS8Key.decode(Unknown Source) at sun.security.rsa.RSAPrivateCrtKeyImpl.<init>(Unknown Source) at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(Unknown Source) at sun.security.rsa.RSAKeyFactory.generatePrivate(Unknown Source) ... 4 more
引发错误的sign方法代码如下:
public static byte[] sign(byte[] data, String privateKeyFile) throws Exception { // Read the private key from file FileInputStream keyfis = new FileInputStream(privateKeyFile); byte[] keyBytes = new byte[keyfis.available()]; keyfis.read(keyBytes); keyfis.close(); // Create the private key object PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey privateKey = kf.generatePrivate(spec); // Sign the data using SHA-256 with RSA Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(data); byte[] signed = signature.sign(); return signed; }
调用该方法的代码行:
byte[] signature = sign(encryptedBytes, userid + PRIVATE_KEY_FILENAME);
完整的Client.java代码:
import java.io.*; import java.net.*; import java.security.*; import java.security.spec.*; import javax.crypto.*; import javax.crypto.spec.*; import java.util.*; import java.math.BigInteger; import java.security.interfaces.*; import java.security.Signature; import java.security.SignatureException; public class Client { private static final String PUBLIC_KEY_FILENAME = "server.pub"; private static final String PRIVATE_KEY_FILENAME = ".prv"; private static final int AES_KEY_SIZE = 256; private static final int IV_SIZE = 16; public static void main(String[] args) throws Exception { // Check command line arguments if (args.length != 4) { System.out.println("Usage: java Client host port userid filename"); return; } // Get command line arguments String host = args[0]; int port = Integer.parseInt(args[1]); String userid = args[2]; String filename = args[3]; // Connect to server Socket socket = new Socket(host, port); System.out.println("Connected to server"); // Set up input and output streams DataInputStream input = new DataInputStream(socket.getInputStream()); DataOutputStream output = new DataOutputStream(socket.getOutputStream()); // Send userid to server output.writeUTF(userid); // Key agreement stage byte[] clientBytes = new byte[16]; SecureRandom random = new SecureRandom(); random.nextBytes(clientBytes); byte[] encryptedBytes = encryptRSA(clientBytes, userid + PRIVATE_KEY_FILENAME); output.writeInt(encryptedBytes.length); output.write(encryptedBytes); byte[] signature = sign(encryptedBytes, userid + PRIVATE_KEY_FILENAME); output.writeInt(signature.length); output.write(signature); int serverEncryptedBytesLength = input.readInt(); byte[] serverEncryptedBytes = new byte[serverEncryptedBytesLength]; input.readFully(serverEncryptedBytes); int serverSignatureLength = input.readInt(); byte[] serverSignature = new byte[serverSignatureLength]; input.readFully(serverSignature); if (!verify(serverEncryptedBytes, serverSignature, PUBLIC_KEY_FILENAME)) { System.out.println("Signature verification failed"); return; } byte[] serverBytes = decryptRSA(serverEncryptedBytes, userid + PRIVATE_KEY_FILENAME); byte[] keyBytes = new byte[AES_KEY_SIZE / 8]; System.arraycopy(clientBytes, 0, keyBytes, 0, IV_SIZE); System.arraycopy(serverBytes, 0, keyBytes, IV_SIZE, IV_SIZE); SecretKey key = new SecretKeySpec(keyBytes, "AES"); // File transmission stage File file = new File(filename); byte[] fileBytes = new byte[(int) file.length()]; FileInputStream fileInput = new FileInputStream(file); fileInput.read(fileBytes); fileInput.close(); byte[] iv = new byte[IV_SIZE]; random.nextBytes(iv); byte[] encryptedFileBytes = encryptAES(fileBytes, key, iv); output.writeInt(iv.length); output.write(iv); output.writeInt(encryptedFileBytes.length); output.write(encryptedFileBytes); socket.close(); } private static byte[] encryptRSA(byte[] data, String privateKeyFilename) throws Exception { PrivateKey privateKey = readPrivateKey(privateKeyFilename); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, privateKey); return cipher.doFinal(data); } private static byte[] decryptRSA(byte[] cipherText, String privateKeyFile) throws Exception { PrivateKey privateKey = readPrivateKey(privateKeyFile); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(cipherText); } // Method to sign data using the specified private key file public static byte[] sign(byte[] data, String privateKeyFile) throws Exception { // Read the private key from file FileInputStream keyfis = new FileInputStream(privateKeyFile); byte[] keyBytes = new byte[keyfis.available()]; keyfis.read(keyBytes); keyfis.close(); // Create the private key object PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey privateKey = kf.generatePrivate(spec); // Sign the data using SHA-256 with RSA Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(data); byte[] signed = signature.sign(); return signed; } private static boolean verify(byte[] data, byte[] signature, String publicKeyFile) throws Exception { PublicKey publicKey = readPublicKey(publicKeyFile); Signature verifier = Signature.getInstance("SHA256withRSA"); verifier.initVerify(publicKey); verifier.update(data); return verifier.verify(signature); } private static byte[] encryptAES(byte[] plainText, SecretKey key, byte[] iv) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); return cipher.doFinal(plainText); } private static PrivateKey readPrivateKey(String fileName) throws Exception { ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(fileName)); PrivateKey key = (PrivateKey) inputStream.readObject(); inputStream.close(); return key; } private static PublicKey readPublicKey(String fileName) throws Exception { ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(fileName)); PublicKey key = (PublicKey) inputStream.readObject(); inputStream.close(); return key; } }
问题原因
代码中存在私钥读取方式不匹配的问题:
- 已有的
readPrivateKey方法是通过ObjectInputStream读取序列化后的PrivateKey对象; - 但
sign方法却把私钥文件内容当作PKCS8编码的原始字节处理,用PKCS8EncodedKeySpec解析,格式完全不兼容,触发DER解析错误。
解决方案
修改sign方法,复用已有的readPrivateKey方法读取私钥,避免格式不匹配:
public static byte[] sign(byte[] data, String privateKeyFile) throws Exception { // 复用已有的readPrivateKey方法读取私钥 PrivateKey privateKey = readPrivateKey(privateKeyFile); // 用SHA-256 with RSA签名数据 Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(data); byte[] signed = signature.sign(); return signed; }
注意encryptRSA和decryptRSA方法已经正确使用readPrivateKey,仅需修正sign方法即可。
内容的提问来源于stack exchange,提问作者Anirudh Saladi
相关产品推荐
相关产品推荐

