无法发送超过47个字符的AES加密消息

时间:2013-08-19 04:39:33

标签: java aes padding

Ello伙伴们!我正在创建一个AES加密的聊天程序,但遇到了一个问题;每当一条消息超过47个字符时,它会显示“javax.crypto.IllegalBlockSizeException:当使用填充密码解密时,输入长度必须是16的倍数”。我理解错误消息,但我似乎无法弄清楚如何解决它。这是我的完整代码: ChatClient.java     包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 javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.text.*;

import java.security.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.*;

import java.net.InetAddress;

/*
 * Known Bugs:
 * Message size is limited to 47 characters
 */

public class ChatClient {

    BufferedReader in;
    PrintWriter out;
    JFrame frame = new JFrame("ELECTRON Chatroom");
    JTextField textField = new JTextField(32);
    JTextArea messageArea = new JTextArea(8, 40);
    byte[] keyValue = new byte[]{'5', '7', '3', '4', '5', '6', '3', '4', '9', '8', '5', '6', 'l', '9', '3', '4'};
    final String ALGO = "AES";
    String name;
    String myName;
    InetAddress ip = null;
    String errorType;

    /*
     * 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 class JTextFieldLimit extends PlainDocument {
    private int limit;
    // optional uppercase conversion
    private boolean toUppercase = false;

    JTextFieldLimit(int limit) {
        super();
        this.limit = limit;
    }

    JTextFieldLimit(int limit, boolean upper) {
        super();
        this.limit = limit;
        toUppercase = upper;
    }

    public void insertString
            (int offset, String  str, AttributeSet attr)
            throws BadLocationException {
        if (str == null) return;

        if ((getLength() + str.length()) <= limit) {
            if (toUppercase) str = str.toUpperCase();
            super.insertString(offset, str, attr);
        }
    }
}
    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 {
                    if ((textField.getText()).startsWith("/")) {
                        if ("Electron".equals(myName)) {
                            String command = (textField.getText());
                            out.println(command);
                            textField.setText("");
                        }
                    } else {
                        //ENCRYPTION                        
                        String encrypt = textField.getText();
                        Key key = generateKey();
                        Cipher c = Cipher.getInstance(ALGO);
                        c.init(Cipher.ENCRYPT_MODE, key);
                        byte[] encVal = c.doFinal(encrypt.getBytes());
                        String input = new BASE64Encoder().encode(encVal);
                        out.println(input);
                        textField.setText("");
                    }
                } catch (Exception 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 {

        // 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")) {
                if (line.length() == 18) {
                    line = line.substring(11);
                    System.out.println(line);
                    switch (line) {
                        case "ERROR 1":
                            errorType = "[ERROR: Name cannot be blank]";
                            JOptionPane.showMessageDialog(frame, errorType);
                            System.exit(0);
                            break;
                        case "ERROR 2":
                            errorType = "[ERROR: Your names 'Admin'? Seems legit...]";
                            JOptionPane.showMessageDialog(frame, errorType);
                            System.exit(0);
                            break;
                        case "ERROR 3":
                            errorType = "[ERROR: You have been banned]";
                            JOptionPane.showMessageDialog(frame, errorType);
                            System.exit(0);
                            break;
                    }
                } else if (line.length() == 10) {
                    out.println(getName());
                }

            } else if (line.startsWith("NAMEACCEPTED")) {
                myName = line.substring(13);
                if (myName == "Admin") {
                    errorType = "[ERROR: Your names 'Admin'? Seems legit...]";
                    JOptionPane.showMessageDialog(frame, errorType);
                    System.exit(0);
                } else if (myName == "ADMIN") {
                    errorType = "[ERROR: Your names 'Admin'? Seems legit...]";
                    JOptionPane.showMessageDialog(frame, errorType);
                    System.exit(0);
                } else if (myName == "admin") {
                    errorType = "[ERROR: Your names 'Admin'? Seems legit...]";
                    JOptionPane.showMessageDialog(frame, errorType);
                    System.exit(0);
                }
                JOptionPane.showMessageDialog(frame, "Welcome " + myName + "  (" + ip + ")");
                ip = InetAddress.getLocalHost();
                textField.setEditable(true);
                //Limits message length to 47 characters (Disabled for debugging purposes)
                //textField.setDocument(new JTextFieldLimit(47));
                out.println(ip);
            } else if (line.startsWith("SERVERMESSAGE")) {
                line = line.substring(14);
                messageArea.append(line + "\n");
                messageArea.setCaretPosition(messageArea.getDocument().getLength());
            } else if (line.startsWith("SERVERCOMMAND")) {
                line = line.substring(14);
                if (line.startsWith("kick " + ip)) {
                    System.exit(0);
                }
            } else if (line.startsWith("FINGERPRINT")) {
                ip = InetAddress.getLocalHost();
                out.println(ip);
            } else if (line.startsWith("NAME")) {
                name = line.substring(5);
            } else if (line.startsWith("MESSAGE")) {
                try {
                    //DECRYPTION
                    System.out.println(line);
                    line = line.substring(8);
                    String encryptedData = line;
                    System.out.println(line.length());
                    Key key = generateKey();
                    Cipher c = Cipher.getInstance(ALGO);
                    c.init(Cipher.DECRYPT_MODE, key);
                    byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
                    byte[] decValue = c.doFinal(decordedValue);
                    String decryptedValue = new String(decValue);
                    messageArea.append(name + ": " + decryptedValue + "\n");
                    messageArea.setCaretPosition(messageArea.getDocument().getLength());
                } catch (Exception ex) {
                    Logger.getLogger(ChatClient.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    private Key generateKey() throws Exception {
        Key key = new SecretKeySpec(keyValue, ALGO);
        return key;
    }

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

ChatServer.java

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;

/**
 * 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<>();
    /**
     * 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<>();
    /**
     * The set of all the print writers for all the clients. This set is kept so
     * we can easily broadcast messages.
     */
    private static HashSet<String> bans = new HashSet<>();
    private static String[] Names = new String[50];
    private static String[] Fingerprints = new String[50];
    static int array = 0;
    static int index;

    /**
     * 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 String ban;
        private Socket socket;
        private BufferedReader in;
        private PrintWriter out;
        private Integer length;
        private String fingerprint;
        /**
         * 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.
         */
        @Override
        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");
                    name = in.readLine();
                    length = name.length();
                    if (length == 0) {
                        out.println("SUBMITNAME ERROR 1");
                        return;
                    }
                    if (name == "null") {
                        out.println("SUBMITNAME ERROR 1");
                        return;
                    }
                    if (name == "Admin") {
                        out.println("SUBMITNAME ERROR 2");
                        return;
                    }
                    if (name == "admin") {
                        out.println("SUBMITNAME ERROR 2");
                        return;
                    }
                    if (name == "ADMIN") {
                        out.println("SUBMITNAME ERROR 2");
                        return;
                    }
                    synchronized (bans) {
                        out.println("FINGERPRINT");
                        fingerprint = in.readLine();
                        if (bans.contains(fingerprint)) {
                            out.println("SUBMITNAME ERROR 3");
                            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 " + name);
                Names[array] = name;
                System.out.println(Names[array]);
                Fingerprints[array] = in.readLine();
                System.out.println(Fingerprints[array]);
                array = array + 1;

                //Announces that user is Online
                for (PrintWriter writer : writers) {
                    writer.println("SERVERMESSAGE " + name + " is now Online");
                }
                System.out.println(Names);

                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) {
                        if (input.startsWith("/")) {
                            if ("Electron".equals(name)) {
                                //Tracks the hostname and IP address of the user
                                if (input.startsWith("/track")) {
                                    input = input.substring(7);
                                    index = java.util.Arrays.asList(Names).indexOf(input);
                                    out.println("SERVERMESSAGE " + input + ": " + Fingerprints[index]);
                                    //Bans the user from returning until Server is deactivated    
                                } else if (input.startsWith("/ban")) {
                                    input = input.substring(5);
                                    index = java.util.Arrays.asList(Names).indexOf(input);
                                    synchronized (bans) {
                                        bans.add(Fingerprints[index]);
                                    }
                                    out.println("SERVERMESSAGE " + input + "(" + Fingerprints[index] + ")" + " is now banned");
                                } else if (input.startsWith("/kick")) {
                                    input = input.substring(6);
                                    index = java.util.Arrays.asList(Names).indexOf(input);
                                    out.println("SERVERCOMMAND kick " + Fingerprints[index]);
                                } else if (input.startsWith("/deactivate")) {
                                    writer.println("SERVERMESSAGE The server is now Offline");
                                    System.exit(0);
                                }
                            }
                        } else {
                            writer.println("NAME " + name);
                            writer.println("MESSAGE " + 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("SERVERMESSAGE " + name + " is now Offline");
                }
                if (name != null) {
                    names.remove(name);
                }
                if (out != null) {
                    writers.remove(out);
                }
                try {
                    socket.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

我尝试改变我发送的文本量,添加更多填充,甚至尝试从1 - 1000的每个输入,以查看是否有模式(没有....)。我在互联网上进行过研究,但无济于事。

感谢帮助Noob程序员完成他的第二个程序(真的。)!祝你有愉快的一周!

1 个答案:

答案 0 :(得分:2)

这是事实。 AES是一个块密码,这意味着它一次只加密16个字节。

这意味着您必须将消息填充为16的倍数,并多次调用它,每个块一次。为了获得良好的加密效果,您还需要添加IV,并将其应用于所有块。这可能很难自己实现,所以你应该让你的库来做。 javax.crypto库已经可以做到。

解决方案是选择另一种算法,如the Javadocs中所示(或查阅完整列表here)。

尝试使用algo:AES/CBC/PKCS5Padding