Java错误:使用填充密码解密时,输入长度必须是16的倍数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18291987/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 00:08:40  来源:igfitidea点击:

Java Error: Input length must be multiple of 16 when decrypting with padded cipher

javaaes

提问by Silver

Ello mates! I am attempting to make a chat program that uses AES encryption but I have ran into a problem. When I try to run the program, it gives the error that you can see in the title. I will give you the code for both classes because it seems that, that is the only way to fully diagnose the problem. There are many duplicates of this but none seem to answer my specific problem. Thanks for all your help! Client:

埃洛伙伴们!我正在尝试制作一个使用 AES 加密的聊天程序,但我遇到了问题。当我尝试运行该程序时,它给出了您可以在标题中看到的错误。我会给你两个类的代码,因为这似乎是完全诊断问题的唯一方法。这有很多重复,但似乎没有一个能回答我的具体问题。感谢你的帮助!客户:

package Chat.Application;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import java.security.spec.AlgorithmParameterSpec;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Encoder;

/**
 * A simple Swing-based client for the chat server. Graphically it is a frame
 * with a text field for entering messages and a textarea to see the whole
 * dialog.
 *
 * The client follows the Chat Protocol which is as follows. When the server
 * sends "SUBMITNAME" the client replies with the desired screen name. The
 * server will keep sending "SUBMITNAME" requests as long as the client submits
 * screen names that are already in use. When the server sends a line beginning
 * with "NAMEACCEPTED" the client is now allowed to start sending the server
 * arbitrary strings to be broadcast to all chatters connected to the server.
 * When the server sends a line beginning with "MESSAGE " then all characters
 * following this string should be displayed in its message area.
 */
public class ChatClient {

    BufferedReader in;
    PrintWriter out;
    JFrame frame = new JFrame("ELECTRON Chatroom");
    JTextField textField = new JTextField(40);
    JTextArea messageArea = new JTextArea(8, 40);
        Cipher cipher;
        // password for encryption
        final String strPassword = "1234567890123456";
        // put this as key in AES
        final SecretKeySpec key = new SecretKeySpec(strPassword.getBytes(), "AES");
    /**
     * Constructs the client by laying out the GUI and registering a listener
     * with the textfield so that pressing Return in the listener sends the
     * textfield contents to the server. Note however that the textfield is
     * initially NOT editable, and only becomes editable AFTER the client
     * receives the NAMEACCEPTED message from the server.
     */
    public ChatClient() {

        // Layout GUI
        textField.setEditable(false);
        messageArea.setEditable(false);
        messageArea.setWrapStyleWord(true);
        messageArea.setLineWrap(true);
        frame.getContentPane().add(textField, "North");
        frame.getContentPane().add(new JScrollPane(messageArea), "Center");
        frame.pack();


        // Add Listeners
        textField.addActionListener(new ActionListener() {
            /**
             * Responds to pressing the enter key in the textfield by sending
             * the contents of the text field to the server. Then clear the text
             * area in preparation for the next message.
             */
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    String input = (textField.getText());
                    //ENCRYPTION
                    // Parameter specific algorithm
                    AlgorithmParameterSpec paramSpec = new IvParameterSpec(strPassword.getBytes());
                    //Whatever you want to encrypt/decrypt
                    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

                    // You can use ENCRYPT_MODE (ENCRYPTunderscoreMODE)  or DECRYPT_MODE (DECRYPT underscore MODE) 

                    cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);

                    // encrypt data 
                    byte[] encrypted = cipher.doFinal(input.getBytes());

                    // encode data using standard encoder
                    //String output = new BASE64Encoder().encode(encrypted);

                    System.out.println("Orginal tring: " + input);
                    System.out.println("Encrypted string: " + encrypted);

                    textField.setText("");
                } catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException ex) {
                    Logger.getLogger(ChatClient.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    /**
     * Prompt for and return the address of the server.
     */
    private String getServerAddress() {
        return JOptionPane.showInputDialog(
                frame,
                "Enter IP Address of the Server:",
                "ELECTRON Chatroom",
                JOptionPane.QUESTION_MESSAGE);
    }

    /**
     * Prompt for and return the desired screen name.
     */
    private String getName() {
        return JOptionPane.showInputDialog(
                frame,
                "Choose a screen name:",
                "Screen name selection",
                JOptionPane.PLAIN_MESSAGE);
    }

    /**
     * Connects to the server then enters the processing loop.
     */
    private void run() throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {

        // Make connection and initialize streams
        String serverAddress = getServerAddress();
        Socket socket = new Socket(serverAddress, 9001);
        in = new BufferedReader(new InputStreamReader(
                socket.getInputStream()));
        out = new PrintWriter(socket.getOutputStream(), true);
        // Process all messages from server, according to the protocol.
        while (true) {
            String line = in.readLine();
            if (line.startsWith("SUBMITNAME")) {
                out.println(getName());
            } else if (line.startsWith("NAMEACCEPTED")) {
                textField.setEditable(true);
            } else if (line.startsWith("MESSAGE")) {
                //DECRYPTION
                line = line.substring(8);
                System.out.println(line);
                // Parameter specific algorithm
                AlgorithmParameterSpec paramSpec = new IvParameterSpec(strPassword.getBytes());
                //Whatever you want to encrypt/decrypt
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

                // You can use ENCRYPT_MODE (ENCRYPTunderscoreMODE)  or DECRYPT_MODE (DECRYPT underscore MODE) 
                cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
                byte messageByte[] = cipher.doFinal(line.getBytes());
                String message = new String(messageByte, "UTF-8");
                messageArea.append(message.substring(8) + "\n");
                System.out.println(message);
            }
        }
    }

    /**
     * Runs the client as an application with a closeable frame.
     */
    public static void main(String[] args) throws Exception {
        ChatClient client = new ChatClient();
        client.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        client.frame.setVisible(true);
        client.run();
    }
}

Server:

服务器:

package Chat.Application;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;

import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;

/**
 * A multi-threaded chat room server.  When a client connects the
 * server requests a screen name by sending the client the
 * text "SUBMITNAME", and keeps requesting a name until
 * a unique one is received.  After a client submits a unique
 * name, the server acknowledges with "NAMEACCEPTED".  Then
 * all messages from that client will be broadcast to all other
 * clients that have submitted a unique screen name.  The
 * broadcast messages are prefixed with "MESSAGE ".
 *
 * Because this is just a teaching example to illustrate a simple
 * chat server, there are a few features that have been left out.
 * Two are very useful and belong in production code:
 *
 *     1. The protocol should be enhanced so that the client can
 *        send clean disconnect messages to the server.
 *
 *     2. The server should do some logging.
 */
public class ChatServer {

    /**
     * The port that the server listens on.
     */
    private static final int PORT = 9001;

    /**
     * The set of all names of clients in the chat room.  Maintained
     * so that we can check that new clients are not registering name
     * already in use.
     */
    private static HashSet<String> names = new HashSet<String>();

    /**
     * The set of all the print writers for all the clients.  This
     * set is kept so we can easily broadcast messages.
     */
    private static HashSet<PrintWriter> writers = new HashSet<PrintWriter>();

    /**
     * The application main method, which just listens on a port and
     * spawns handler threads.
     */
    public static void main(String[] args) throws Exception {
        System.out.println("Chat Server Activated");
        ServerSocket listener = new ServerSocket(PORT);
        try {
            while (true) {
                new Handler(listener.accept()).start();
            }
        } finally {
            listener.close();
        }
    }

    /**
     * A handler thread class.  Handlers are spawned from the listening
     * loop and are responsible for a dealing with a single client
     * and broadcasting its messages.
     */
    private static class Handler extends Thread {
        private String name;
        private Socket socket;
        private BufferedReader in;
        private PrintWriter out;
        private Integer length;

        /**
         * Constructs a handler thread, squirreling away the socket.
         * All the interesting work is done in the run method.
         */
        public Handler(Socket socket) {
            this.socket = socket;
        }

        /**
         * Services this thread's client by repeatedly requesting a
         * screen name until a unique one has been submitted, then
         * acknowledges the name and registers the output stream for
         * the client in a global set, then repeatedly gets inputs and
         * broadcasts them.
         */
        public void run() {
            try {

                // Create character streams for the socket.
                in = new BufferedReader(new InputStreamReader(
                    socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream(), true);
                // Request a name from this client.  Keep requesting until
                // a name is submitted that is not already used.  Note that
                // checking for the existence of a name and adding the name
                // must be done while locking the set of names.
                while (true) {
                    out.println("SUBMITNAME");

                /*Supposed to change anybody who tries to login as Admin into different name... "Supposed" to....
                    if (name.equals("Admin")) {
                        out.println("SUBMITNAME");
                        return;
                    }*/

                    name = in.readLine();
                    length = name.length();
                    if (length == 0) {
                        out.println("SUBMITNAME");
                        return;
                    }
                    if (name == "null") {
                        out.println("SUBMITNAME");
                        return;
                    }
                        synchronized (names) {
                        if (!names.contains(name)) {
                            names.add(name);
                            break;
                        }
                    }
                }
                // Now that a successful name has been chosen, add the
                // socket's print writer to the set of all writers so
                // this client can receive broadcast messages.
                out.println("NAMEACCEPTED");
                //Announces that user is Online
                out.println("MESSAGE " + name + " is now Online");

                for (PrintWriter writer : writers) {
                writer.println("MESSAGE " + name + " is now Online");
                }
                writers.add(out);
                // Accept messages from this client and broadcast them.
                // Ignore other clients that cannot be broadcasted to.
                while (true) {
                    String input = in.readLine();
                    System.out.println(input);
                    if (input == null) {
                        return;
                    }
                    for (PrintWriter writer : writers) {
                        writer.println("MESSAGE " + name + ": " + input);
                    }
                }
            } catch (IOException e) {
                System.out.println(e);
            } finally {
                // This client is going down!  Remove its name and its print
                // writer from the sets, and close its socket.
                for (PrintWriter writer : writers) {
                writer.println("MESSAGE " + name + " is now Offline");
                }                if (name != null) {
                    names.remove(name);
                }
                if (out != null) {
                    writers.remove(out);
                }
                try {
                    socket.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

Thanks so much for all your help in saving a complete noob at programming!

非常感谢您为节省编程新手的所有帮助!

-Silver

-银

采纳答案by Rob

This is probably the best and most useful error message you will ever see. It tells you exactly what the problem is. You are using a cipher that requires padding so that the thing you are encrypting must have a length that is a multiple of 16 bytes.

这可能是您见过的最好、最有用的错误消息。它会准确地告诉您问题所在。您正在使用需要填充的密码,以便您加密的内容的长度必须是 16 字节的倍数。

You either have to pad your messages (e.g., with white space that you trim() after decryption), or you need to change to a non-padded cipher.

您要么必须填充您的消息(例如,使用解密后修剪()的空格),要么您需要更改为非填充密码。