DES шифровать/дешифровать из файла

Я пишу программу, в которой я беру строку, шифрую ее, а затем записываю в файл. Затем позже я читаю из файла строку, расшифровываю ее, а затем изменяю. Вот мой код для шифрования/дешифрования DES:

/* class for crypting and decrypting a file */
class DESEncrypter
{
private Cipher encryptionCipher;
private Cipher decryptionCipher;

public DESEncrypter (SecretKey key) throws Exception
{
encryptionCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
encryptionCipher.init(Cipher.ENCRYPT_MODE, key);
decryptionCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
decryptionCipher.init(Cipher.DECRYPT_MODE, key);
}

/* write to 'out' the encryption of the information read from 'in' */
public String encrypt(String unencryptedString)
{
    String encryptedString = "";

    try {
        byte[] unencryptedByteArray = unencryptedString.getBytes("UTF8");

        byte[] encryptedBytes = this.encryptionCipher.doFinal(unencryptedByteArray);

        encryptedString = new sun.misc.BASE64Encoder().encode(encryptedBytes);

    } catch (Exception ex) {
        Logger.getLogger(DESEncrypter.class.getName()).log(Level.SEVERE, null, ex);
    }

    return encryptedString;
}

private static String bytes2String(byte[] bytes)
{

    StringBuffer stringBuffer = new StringBuffer();
    for (int i = 0; i < bytes.length; i++) 
    {
        stringBuffer.append((char) bytes[i]);
    }

    return stringBuffer.toString();
}

/* write to 'out' the information obtained by decrypting the information read from 'in' */
public String decrypt (String encryptedString) throws UnsupportedEncodingException
{
    byte[] unencryptedByteArray = new byte[4096];

    try {
        // Encode bytes to base64 to get a string
        byte[] decodedBytes = new sun.misc.BASE64Decoder().decodeBuffer(encryptedString);

       // Decrypt
       unencryptedByteArray = this.decryptionCipher.doFinal(decodedBytes);     
    } catch (Exception ex) {
        Logger.getLogger(DESEncrypter.class.getName()).log(Level.SEVERE, null, ex);
    }

    return bytes2String(unencryptedByteArray);
}
} 

И это функция, в которой я записываю зашифрованную строку в файл:

public void writeToFileEncrypted(String filename, String owner, String departament)
{
try 
    {
        BufferedReader br = new BufferedReader(new FileReader(new File("files_encrypted")));
        String crypt = "";
        String aux;
        while ((aux = br.readLine()) != null)
        {
            crypt += aux;
        }
        br.close();

        String info = this.server.crypt.decrypt(crypt);
        info += filename + " " + owner + " " + departament + "\n";

        /* delete the old encryption */
        File temp = new File("files_encrypted");
        temp.delete();

        String infoCrypt = this.server.crypt.encrypt(info); 

        File newFiles = new File("files_encrypted");
        if (newFiles.createNewFile() == false) 
        {
    log.severe("Failed to re-create the 'files_encrypted' file when trying to add a new file");
    return; 
        }

        BufferedWriter bw = new BufferedWriter(new FileWriter(newFiles));
        bw.write(infoCrypt);
        bw.close();
    }
    catch (Exception e)
    {
        log.warning("An exception was caught while trying to remove '" + clientName + "' from the banned list");
        e.printStackTrace();
        return;
}
}

Пока сервер работает, я могу внести изменения в эту строку из файла (запускать эту функцию много раз). Проблема в том, что когда я закрываю сервер, а затем открываю его снова, потому что получаю сообщение об ошибке: javax.crypto.BadPaddingException: данный последний блок неправильно заполнен

Вот как я читаю из файла, когда сервер открывается:

BufferedReader br = new BufferedReader(new FileReader(new File("files_encrypted")));
String crypto = new String();
String aux;
while ((aux = br.readLine()) != null)
{
    crypto += aux;
    readBytes++;
}
br.close();
System.out.println(readBytes);
info = this.crypt.decrypt(crypto); 

Почему я получаю эту ошибку? Что я делаю неправильно? Я должен записать зашифрованную строку в файл каким-то другим способом?

ПОЗЖЕ РЕДАКТИРОВАТЬ:

Я изменил функцию, которая считывает строку из файла, расшифровывает ее, модифицирует, шифрует, а затем записывает в файл.

public void writeToFileEncrypted(String filename, String owner, String departament)
{
try 
    {
        File f = new File("files_encrypted");
        int nrRead = 0;
        String info = null;
        FileInputStream fis = new FileInputStream(f);
        StringBuffer sb = new StringBuffer();
        int ch;
        while ((ch = fis.read()) != -1)
        {
            sb.append((char)ch);
            nrRead++;
        }
        fis.close();

        StringBuilder sba = null;
        if (nrRead != 0)
        {
            info = this.server.crypt.decrypt(new String(sb.toString().getBytes("UTF-8"), "UTF-8"));
            sba = new StringBuilder(info);
            sba.append(filename + " " + owner + " " + departament + " ");
        }
        else
        {
            sba = new StringBuilder(filename + " " + owner + " " + departament + " ");
        }

        /* delete the old encryption */
        File temp = new File("files_encrypted");
        temp.delete();
        //System.out.println("before: " + sba.toString());
        String infoCrypt = this.server.crypt.encrypt(sba.toString()); 
        //System.out.println("after: " + infoCrypt);
        File newFiles = new File("files_encrypted");
        if (newFiles.createNewFile() == false) 
        {
    log.severe("Failed to re-create the 'files_encrypted' file when trying to add a new file");
    return; 
        }

        FileOutputStream fos = new FileOutputStream(newFiles);
        fos.write(infoCrypt.getBytes("UTF-8"));
        fos.flush();
        fos.close();
    }
    catch (Exception e)
    {
        log.warning("An exception was caught while trying to remove '" + clientName + "' from the banned list");
        e.printStackTrace();
        return;
}
}

Я также изменил, где я читал информацию из файла, когда сервер открывается в первый раз:

FileInputStream fis = new FileInputStream(f);
StringBuffer sb = new StringBuffer();
int ch;
while ((ch = fis.read()) != -1)
{
    sb.append((char)ch);
    readBytes++;
}

fis.close();
if (readBytes != 0)
{
    System.out.println("on: " + sb.toString());
    info = this.crypt.decrypt(new String(sb.toString().getBytes("UTF-8"), "UTF-8"));                
    System.out.println("load: " + info);
}
} 

В System.out.println с «on:» я читаю из файла именно то, что я написал в зашифрованном виде, без каких-либо пробелов или новых строк. Если я читаю с помощью read(buffer), где буфер равен byte[], кажется, что добавляется много пробелов.

Хотя я сделал все эти изменения, я все еще получаю сообщение об ошибке javax.crypto.BadPaddingException: данный последний блок не заполнен должным образом.

Кто-нибудь знает, что здесь происходит?


person Stanciu Alexandru-Marian    schedule 13.12.2012    source источник
comment
Мое первое предположение было бы вашим методом чтения. Прежде всего, это плохая практика - использовать конкатенацию строк так, как вы это делаете. Используйте StringBuilder. Во-вторых, вы используете readline для двоичных данных. В-третьих, вы кодируете (getBytes) в UTF8. Имейте в виду, что стандартом Java является UTF 16. Это может нарушить ваше чтение.   -  person Fildor    schedule 14.12.2012
comment
@Fildor - зашифрованные данные имеют кодировку Base64, поэтому это не двоичные данные. Кроме того, getBytes() работает нормально, если для кодирования и декодирования используется правильная кодировка. java использует внутри UTF-16, но это не имеет значения.   -  person jtahlborn    schedule 14.12.2012
comment
Это строка, да. Но String представляет двоичные данные. Если бы он прочитал его в byte[], а затем создал строку, у него было бы больше контроля, по крайней мере, во время отладки. И новые строки будут удалены. Но новая строка может не быть здесь новой строкой.   -  person Fildor    schedule 14.12.2012


Ответы (4)


Здесь есть несколько вещей.

private static String bytes2String(byte[] bytes)

Является изворотливым, вы переводите байт в char в этом методе, поэтому здесь не указана кодировка символов. Чтобы преобразовать байты в символы, вы должны просто использовать конструктор String, который принимает массив байтов и кодировку. например

    byte[] tmp = new byte[10];      
    String a = new String(tmp, "UTF-8");

Будьте осторожны, используя BufferedReaders + .readLine() - это удалит любые символы новой строки из вашего файла, когда вы его читаете, если вы не добавите их обратно в свой буфер. Хотя я не думаю, что это ваша проблема.

Но я думаю, что лучший способ упростить ваш код — записать закодированные байты через OutputStream непосредственно в файл. Если вам не нужно отправлять содержимое файла через транспорт, который не любит двоичные данные, нет необходимости кодировать base64. Просто используйте Input/OutputStreams для записи зашифрованных байтов прямо на диск.

ОТВЕТ НА ПОСЛЕДНЕЕ РЕДАКТИРОВАНИЕ:

Вы все еще смешиваете использование двоичных данных (байты) и символьных данных (строка/символы). Вы не можете делать такие вещи, как:

    int ch;
    while ((ch = fis.read()) != -1)
    {
        sb.append((char)ch);

Входной поток перенастраивает байты, байт не является символом, и простое приведение его к единице вызовет проблемы. При использовании шифрования выходные данные операции шифрования представляют собой двоичные данные, а входные данные операции дешифрования также являются двоичными данными. Тот факт, что вы шифруете текст, — это то, с чем вы имеете дело до того, как произойдет шифрование, и после того, как произойдет расшифровка. Ваша основная операция должна идти по следующим направлениям.

  • Возьмите текст, который вы хотите зашифровать, и преобразуйте его в байты, указав кодировку с помощью .getBytes(String charsetName) в вашей строке.
  • Передайте эти байты в свою процедуру шифрования
  • Запишите полученные байты прямо на диск

Чтобы расшифровать:

  • Прочитать байты из файла
  • Передайте байты в процедуру расшифровки (в виде байтов! Никаких строк/текста)
  • Возьмите выходные байты и заново создайте строку, используя новую строку (byte [] bytes, String charsetName), указав ту же кодировку, что и раньше.

Вы можете найти следующие (непроверенные, но должны работать) методы полезными:

public byte[] readBinaryFile(File f) throws IOException
{       
    byte[] contents = new byte[(int)f.length()];
    BufferedInputStream bis = null;
    try
    {
        bis = new BufferedInputStream(new FileInputStream(f));
        DataInputStream dis = new DataInputStream(bis);
        dis.readFully(contents);
    }
    finally
    {
        if(bis != null)
        {
            bis.close();
        }
    }           
    return contents;            
}

public void writeBinaryFile(byte[] contents, File f) throws IOException
{
    BufferedOutputStream bos = null;
    try
    {
        bos = new BufferedOutputStream(new FileOutputStream(f));
        bos.write(contents);
    }
    finally
    {
        if(bos != null)
        {
            bos.close();
        }
    }           
}

Таким образом, вам также нужно будет изменить интерфейс и внутренности ваших методов шифрования и дешифрования, чтобы они принимали и возвращали массивы байтов и отказывались от кодировки base64.

person Malcolm Smith    schedule 13.12.2012
comment
Я сделал изменения, которые вы сказали (я добавил раздел ПОСЛЕДНЯЯ РЕДАКЦИЯ), но все равно получаю то же исключение. - person Stanciu Alexandru-Marian; 14.12.2012
comment
Большое спасибо. Я сам нашел решение, но похоже на ваш пример. - person Stanciu Alexandru-Marian; 28.12.2012

У вас несколько проблем. Процесс чтения и расшифровки должен быть симметричен процессу шифрования и записи. Но

  • вы преобразуете свою строку в byte[] с помощью getBytes("UTF8"), что нормально, но вы не используете new String(byte[], "UTF8") для выполнения обратной операции.
  • вы записываете в файл всю строку, включая потенциальные разрывы строк, но читаете ее построчно и объединяете каждую строку, теряя при этом разрывы строк. Вы должны прочитать каждый написанный символ.

Кроме того, не следует полагаться на недокументированные, неподдерживаемые классы, такие как sun.misc.Base64Encoder/Decoder. Используйте Apache commons-codec, чтобы найти задокументированную кодировку Base64, которая гарантированно сохранится, когда выйдет следующий JDK, и которую можно использовать на каждой JVM, включая JVM других производителей.

person JB Nizet    schedule 13.12.2012
comment
потеря разрывов строк не должна влиять на процесс шифрования. - person jtahlborn; 14.12.2012

Я думаю дело в инициализации

SecureRandom sr = new SecureRandom();
cipher.init( Cipher.DECRYPT_MODE, desKey, sr);

person Ibrahim    schedule 13.12.2012

Не уверен, что это основная проблема, но когда вы возвращаете расшифрованную строку из decrypt(), вы должны использовать:

return new String(unencryptedByteArray, "UTF-8");
person jtahlborn    schedule 13.12.2012