演示如何使用RSA公钥系统来交换实现机密性和完整性/身份validation的消息

我正在尝试演示使用RSA公钥系统来交换实现机密性和完整性/身份validation的消息。 我试图在客户端加密消息并将此信息发送到服务器端进行解密。 我遇到的问题是我的代码没有解密。 它给了我以下错误:

javax.crypto.BadPaddingException: Data must start with zero at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:308) at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:255) at com.sun.crypto.provider.RSACipher.a(DashoA13*..) at com.sun.crypto.provider.RSACipher.engineDoFinal(DashoA13*..) at javax.crypto.Cipher.doFinal(DashoA13*..) at PKServer.decryptMessage(PKServer.java:36) at PKServer.main(PKServer.java:69) 

公钥客户端代码:

 import java.io.*; import java.net.*; import java.security.*; import javax.crypto.*; public class PKClient { public static final int kBufferSize = 8192; public static void main(String[] args) throws Exception { try { // Generate new key KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); String message = "The quick brown fox jumps over the lazy dog."; // Compute signature Signature instance = Signature.getInstance("SHA1withRSA"); instance.initSign(privateKey); instance.update((message).getBytes()); byte[] signature = instance.sign(); // Compute digest MessageDigest sha1 = MessageDigest.getInstance("SHA1"); byte[] digest = sha1.digest((message).getBytes()); // Encrypt digest Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] encryptedMsg = cipher.doFinal(digest); //Store the key in a file ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("KeyFile.xx")); out.writeObject(privateKey); out.close(); System.out.println("Client - Message: " + message); System.out.println("Client - Encrypted: " + PKServer.asHex(encryptedMsg)); String host = "localhost"; int port = 7999; Socket s = new Socket(host, port); //Open stream to cipher server DataOutputStream os = new DataOutputStream(s.getOutputStream()); os.writeInt(encryptedMsg.length); os.write(encryptedMsg); os.writeInt(digest.length); os.write(digest); os.writeInt(signature.length); os.write(signature); os.flush(); os.close(); //Close socket s.close(); }catch (Exception e) { e.printStackTrace(); } } } 

公钥服务器代码:

 import java.io.*; import java.net.*; import java.security.*; import javax.crypto.*; public class PKServer { public void decryptMessage(InputStream inStream) throws IOException, NoSuchAlgorithmException { try { //Create the Data input stream from the socket DataInputStream dis = new DataInputStream(inStream); //Get the key ObjectInputStream in = new ObjectInputStream(new FileInputStream("KeyFile.xx")); //ObjectOutputStream outSocket = new ObjectOutputStream(s.getOutputStream()); PrivateKey privatekey = (PrivateKey) in.readObject(); System.out.println("Key Used: " + in.toString()); in.close(); //Initiate the cipher Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE,privatekey); int len = dis.readInt(); byte[] encryptedMsg = new byte[len]; dis.readFully(encryptedMsg); System.out.println("Server - Msg Length: " + len); System.out.println("Server - Encrypted: " + asHex(encryptedMsg)); // -Print out the decrypt String to see if it matches the original message. byte[] plainText = cipher.doFinal(encryptedMsg); System.out.println("Decrypted Message: " + new String(plainText, "SHA")); } catch (Exception e) { e.printStackTrace(); } } //Function to make the bytes printable (hex format) public static String asHex(byte buf[]) { StringBuilder strbuf = new StringBuilder(buf.length * 2); int i; for (i = 0; i < buf.length; i++) { if (((int) buf[i] & 0xff) < 0x10) { strbuf.append("0"); } strbuf.append(Long.toString((int) buf[i] & 0xff, 16)); } return strbuf.toString(); } public static void main(String[] args) throws Exception { int port = 7999; ServerSocket server = new ServerSocket(port); Socket s = server.accept(); PKServer cs = new PKServer(); cs.decryptMessage(s.getInputStream()); server.close(); } } 

这是我在服务器端收到的输出:

 Key Used: java.io.ObjectInputStream@fee4648 Server - Msg Length: 128 Server - Encrypted: 8c23b2cd96c07950f4901a670b025531b5f52be0730e4548c9a76090d7ae65a8ce82901c66acfb6bd79520cf8d86bf74bf3105fd638892a681a6905556cbadf394915fbdc09babb5b78b9dd06382e92604e9ca88901613520ccb45fcc376e813df059ebc649c52f233dc2632733d99212b42ce54e59ebd6d9dca98af36a20fc6 javax.crypto.BadPaddingException: Data must start with zero at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:308) at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:255) at com.sun.crypto.provider.RSACipher.a(DashoA13*..) at com.sun.crypto.provider.RSACipher.engineDoFinal(DashoA13*..) at javax.crypto.Cipher.doFinal(DashoA13*..) at PKServer.decryptMessage(PKServer.java:36) at PKServer.main(PKServer.java:69) 

我注意到如果我调整线条:

 Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 

 Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding"); 

该程序确实解密但它没有解密到预期的字符串。 它看起来像这样:

 Decrypted Message: E???.1G?:*?$??|o\?"? V????????O)Z?j??a?!p)6??????_??T*?c?6O????????:?(??C?,??'??`??????(?2D?mC?OLc<7?'?S?R? 

如果你能看到我的代码出错,请告诉我。

您需要使用PKClient中的公钥加密您的摘要:

 KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); Key pubKey = keyPair.getPublic(); // add this PrivateKey privateKey = keyPair.getPrivate(); // several lines later... // Initiate the cipher cipher.init(Cipher.ENCRYPT_MODE, pubKey); // change privateKey to pubKey 

此代码存在许多问题,并且您对公钥加密有所了解。

首先,让我们更正公钥加密部分。

加密部分为您提供机密性。 签名部分为您提供完整性和身份validation。 建议不要使用相同的密钥集进行签名和加密。

您使用PRIVATE键进行签名,并使用相应的PUBLIC密钥validation签名。

使用DIFFERENT密钥对,您可以使用PUBLIC密钥进行加密,并使用PRIVATE密钥进行解密。 这样,没有人必须将私钥传递给其他人。

客户端只是将客户端公钥发送到服务器,以便服务器可以validation签名。

服务器只是将服务器公钥发送给客户端,以便客户端可以使用公钥进行加密,然后服务器可以使用自己的私钥进行解密。

现在的代码。

正如我上面提到的,你应该生成2个密钥对,而不仅仅是1。

您的代码实际上是在加密消息的SHA1摘要,而不是消息本身。

摘要是单向算法。 无法撤消摘要以获取原始邮件。 您正在加密摘要,然后尝试解密它并获取原始消息。 那不行。 如果你解密它,你可以得到的只是摘要。

出于您的目的,您无需计算摘要。 您只需要对邮件进行签名和加密。

将签名,加密消息和客户端公钥发送到服务器。

附注:RSA密钥可以签署任意长度的消息。 但是,RSA密钥只能加密小于密钥长度的消息。 对于长度超过密钥长度的消息,通常使用对称加密 (如AES)。 您可以使用AES密钥加密消息,然后使用RSA密钥加密AES密钥。 将加密的AES密钥和消息发送给收件人。 收件人使用其私钥解密AES密钥,然后使用该AES密钥解密邮件。

在服务器上,您尝试使用相同的密钥进行解密,就像您以前加密一样。 RSA是一种非对称算法,这意味着一个密钥用于加密,另一个密钥用于解密。 如上所述,您真正想要做的是使用服务器的公钥进行加密,然后服务器将使用其私钥进行解密。

此外, new String(plainText, "SHA")没有按照您的想法行事。 它无法逆转摘要。 它可能会给你一个UnsupportedEncodingException,因为SHA不是一个字符集。 有关详细信息,请参阅Java String 。

在服务器端,您需要添加签名validation。 使用Java Signature类(特别是validation方法)检查​​签名。

这应该让你朝着正确的方向前进,让你更接近你想要达到的目标。

祝你好运!