使用CipherInputStream/OutputStream传输RSA加密数据时遇BadPaddingException
问题:使用CipherOutputStream/CipherInputStream网络传输加密数据时出现BadPaddingException
尝试用CipherOutputStream/CipherInputStream通过网络传输加密数据,一直报BadPaddingException。双方使用相同的RSA密钥,手动加解密完全正常,公私钥模数也匹配。
手动加解密可行示例代码
public static void main(String[] args) { PrintStream out = System.out; try { PrivateKey pri_key = Util.readPrivateKey("data/private.der"); PublicKey pub_key = Util.readPublicKey("data/public.der"); Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding"); String hi = "Hello World"; byte[] data = hi.getBytes("UTF8"); out.println("data[" + data.length + "]"); cipher.init(Cipher.ENCRYPT_MODE, pub_key); byte[] encdata = cipher.doFinal(data); out.println("encdata[" + encdata.length + "]"); cipher.init(Cipher.DECRYPT_MODE, pri_key); byte[] decdata = cipher.doFinal(encdata); out.println("decdata[" + decdata.length + "]"); out.println(new String(decdata, "UTF8")); } catch (Exception e) { e.printStackTrace(); } }
网络收发加密代码
static class ByteEncComm extends Comm { public ByteEncComm(Socket sock, PrivateKey priv_key, PublicKey pub_key) { super(sock, priv_key, pub_key); } public void send(Bird b) { try { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding"); cipher.init(Cipher.ENCRYPT_MODE, public_key); byte[] data = "Hello World".getBytes("UTF8"); CipherOutputStream cos = new CipherOutputStream(sock.getOutputStream(), cipher); cos.write(data, 0, data.length); cos.flush(); out.println("Sent data[" + data.length + "]"); } catch (Exception e) { e.printStackTrace(); } } public Bird receive() { PrintStream out = System.out; try { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding"); cipher.init(Cipher.DECRYPT_MODE, private_key); CipherInputStream cis = new CipherInputStream(sock.getInputStream(), cipher); out.println("Avail " + cis.available()); byte[] data = cis.readAllBytes(); out.println("Got data[" + data.length + "]"); } catch (Exception e) { e.printStackTrace(); } return null; } }
客户端代码
public class Client { public static void main(String[] args) { PrintStream out = System.out; try { PrivateKey pri_key = Util.readPrivateKey("data/private.der"); PublicKey pub_key = Util.readPublicKey("data/public.der"); Socket sock = new Socket("127.0.0.1", 5555); Comm comm = new Comm.ByteEncComm(sock, pri_key, pub_key); comm.send(new Bird("Robin")); sock.close(); } catch (Exception e) { e.printStackTrace(); } } }
服务端代码
public class Server extends Thread { public static void main(String[] args) { PrintStream out = System.out; try { PrivateKey pri_key = Util.readPrivateKey("data/private.der"); PublicKey pub_key = Util.readPublicKey("data/public.der"); ServerSocket serversock = new ServerSocket(5555); Socket sock = serversock.accept(); Comm comm = new Comm.ByteEncComm(sock, pri_key, pub_key); comm.receive(); sock.close(); serversock.close(); } catch (Exception e) { e.printStackTrace(); } } }
报错信息
java.io.IOException: javax.crypto.BadPaddingException: Decryption error at java.base/javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:148) at java.base/javax.crypto.CipherInputStream.read(CipherInputStream.java:261) at java.base/java.io.InputStream.readNBytes(InputStream.java:409) at java.base/java.io.InputStream.readAllBytes(InputStream.java:346) at foo.enc.Comm$ByteEncComm.receive(Comm.java:139) at foo.enc.Server.main(Server.java:21) Caused by: javax.crypto.BadPaddingException: Decryption error at java.base/sun.security.rsa.RSAPadding.unpadOAEP(RSAPadding.java:488) at java.base/sun.security.rsa.RSAPadding.unpad(RSAPadding.java:284) at java.base/com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:372) at java.base/com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:418) at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2152) at java.base/javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:145) ... 5 more
问题原因与解决方案
核心问题1:RSA流式加密的误用
RSA是块加密算法,单块长度受密钥长度限制(比如2048位RSA,OAEP padding下单块最大明文长度为214字节)。CipherOutputStream会按块加密数据,但仅调用flush()不会触发doFinal()——RSA加密需要该方法完成最终块的padding操作。客户端没有关闭流,导致服务端收到的加密数据不完整,解密时padding校验失败。
核心问题2:密钥使用逻辑错误
客户端发送数据时应该用服务端的公钥加密,服务端用自己的私钥解密;但当前代码中客户端和服务端都使用本地的公私钥对,相当于客户端用自己的公钥加密,服务端用自己的私钥解密,密钥不匹配必然解密失败。
修复步骤:
修正密钥使用逻辑
- 客户端加载服务端的公钥进行加密;
- 服务端加载自身私钥进行解密。
正确关闭CipherOutputStream
在客户端send方法中,写完数据后必须调用cos.close(),触发doFinal()完成加密:public void send(Bird b) { try { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding"); // 替换为服务端公钥 cipher.init(Cipher.ENCRYPT_MODE, server_public_key); byte[] data = "Hello World".getBytes("UTF8"); CipherOutputStream cos = new CipherOutputStream(sock.getOutputStream(), cipher); cos.write(data, 0, data.length); cos.close(); // 必须关闭流以完成最终块加密 out.println("Sent data[" + data.length + "]"); } catch (Exception e) { e.printStackTrace(); } }优化RSA使用场景
RSA不适合直接加密大体积数据,常规方案是:- 生成随机对称密钥(如AES);
- 用对称密钥加密实际数据;
- 用RSA加密对称密钥,与加密数据一同发送;
- 接收方先用RSA解密出对称密钥,再解密数据。
内容的提问来源于stack exchange,提问作者trungdok
相关产品推荐
相关产品推荐

