2016-04-17 40 views
0

所以我在從基於字符串的密碼生成密鑰時遇到了問題。加密步驟正常工作,解密步驟將運行直到底部列出的錯誤,並使解密的文件損壞。我用下面的函數來執行加密/解密:EVP_DecryptFinal_ex:糟糕的解密,Android中可能的密鑰生成問題

public static boolean decryptFileFromUri(Context context, Uri file, String keyphrase) { 
    try { 
     File f = new File(getRealPathFromURI(context, file)); 
     FileInputStream fis = new FileInputStream(f); 

     File ef = new File(f.toString().replace(".epf", "")); 
     FileOutputStream fos = new FileOutputStream(ef); 

     Log.d("HIDEMYPICS","Decrypting: " + f.toString()); 

     byte[] rawKey = getRawKey(keyphrase.getBytes("UTF8")); 
     /*KeyGenerator kgen = KeyGenerator.getInstance("AES"); 
     SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); 
     sr.setSeed(rawKey); 
     kgen.init(128, sr); // 192 and 256 bits may not be available 
     SecretKey skey = kgen.generateKey(); 
     byte[] key = skey.getEncoded();*/ 
     SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES"); 
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
     cipher.init(Cipher.DECRYPT_MODE, skeySpec); 
     // Wrap the output stream 
     CipherOutputStream cos = new CipherOutputStream(fos, cipher); 
     // Write bytes 
     int b; 
     byte[] d = new byte[8]; 
     while ((b = fis.read(d)) != -1) { 
      cos.write(d, 0, b); 
     } 
     // Flush and close streams. 
     cos.flush(); 
     cos.close(); 
     fis.close(); 

     Log.d("HIDEMYPICS","Decrypted to: " + ef.toString()); 
     return true; 
    } catch (IOException e){ 
     e.printStackTrace(); 
    } catch (NoSuchPaddingException e) { 
     e.printStackTrace(); 
    } catch (NoSuchAlgorithmException e) { 
     e.printStackTrace(); 
    } catch (InvalidKeyException e) { 
     e.printStackTrace(); 
    } 
    return false; 
} 

public static boolean encryptFileFromUri(Context context, Uri file, String keyphrase) { 
    try { 
     File f = new File(getRealPathFromURI(context, file)); 
     FileInputStream fis = new FileInputStream(f); 

     File ef = new File(f.toString() + ".epf"); 
     FileOutputStream fos = new FileOutputStream(ef); 

     Log.d("HIDEMYPICS","Encrypting: " + f.toString()); 

     byte[] rawKey = getRawKey(keyphrase.getBytes("UTF8")); 
     /*KeyGenerator kgen = KeyGenerator.getInstance("AES"); 
     SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); 
     sr.setSeed(rawKey); 
     kgen.init(128, sr); // 192 and 256 bits may not be available 
     SecretKey skey = kgen.generateKey(); 
     byte[] key = skey.getEncoded();*/ 
     SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES"); 
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
     cipher.init(Cipher.ENCRYPT_MODE, skeySpec); 
     // Wrap the output stream 
     CipherOutputStream cos = new CipherOutputStream(fos, cipher); 
     // Write bytes 
     int b; 
     byte[] d = new byte[8]; 
     while ((b = fis.read(d)) != -1) { 
      cos.write(d, 0, b); 
     } 
     // Flush and close streams. 
     cos.flush(); 
     cos.close(); 
     fis.close(); 
     Log.d("HIDEMYPICS","Encrypted to: " + ef.toString()); 
     return true; 
    } catch (IOException e){ 
     e.printStackTrace(); 
    } catch (NoSuchPaddingException e) { 
     e.printStackTrace(); 
    } catch (NoSuchAlgorithmException e) { 
     e.printStackTrace(); 
    } catch (InvalidKeyException e) { 
     e.printStackTrace(); 
    } 
    return false; 
} 

而這裏的生成原始密鑰的功能:

private static byte[] getRawKey(byte[] seed) throws NoSuchAlgorithmException { 
    KeyGenerator kgen = KeyGenerator.getInstance("AES"); 
    SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); 
    sr.setSeed(seed); 
    kgen.init(128, sr); // 192 and 256 bits may not be available 
    SecretKey skey = kgen.generateKey(); 
    byte[] raw = skey.getEncoded(); 

    String result = ""; 
    for(int index = 0; index < raw.length; index++) { 
     result += Integer.toHexString(raw[index]); 
     // maybe you have to convert your byte to int before this can be done 
     // (cannot check reight now) 
    } 

    Log.d("HIDEMYPICS","Passphrase: " + new String(seed).toString() + " Key: " + result); 
    return raw; 
} 

爲「測試」輸入字符串的輸出是如下按鍵上方的十六進制轉儲:

加密:

04-17 09:01:25.088 18341-18341/com.dcheeseman.hidemypics D/HIDEMYPICS: Encrypting: /storage/emulated/0/Download/bailout_5128280_GIFSoup.com-1.gif 
04-17 09:01:25.088 18341-18341/com.dcheeseman.hidemypics D/HIDEMYPICS: Passphrase: test Key: ffffff85affffffe21023ffffffb7ffffffe8ffffffc8214031fffffffa5b29ffffff9affffff80 

解密:

04-17 09:01:43.808 18341-18341/com.dcheeseman.hidemypics D/HIDEMYPICS: Decrypting: /storage/emulated/0/Download/bailout_5128280_GIFSoup.com-1.gif.epf 
04-17 09:01:43.808 18341-18341/com.dcheeseman.hidemypics D/HIDEMYPICS: Passphrase: test Key: ffffff8331ffffffe2ffffff87ffffffe242dffffffa61cffffffc7ffffffb4ffffffa1d74ffffff9affffff9b 

兩個加密和解密用的「測試」作爲密碼播種,但該函數返回2個不同的密鑰,這就是爲什麼我覺得我越來越壞 - 解密錯誤,這完全跟蹤上市如下:

04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err: java.io.IOException: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at javax.crypto.CipherOutputStream.close(CipherOutputStream.java:136) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at com.dcheeseman.hidemypics.AESUtils.decryptFileFromUri(AESUtils.java:102) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at com.dcheeseman.hidemypics.HideMyPics.onActivityResult(HideMyPics.java:35) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at android.app.Activity.dispatchActivityResult(Activity.java:6808) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at android.app.ActivityThread.deliverResults(ActivityThread.java:4698) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at android.app.ActivityThread.handleSendResult(ActivityThread.java:4745) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at android.app.ActivityThread.access$1500(ActivityThread.java:197) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1730) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at android.os.Handler.dispatchMessage(Handler.java:102) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at android.os.Looper.loop(Looper.java:145) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at android.app.ActivityThread.main(ActivityThread.java:6872) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at java.lang.reflect.Method.invoke(Native Method) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at java.lang.reflect.Method.invoke(Method.java:372) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404) 
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199) 

在此先感謝您對此問題的任何幫助!

+0

從問題中刪除未使用的代碼。 – zaph

+0

您聲明:「2個不同的密鑰」,密鑰必須相同,因此要調試的區域是密鑰派生'getRawKey'。還要檢查IV是如何創建並傳遞給解密的,您需要研究庫文檔。 – zaph

回答

0

您聲明:「2個不同的鍵」,鍵必須相同。

通常情況下,您預共享密鑰或密碼,並且如果使用密碼,則使用PBKDF2等函數從其派生密鑰。如果使用密碼派生,則必須對加密和解密使用相同的確定性函數函數。所使用的功能必須從每邊的密碼生成相同的密鑰。由於您使用的是隨機生成器,所以派生不是確定性的,並且這不起作用。

+0

不要將安全隨機函數上的種子設置爲強制算法確定性的一種方式?我遇到的問題是我試圖使用確定性函數,並且每次運行它時都會生成不同的密鑰。 – Nuvious

+0

不適用於加密PRNG,它們從系統上的事件中播種並重新播種。使用PBKDF2(基於密碼的密鑰推導函數2),這是它設計的目的。 PBKDF2還需要迭代計數才能使計算花費更長時間,這對於延遲攻擊者多次嘗試的密鑰推導是可取的,因此使用導致大約100ms進行推導的值。 – zaph

+0

接受了您的建議並找到了文檔以獲得PBKDF2,並解決了密鑰生成問題。感謝您的答覆! – Nuvious

0

接受了Zaph的建議並交換到了PBKDF2,並且現在具有一致的密鑰生成。這是對任何好奇的人的代碼。

public static SecretKey generateKey(Context c, char[] passphraseOrPin) throws NoSuchAlgorithmException, InvalidKeySpecException { 
    // Number of PBKDF2 hardening rounds to use. Larger values increase 
    // computation time. You should select a value that causes computation 
    // to take >100ms. 
    byte[] salt = Settings.Secure.getString(c.getContentResolver(), 
      Settings.Secure.ANDROID_ID).getBytes(); 

    final int iterations = 1000; 

    final int outputKeyLength = 128; 

    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 
    KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength); 
    SecretKey secretKey = secretKeyFactory.generateSecret(keySpec); 
    Log.d("HIDEMYPICS","Secret Key: " + toHex(secretKey.getEncoded())); 
    return secretKey; 
} 

public static boolean decryptFileFromUri(Context context, Uri file, String keyphrase) { 
    try { 
     File f = new File(getRealPathFromURI(context, file)); 
     FileInputStream fis = new FileInputStream(f); 

     File ef = new File(f.toString().replace(".epf", "")); 
     FileOutputStream fos = new FileOutputStream(ef); 

     Log.d("HIDEMYPICS","Decrypting: " + f.toString()); 

     SecretKey key = generateKey(context, keyphrase.toCharArray()); 
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
     cipher.init(Cipher.DECRYPT_MODE, key); 
     // Wrap the output stream 
     CipherOutputStream cos = new CipherOutputStream(fos, cipher); 
     // Write bytes 
     int b; 
     byte[] d = new byte[8]; 
     while ((b = fis.read(d)) != -1) { 
      cos.write(d, 0, b); 
     } 
     // Flush and close streams. 
     cos.flush(); 
     cos.close(); 
     fis.close(); 

     Log.d("HIDEMYPICS","Decrypted to: " + ef.toString()); 
     return true; 
    } catch (IOException e){ 
     e.printStackTrace(); 
    } catch (NoSuchPaddingException e) { 
     e.printStackTrace(); 
    } catch (NoSuchAlgorithmException e) { 
     e.printStackTrace(); 
    } catch (InvalidKeyException e) { 
     e.printStackTrace(); 
    } catch (InvalidKeySpecException e) { 
     e.printStackTrace(); 
    } 
    return false; 
} 

public static boolean encryptFileFromUri(Context context, Uri file, String keyphrase) { 
    try { 
     File f = new File(getRealPathFromURI(context, file)); 
     FileInputStream fis = new FileInputStream(f); 

     File ef = new File(f.toString() + ".epf"); 
     FileOutputStream fos = new FileOutputStream(ef); 

     Log.d("HIDEMYPICS","Encrypting: " + f.toString()); 

     SecretKey key = generateKey(context, keyphrase.toCharArray()); 
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
     cipher.init(Cipher.ENCRYPT_MODE, key); 
     // Wrap the output stream 
     CipherOutputStream cos = new CipherOutputStream(fos, cipher); 
     // Write bytes 
     int b; 
     byte[] d = new byte[8]; 
     while ((b = fis.read(d)) != -1) { 
      cos.write(d, 0, b); 
     } 
     // Flush and close streams. 
     cos.flush(); 
     cos.close(); 
     fis.close(); 
     Log.d("HIDEMYPICS","Encrypted to: " + ef.toString()); 
     return true; 
    } catch (IOException e){ 
     e.printStackTrace(); 
    } catch (NoSuchPaddingException e) { 
     e.printStackTrace(); 
    } catch (NoSuchAlgorithmException e) { 
     e.printStackTrace(); 
    } catch (InvalidKeyException e) { 
     e.printStackTrace(); 
    } catch (InvalidKeySpecException e) { 
     e.printStackTrace(); 
    } 
    return false; 
} 

注意:它仍然不能100%工作,在解密過程中會破壞前幾個字節,導致文件頭損壞。如果你想查看結果,我已經爲此開始了一個單獨的問題帖子:CipherOutputStream corrupting headers in Android