Мое шифрование xor не работает со сложными файлами

Просто для удовольствия я пытался создать файловый шифратор, который выполняет побитовое шифрование xor против 128-битного ключа. Кажется, это работает для простых текстовых файлов с использованием некоторых редакторов, но для других и более сложных файлов я сталкиваюсь с ошибкой «поврежденный файл». Если я дважды выполняю шифрование текстового файла, в Gedit он отображает содержимое исходного файла, как и должно быть, но в Блокноте я просто получаю целую кучу вопросительных знаков.

Я написал этот код на Java, однако часть шифрования довольно универсальна. Ключ в виде массива из 8 16-битных символов:

char[] key = {26372, 15219, 53931, 50406, 26072, 23469, 25002, 37812};

Чтобы зашифровать файл, у меня есть цикл, который запускает этот код:

builder.append((char)(bR.read() ^ key[pos++]));
if(pos == 8) pos = 0; //returns to first position in key

Это считывает следующий символ из файла с помощью BufferedReader, выполняет операцию XOR против следующего символа в ключе и добавляет его к StringBuilder.

Вот весь код:

import java.awt.BorderLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;


public class Encryptor implements Runnable {

    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            SwingUtilities.invokeAndWait(new Encryptor());
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    JFrame frame;
    JButton bCancel, bEncrypt;
    JTextArea statusArea;
    JScrollPane statusScroll;
    JProgressBar progressBar;
    String fileContents, fileType, fileName;
    EncryptSwingWorker encryptWorker;
    char[] key = {
        26372, 15219, 53931, 50406, 26072, 23469, 25002, 37812};

    public void run() {

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch(Exception e) {}

        frame = new JFrame();

        statusArea = new JTextArea();
        statusArea.setEditable(false);
        statusScroll = new JScrollPane(statusArea);
        progressBar = new JProgressBar();
        progressBar.setValue(0);

        bCancel = new JButton("Cancel");
        bCancel.setEnabled(false);
        bCancel.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                int option = JOptionPane.showOptionDialog(frame,
                        "Are you sure you want to cancel?",
                        "Warning", JOptionPane.YES_NO_OPTION,
                        JOptionPane.QUESTION_MESSAGE, null,
                        null, null
                );
                if(option == 0) {
                    encryptWorker.cancel(true);
                    statusArea.append("Encryption cancelled\n");
                    progressBar.setValue(0);
                    progressBar.setIndeterminate(false);
                    bCancel.setEnabled(false);
                    bEncrypt.setEnabled(true);
                }
            }

        });
        bEncrypt = new JButton("Encrypt file...");
        bEncrypt.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                encryptWorker = Encryptor.this.new EncryptSwingWorker();
                encryptWorker.execute();
            }

        });

        JPanel top = new JPanel(new BorderLayout());
        top.add(bEncrypt, BorderLayout.CENTER);
        top.add(bCancel, BorderLayout.EAST);
        frame.getContentPane().add(top, BorderLayout.NORTH);
        frame.getContentPane().add(statusScroll, BorderLayout.CENTER);
        frame.getContentPane().add(progressBar, BorderLayout.SOUTH);

        frame.setSize(200, 150);
        frame.setResizable(false);
        frame.setTitle("sEncryptor");
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setIconImage(Toolkit.getDefaultToolkit().getImage("encryptor.png"));
    }

    void encrypt() throws IOException {
        JFileChooser fc = new JFileChooser();
        int returnVal = fc.showOpenDialog(frame);

        if(returnVal == JFileChooser.APPROVE_OPTION) {
            if(fc.getSelectedFile() == null) {
                JOptionPane.showOptionDialog(frame,
                    "You did not select a file",
                    "File Open Error",
                    JOptionPane.DEFAULT_OPTION,
                    JOptionPane.WARNING_MESSAGE, null,
                    null, null
                );
                encrypt();
                return;
            }

            bCancel.setEnabled(true);
            bEncrypt.setEnabled(false);

            fileName = fc.getSelectedFile().getPath();

            statusArea.append("Calculating size...\n");
            progressBar.setIndeterminate(true);
            int size = (int)(new File(fileName).length() / 32);
            progressBar.setIndeterminate(false);
            progressBar.setMaximum(size);

            BufferedReader bR = new BufferedReader(new FileReader(fc.getSelectedFile()));
            StringBuilder builder = new StringBuilder();

            statusArea.append("Encrypting...\n");
            int pos = 0;
            for(double d = 0; bR.ready(); d += 0.03125) {
                if(encryptWorker.isCancelled()) return;
                progressBar.setValue((int)d);
                builder.append((char)(bR.read() ^ key[pos++]));
                if(pos == 8) pos = 0;
            }

            progressBar.setValue(0);
            progressBar.setIndeterminate(true);

            fileContents = builder.toString();
            bR.close();
            statusArea.append("Encryption finished\n");
            progressBar.setIndeterminate(false);
        }
    }

    void save(String file) throws IOException {
        statusArea.append("Saving...\n");
        progressBar.setIndeterminate(true);
        BufferedWriter bW = new BufferedWriter(new FileWriter(file));
        bW.write(fileContents);
        bW.close();
        progressBar.setIndeterminate(false);
        statusArea.append("Done!\n");
    }

    void saveAs() throws IOException {
        JFileChooser fc = new JFileChooser();
        int returnVal = fc.showSaveDialog(frame);

        if(returnVal == JFileChooser.APPROVE_OPTION) {
            if(fc.getSelectedFile() == null) {
                JOptionPane.showOptionDialog(frame,
                    "You did not select a file",
                    "File Open Error",
                    JOptionPane.DEFAULT_OPTION,
                    JOptionPane.WARNING_MESSAGE, null,
                    null, null
                );
                saveAs();
                return;
            }

            save(fc.getSelectedFile().getPath());
        }
    }

    class EncryptSwingWorker extends SwingWorker<Void, Void> {

        @Override
        protected Void doInBackground() throws Exception {
            encrypt();
            if(EncryptSwingWorker.this.isCancelled()) return null;
            bCancel.setEnabled(false);
            while(true) {
                Object[] options = {"Override", "Save As...", "Cancel"};
                int option = JOptionPane.showOptionDialog(frame,
                        "Override existing file?",
                        "Save", JOptionPane.YES_NO_CANCEL_OPTION,
                        JOptionPane.QUESTION_MESSAGE, null,
                        options, options[0]
                );
                if(option == 0)
                    try {
                        save(fileName);
                    } catch(IOException e1) {
                        e1.printStackTrace();
                    }
                else if(option == 1)
                    try {
                        saveAs();
                    } catch(IOException e1) {
                        e1.printStackTrace();
                    }
                else {
                    int option1 = JOptionPane.showOptionDialog(frame,
                            "The encryption will be lost if you continue\n" +
                            "Are you sure you want to cancel?",
                            "Warning", JOptionPane.YES_NO_OPTION,
                            JOptionPane.QUESTION_MESSAGE, null,
                            null, null
                    );
                    if(option1 == 1) continue;
                    break;
                }
                break;
            }
            fileContents = "";
            bEncrypt.setEnabled(true);
            return null;
        }

    }
}

Есть ли что-то, чего мне не хватает в моем коде, или это вызывает проблемы с исправлением всего файла?


person sticks    schedule 13.06.2012    source источник
comment
Не могли бы вы пожалуйста включить только минимальный, действительно релевантный код, несостоятельность которого может быть доказана?   -  person Niklas B.    schedule 13.06.2012
comment
Боже мой, сколько кода без комментариев. Попробуйте опубликовать его на Code Review. Тем не менее, я запустил его в файле, он создал новый файл полный из 3F (63 в десятичном формате) символов. Вы можете попытаться отладить его, пройти шаг за шагом и проверить свою логику.   -  person Petr Janeček    schedule 13.06.2012
comment
После попытки прочитать исходный код вот вам кое-что в качестве рекомендации для самосовершенствования в будущем — перечисления! Так гораздо меньше ошибок, так гораздо более читабельно.   -  person Petr Janeček    schedule 13.06.2012


Ответы (1)


Понятно. Проблема как всегда в кодировке файла :). Никогда, никогда не полагайтесь на кодировку по умолчанию — в конечном итоге это обернется для вас неприятными последствиями.

Поскольку классы Reader и Writer созданы для ввода/вывода в виде открытого текста, это приводит к путанице. В этом случае Writer не знает, что делать с различными целыми числами, которые он получает, и в конечном итоге терпит неудачу. Когда вы исправляете Writer, вы должны исправить и Reader, потому что зашифрованные данные могут состоять не только из допустимых символов...

лучшее решение — игнорировать символы, использовать байты и FileInputStream, FileOutputStream< /a> (при необходимости обернутые их братьями Buffered), потому что они созданы для обработки двоичных данных. Таким образом, вы в конечном итоге будете читать/записывать именно те данные, которые собираетесь читать/записывать. Попробуйте собрать его сами, это отличный урок. Если вы сильно застряли, посмотрите здесь.

Самое простое решение — изменить только две строки. Обратите внимание, что это снова будет работать только для текстовых файлов! Серьезно, не путайте случайные числа и печатные символы вместе.

BufferedReader bR = new BufferedReader(new FileReader(fc.getSelectedFile()));

to

BufferedReader bR = new BufferedReader(new InputStreamReader(new FileInputStream(fc.getSelectedFile()), "UTF-8"));

а также

BufferedWriter bW = new BufferedWriter(new FileWriter(file));

to

BufferedWriter bW = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
person Petr Janeček    schedule 13.06.2012
comment
Просто добавлю: короче говоря, используйте голый поток в Java для двоичных данных, используйте считыватель/запись (с правильной кодировкой) для текста. - person nhahtdh; 13.06.2012
comment
Абсолютно верно, я вложил это в ответ. - person Petr Janeček; 13.06.2012