2012-05-27 3 views
5

Ich verwende Java-Code, der den Inhalt einer Textdatei mit Blowfish verschlüsselt. Wenn ich die verschlüsselte Datei zurückkonvertiere (d. H. Entschlüssle), fehlt der Zeichenfolge ein Zeichen vom Ende. Irgendwelche Ideen warum? Ich bin sehr neu in Java und habe stundenlang damit herumgespielt, ohne Glück.Java - Fehlende letzte Zeichen beim Verschlüsseln mit Blowfish

Die Datei war_and_peace.txt enthält nur die Zeichenfolge "Dies ist ein Text". decrypted.txt enthält "This is any tex" (ohne t am Ende). Hier ist der Java-Code:

public static void encrypt(String key, InputStream is, OutputStream os) throws Throwable { 
    encryptOrDecrypt(key, Cipher.ENCRYPT_MODE, is, os); 
} 

public static void decrypt(String key, InputStream is, OutputStream os) throws Throwable { 
    encryptOrDecrypt(key, Cipher.DECRYPT_MODE, is, os); 
} 

private static byte[] getBytes(String toGet) 
{ 
    try 
    { 
     byte[] retVal = new byte[toGet.length()]; 
     for (int i = 0; i < toGet.length(); i++) 
     { 
      char anychar = toGet.charAt(i); 
      retVal[i] = (byte)anychar; 
     } 
     return retVal; 
    }catch(Exception e) 
    { 
     String errorMsg = "ERROR: getBytes :" + e; 
     return null; 
    } 
} 

public static void encryptOrDecrypt(String key, int mode, InputStream is, OutputStream os) throws Throwable { 


    String iv = "12345678"; 
    byte[] IVBytes = getBytes(iv); 
    IvParameterSpec IV = new IvParameterSpec(IVBytes); 


    byte[] KeyData = key.getBytes(); 
    SecretKeySpec blowKey = new SecretKeySpec(KeyData, "Blowfish"); 
    //Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding"); 
    Cipher cipher = Cipher.getInstance("Blowfish/CBC/NoPadding"); 

    if (mode == Cipher.ENCRYPT_MODE) { 
     cipher.init(Cipher.ENCRYPT_MODE, blowKey, IV); 
     CipherInputStream cis = new CipherInputStream(is, cipher); 
     doCopy(cis, os); 
    } else if (mode == Cipher.DECRYPT_MODE) { 
     cipher.init(Cipher.DECRYPT_MODE, blowKey, IV); 
     CipherOutputStream cos = new CipherOutputStream(os, cipher); 
     doCopy(is, cos); 
    } 
} 

public static void doCopy(InputStream is, OutputStream os) throws IOException { 
    byte[] bytes = new byte[4096]; 
    //byte[] bytes = new byte[64]; 
    int numBytes; 
    while ((numBytes = is.read(bytes)) != -1) { 
     os.write(bytes, 0, numBytes); 
    } 
    os.flush(); 
    os.close(); 
    is.close(); 
} 

public static void main(String[] args) { 


    //Encrypt the reports 
    try { 
     String key = "squirrel123"; 

     FileInputStream fis = new FileInputStream("war_and_peace.txt"); 
     FileOutputStream fos = new FileOutputStream("encrypted.txt"); 
     encrypt(key, fis, fos); 

     FileInputStream fis2 = new FileInputStream("encrypted.txt"); 
     FileOutputStream fos2 = new FileOutputStream("decrypted.txt"); 
     decrypt(key, fis2, fos2); 
    } catch (Throwable e) { 
     e.printStackTrace(); 
    } 
} 

`

+0

Dummy-Zeichen einfügen – SjB

+0

Ich habe die Länge der Zeichenfolge verdoppelt und den gleichen Code ausgeführt und es hat funktioniert! Ich weiß nicht, warum es überhaupt nicht funktioniert hat. Vielen Dank. –

+0

Verwandte http://StackOverflow.com/Questions/18146985/GET-decrypted-inputstream – Qwerky

Antwort

1

Keys sind binär und String ist kein Behälter für binäre Daten. Verwende ein Byte [].

+0

Ich habe eine Methode namens getBytes(), die die Schlüssel-String in ein Byte konvertiert [] - ist das, wo das Problem liegt? –

+0

Es kann ein Problem sein, wenn Sie nicht genau die gewünschte Kodierung angeben. – rossum

7

Es gibt ein paar Dinge hier nicht optimal.

Aber lassen Sie uns zuerst Ihr Problem lösen. Der Grund, warum der letzte Teil Ihrer Eingabe irgendwie fehlt, ist das von Ihnen angegebene Padding: none! Ohne eine Auffüllung anzugeben, kann die Cipher nur mit Blöcken voller Länge (8 Byte für Blowfish) arbeiten. Überschüssige Eingaben, die weniger als einen Block lang sind, werden automatisch verworfen, und Ihr Text fehlt. Im Detail: "Dies ist ein Text" ist 17 Bytes lang, so dass zwei volle Blöcke entschlüsselt werden, und das letzte 17. Byte, "t", wird verworfen.

Verwenden Sie immer eine Auffüllung in Kombination mit symmetrischen Blockchiffren, PKCS5Padding ist in Ordnung.

Als nächstes, wenn Sie mit Cipher arbeiten, müssen Sie nicht Ihre eigenen getBytes() implementieren - es gibt String#getBytes bereits die Arbeit für Sie erledigen. Achten Sie darauf, beim Einlesen der Bytes mit der gleichen Zeichencodierung zu arbeiten, und wenn Sie später eine String von Bytes rekonstruieren, ist dies eine häufige Fehlerquelle.

Sie sollten sich die JCE docs ansehen, sie werden Ihnen helfen, einige der häufigsten Fehler zu vermeiden.

Zum Beispiel ist die direkte Verwendung von String-Schlüsseln für die symmetrische Kryptographie ungeeignet, sie enthalten nicht genug Entropie, was es einfacher macht, einen solchen Schlüssel zu erzwingen. Die JCE gibt Ihnen die KeyGenerator Klasse und Sie sollten immer verwenden, wenn Sie nicht genau wissen, was Sie tun. Es erzeugt einen sicheren zufälligen Schlüssel der passenden Größe für Sie, aber zusätzlich, und das ist etwas, was die Leute dazu neigen zu vergessen, es wird auch sicherstellen, dass es keinen schwachen Schlüssel erzeugt. Zum Beispiel gibt es bekannte schwache Schlüssel für Blowfish, die im praktischen Gebrauch vermieden werden sollten.

Schließlich sollten Sie bei der CBC-Verschlüsselung keine deterministische IV verwenden. Es gibt einige kürzliche Angriffe, die es möglich machen, dies auszunutzen, was zu einer vollständigen Wiederherstellung der Nachricht führt, und das ist offensichtlich nicht cool. Die IV sollte immer zufällig ausgewählt werden (unter Verwendung einer SecureRandom), um es unvorhersehbar zu machen. Cipher tut dies für Sie standardmäßig, Sie können einfach die verwendete IV nach der Verschlüsselung mit Cipher#getIV erhalten.

Ein weiterer Hinweis, weniger sicherheitsrelevant: Sie sollten Streams in einem finally Block schließen, um sicherzustellen, dass sie um jeden Preis geschlossen sind - andernfalls bleibt Ihnen im Falle einer Ausnahme eine offene Dateikennung übrig.

Hier ist eine aktualisierte Version des Codes, die alle diese Aspekte berücksichtigt (hatte Strings verwenden anstelle von Dateien in main, aber man kann einfach ersetzen Sie es mit dem, was du da hatte):

private static final String ALGORITHM = "Blowfish/CBC/PKCS5Padding"; 

/* now returns the IV that was used */ 
private static byte[] encrypt(SecretKey key, 
           InputStream is, 
           OutputStream os) { 
    try { 
     Cipher cipher = Cipher.getInstance(ALGORITHM); 
     cipher.init(Cipher.ENCRYPT_MODE, key); 
     CipherInputStream cis = new CipherInputStream(is, cipher); 
     doCopy(cis, os); 
     return cipher.getIV(); 
    } catch (Exception ex) { 
     throw new RuntimeException(ex); 
    } 
} 

private static void decrypt(SecretKey key, 
          byte[] iv, 
          InputStream is, 
          OutputStream os) 
{ 
    try { 
     Cipher cipher = Cipher.getInstance(ALGORITHM); 
     IvParameterSpec ivSpec = new IvParameterSpec(iv); 
     cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); 
     CipherInputStream cis = new CipherInputStream(is, cipher); 
     doCopy(cis, os); 
    } catch (Exception ex) { 
     throw new RuntimeException(ex); 
    } 
} 

private static void doCopy(InputStream is, OutputStream os) 
throws IOException { 
    try { 
     byte[] bytes = new byte[4096]; 
     int numBytes; 
     while ((numBytes = is.read(bytes)) != -1) { 
      os.write(bytes, 0, numBytes); 
     } 
    } finally { 
     is.close(); 
     os.close(); 
    } 
} 

public static void main(String[] args) { 
    try { 
     String plain = "I am very secret. Help!"; 

     KeyGenerator keyGen = KeyGenerator.getInstance("Blowfish"); 
     SecretKey key = keyGen.generateKey(); 
     byte[] iv; 

     InputStream in = new ByteArrayInputStream(plain.getBytes("UTF-8")); 
     ByteArrayOutputStream out = new ByteArrayOutputStream(); 
     iv = encrypt(key, in, out); 

     in = new ByteArrayInputStream(out.toByteArray()); 
     out = new ByteArrayOutputStream(); 
     decrypt(key, iv, in, out); 

     String result = new String(out.toByteArray(), "UTF-8"); 
     System.out.println(result); 
     System.out.println(plain.equals(result)); // => true 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
} 
2

Sie habe deine CipherInputStream und CipherOutputStream durcheinander gebracht. Zum Verschlüsseln lesen Sie von einem einfachen Eingabestream und schreiben Sie in eine CipherOutputStream. Um zu entschlüsseln ... bekommst du die Idee.

EDIT:

Was passiert ist, dass Sie NOPADDING angegeben haben und Sie versuchen, eine CipherInputStream zu verschlüsseln verwenden. Die ersten 16 Bytes bilden zwei gültige vollständige Blöcke und sind somit korrekt verschlüsselt. Dann ist nur noch 1 Byte übrig, und wenn die CipherInputStream-Klasse die Dateiendeanzeige erhält, führt sie eine Cipher.doFinal() für das Chiffrierobjekt aus und empfängt eine IllegalBlockSizeException. Diese Ausnahme wird verschluckt und liest -1 zurück, um das Dateiende anzuzeigen. Wenn Sie jedoch PKCS5PADDING verwenden, sollte alles funktionieren.

EDIT 2:

Prägungs ist richtig, dass das eigentliche Problem ist einfach, dass es schwierig ist und fehleranfällig die CipherStream Klassen mit der NOPADDING Option zu verwenden. Tatsächlich geben diese Klassen explizit an, dass sie jede von der zugrundeliegenden Cipher-Instanz geworfene Sicherheitsausnahme stillschweigend verschlucken, so dass sie für Anfänger vielleicht keine gute Wahl sind.

+0

Das ist wahrscheinlich der intuitivste Weg, aber es ist nicht notwendig, es so zu machen. Der Weg des OPs funktioniert - CipherInputStream und CipherOutputStream sind FilterInputStreams bzw. FilterOutputStreams, also spielt die "Richtung", in der Sie sie anwenden, keine Rolle. – emboss

+1

Ich müsste den Quellcode für CipherInputStream sehen, aber es ignoriert anscheinend alle Teilblöcke, wenn Sie NOPADDING angeben. Deshalb liest es nur die ersten 16 Bytes und scheint nicht der 17. zu sein. –

+0

Ah, OK, ich verstehe was du meinst. Ich habe mich gefragt, warum es keine Ausnahme auslöst, ich werde überprüfen, ob CipherOutputStream sich anders verhält. Wenn ja, wäre das ein Fehler, findest du nicht? – emboss

Verwandte Themen