Demonstrate the use of RSA Public-key system to ex

2019-04-03 00:49发布

问题:

I'm trying to demonstrate the use of RSA Public-key system to exchange messages that achieve confidentiality and integrity/authentication. I am trying to encrypt a message on the client side and send this information to the server side to decrypt. The issue I am having is that my code is not decrypting. It is giving me the following error:

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)

Public-Key Client code:

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();
        }
    }
}

Public-key Server code:

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();
    }
}

Here is the output I receive on the server side:

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)

I have noticed that if I adjust the line:

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

to

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

The program does decrypt but it does not decrypt to the intended string. It looks like this:

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

Please let me know if you can see where I am going wrong with this code.

回答1:

You need to encrypt your digest with public key in 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


回答2:

There are a number of problems with this code and with your understanding of Public Key Cryptography.

First, let's correct the Public Key Cryptography part.

The encryption part gives you confidentiality. The signature part gives you integrity and authentication. It is NOT recommended to use the same sets of keys for both signing and encrypting.

You sign with a PRIVATE key and verify the signature with a corresponding PUBLIC key.

With a DIFFERENT keypair, you encrypt with a PUBLIC key and decrypt with a PRIVATE key. That way, no one has to pass a private key to someone else.

The client just sends the client public key to the server so the server can verify the signature.

The server just sends the server public key to the client so the client can encrypt with the public key and the server can then decrypt with its own private key.

Now the code.

As I mentioned above, you should really have 2 keypairs generated, not just 1.

Your code is actually encrypting the SHA1 digest of the message, NOT the message itself.

Digests are one-way algorithms. There is no way to reverse a digest to get the original message. You are encrypting the digest and then trying to decrypt it and get the original message back. That won't work. If you decrypt it, all you can get back is the digest.

For your purposes, you do not need to compute the digest. You only need to sign and encrypt the message.

Send the signature, the encrypted message, and the client public key to the server.

A side note: RSA keys can SIGN a message of any length. However, RSA keys can only ENCRYPT a message less than the length of the key. For messages longer than the keylength, Symmetric encryption (like AES) is usually used. You can encrypt the message with an AES key, and then use the RSA key to encrypt the AES key. Send the encrypted AES key and message to the recipient. The recipient uses their private key to decrypt the AES key, and then uses that AES key to decrypt the message.

On the server, you are trying to use the same key to decrypt as you used to encrypt. RSA is an asymmetric algorithm, which means one key is used to encrypt and the other is used to decrypt. As I said above, what you really want to do is encrypt with the server's public key, and then the server will use its private key to decrypt.

Also, new String(plainText, "SHA") is not doing what you think it is. It will not be able to reverse the digest. It will probably give you a UnsupportedEncodingException because SHA is not a charset. See Java String for more details.

On the server side, you'll want to add a signature verification. Use the Java Signature class (specifically the verify method) to check the signature.

That should get you going in the right direction and get you much closer to what you want to achieve.

Good luck!