2012-05-27 10 views
5

Blowfish kullanarak bir metin dosyasının içeriğini şifreleyen bir java kodu kullanıyorum. Şifrelenmiş dosyayı geri dönüştürdüğümde (yani şifresini çözerken), dize sonunda bir karakter eksik. Herhangi bir fikir neden? Java için çok yeni ve şanssız saatlerce bununla uğraşıyorum.Java - Blowfish kullanarak şifrelenirken son karakterler eksik

Dosya war_and_peace.txt sadece "Bu bir metin" dizesini içerir. decrypted.txt "Bu biraz tex" içerir (sonunda hiçbir t ile).

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

kukla karakter ekleme – SjB

+0

Dizenin uzunluğunu iki katına çıkardım ve aynı kodu çalıştırdım ve işe yaradı! Neden ilk etapta işe yaramadığını bilmiyorum. Teşekkürler. –

+0

İlgili http://stackoverflow.com/questions/18146985/get-decrypted-inputstream – Qwerky

cevap

1

Tuşlar ikilidir ve String ikili veri için bir kap değildir: Burada java kodudur. Bir bayt [] kullanın.

+0

Anahtar dizeyi bir bayt [] dönüştüren getBytes() adında bir yöntem var - sorunun nerede yattığı yer burası? –

+0

Tam olarak istediğiniz kodlamayı belirtmezseniz sorun olabilir. – rossum

7

burada uygun değildir birkaç şey vardır.

Ama önce sorununuzu çözelim. Girdinizin son bölümünün neden eksik olduğunun nedeni, belirttiğiniz doldurmadır: Yok! Bir dolgu belirtmeden, Cipher tam uzunluklu bloklarda (Blowfish için 8 bayt) çalışabilir. Bir bloktan daha uzun olan fazla giriş sessizce atılır ve eksik metniniz vardır. Ayrıntılı olarak: "Bu bir metindir" 17 bayt uzunluğundadır, bu yüzden iki tam blok şifresi çözülür ve son 17 bayt "t" atılır.

Her zaman simetrik blok şifrelere sahip bir dolgu kullanın, PKCS5Padding iyidir. Cipher ile çalışırken

Sonra, sen getBytes() kendi uygulamak gerekmez - String#getBytes zaten sizin için iş yapıyor orada. Sadece bayt alırken aynı karakter kodlaması üzerinde çalıştığınızdan emin olun ve daha sonra byte'lardan String yeniden oluştururken, yaygın bir hata kaynağıdır.

JCE docs'a bir göz atmanız gerekir; bunlar, yaygın bazı hatalardan kaçınmanıza yardımcı olurlar. Örneğin, String tuşlarını doğrudan kullanmak simetrik kriptografi için kullanılmaz, yeterli entropi içermez, bu da böyle bir anahtarı kaba kuvveti kolaylaştırır. JCE size KeyGenerator sınıfını verir ve ne yaptığınızı tam olarak bilmediğiniz sürece her zaman kullanmalısınız. Sizin için uygun büyüklükte güvenli bir rastgele anahtar oluşturur, ancak buna ek olarak, ve insanlar unutmaya eğilimli bir şeydir, aynı zamanda zayıf bir anahtar yaratmayacağından emin olacaktır. Örneğin, pratik kullanımda kaçınılması gereken Blowfish için bilinen zayıf anahtarlar vardır. Son olarak, CBC şifrelemesi yaparken deterministik bir IV kullanmamalısınız. Bunu istismar etmeyi mümkün kılan bazı son saldırılar vardır, bu da mesajın tamamen kurtarılmasına neden olur ve bu kesinlikle hoş değildir. IV, tahmin edilemez hale getirmek için daima rastgele seçilmelidir (bir SecureRandom kullanarak). Cipher bunu sizin için varsayılan olarak yapar, Cipher#getIV ile şifrelemeden sonra kullanılmış IV'i elde edebilirsiniz.

Başka bir notta, daha az güvenlikle ilgili: finally bloğunda akışları kapatıp kapatılmalarını sağlamak için kapatmanız gerekir; aksi takdirde istisna durumunda açık dosya tanıtıcısı bırakılır.İşte

dikkate Tüm bu yönleriyle sürer kod güncelleştirilmiş bir sürümü ( main yerine dosyaların Dizeleri kullanmak zorunda kaldı, ama orada ne vardı sadece ile değiştirin):

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

You CipherInputStream ve CipherOutputStream'unuzu karıştırın. Şifrelemek için düz bir girişten okur ve bir CipherOutputStream'a yazarsınız. Şifreyi çözmek için ... fikri anladın.

DÜZENLEME:

Ne oluyor size CipherInputStream kullanarak şifrelemek çalışıyorsunuz NOPADDING belirtilen ve olmalarıdır. İlk 16 bayt iki geçerli tam blok oluşturur ve bu nedenle doğru şekilde şifrelenir. Daha sonra sadece 1 byte kalmıştır ve CipherInputStream sınıfı dosya sonu göstergesini aldığında, şifre nesnesi üzerinde bir Cipher.doFinal() gerçekleştirir ve bir IllegalBlockSizeException alır. Bu istisna yutulur ve dosya sonu değerini gösteren okuma -1 döndürür. Ancak eğer PKCS5PADDING kullanırsanız, her şey çalışmalıdır.

DÜZENLEME 2: Asıl mesele o zor ve hataya açık NOPADDING seçeneğiyle CipherStream sınıfları kullanmak olduğunu basitçe olması ile

kabartma doğrudur. Aslında, bu sınıflar açıkça altta yatan Cipher örneğinin attığı her Güvenlik özel durumunun sessizce yutulduğunu açıkça belirtir, böylece yeni başlayanlar için belki de iyi bir seçim değildir.

+0

Muhtemelen bunu yapmanın en sezgisel yolu budur, ancak bu şekilde yapılması gerekli değildir. OP'nin çalışma şekli - CipherInputStream ve CipherOutputStream sırasıyla FilterInputStreams ve FilterOutputStreams'tir, böylece onları uyguladığınız "yön" önemli değildir. – emboss

+1

CipherInputStream için kaynak koduna bakmam gerekir, ancak NOPADDING belirtirseniz herhangi bir kısmi engellemeyi yok sayar. Bu yüzden sadece ilk 16 baytı okuyor ve görünüşe göre 17. değil. –

+0

Ah, tamam, ne demek istediğini anladım. Neden bir istisna atmadığını merak ediyordum, CipherOutputStream'in farklı davranıp davranmadığını kontrol edeceğim. Eğer öyleyse, bir hata olur, sence de öyle değil mi? – emboss