Android:7.0 後加密庫 Crypto 被廢棄

weixin_33670713發表於2017-06-26

Security "Crypto" provider deprecated in Android N

Android:7.0 後加密庫 Crypto 被廢棄

一.問題描述

我們知道加密[演算法]都是需要金鑰的,比如 AES 演算法支援128 位元、192 位元和 256 位元三種長度的金鑰,通常這些金鑰會被轉化成位元組陣列明文寫在程式碼中或者寫入成 KeyStore 檔案。如果你是直接使用這些金鑰的話是不會有任何問題的,但是有的時候我們需要通過一個字串格式的密碼來生成金鑰。
我們需要可逆的加密方式的時間,在Android中一般會使用Crypto這個庫裡面的一些函式進行加密,但是,使用targetSdkVersion為25進行編譯執行在Android7.0的手機上額時間,你會發現,首次安裝加密的串一定是空的,錯誤如下所示。

2333435-2587d1bf4f447e71.png

上述的連結為http://android-developers.blogspot.com/2016/06/security-crypto-provider-deprecated-in.html
指向的是官網說明:Android:7.0 後加密庫 Crypto 被廢棄
Android Developers Blog 官方版本的說明文件

2333435-27a6f45584f84b20.png

二.原始程式碼(被棄用程式碼書寫方式)

一般情況下可逆的加密方式網上有很多種,百度可以出來一大堆,這裡使用官方示例
Keys can be derived in the following way:
If you're reading an AES key from disk, just store the actual key and don't go through this weird dance. You can get a SecretKey for AES usage from the bytes by doing:

SecretKey key = new SecretKeySpec(keyBytes, "AES");

EXAMPLE:

public static final String DEPREACATED_SECURE_ALGORITHM_SHA1PRNG = "SHA1PRNG";
    public static final String DEPREACATED_SECURE_PROVIDER_CRYPTO = "Crypto";

    /**
     * 按照指定編碼從字串中生成指定長度的金鑰 key。
     *
     * @param password
     * @param charset
     * @param keyBitLen
     * @return
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     */
    @Deprecated
    public static byte[] deriveKeyDeprecated(String password, @Nullable Charset charset, int keyBitLen) throws NoSuchProviderException, NoSuchAlgorithmException {
        SecureRandom secureRandom = SecureRandom.getInstance(DEPREACATED_SECURE_ALGORITHM_SHA1PRNG, DEPREACATED_SECURE_PROVIDER_CRYPTO);
        //在隨機數生成器中將密碼的字串設為種子換算出最終的金鑰key,異常就是在這裡發生的
        secureRandom.setSeed(password.getBytes(charset != null ? charset : Charset.defaultCharset()));

        KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);
        keyGenerator.init(keyBitLen, secureRandom);
        SecretKey secretKey = keyGenerator.generateKey();
        return secretKey.getEncoded();
    }

可以看到我們將密碼作為隨機數生成器的種子換算出金鑰 key,這種做法已經被認定為是不安全的。官方開發人員在 Axndroid N 上已經將相關的 Crypto provider 和 SHA1PRNG 演算法同時廢棄掉了,並計劃在後續的 SDK 中完全移除相關的庫。
當然你可以直接使用金鑰來繞過這個問題,或者將 targetSdkVersion 調低一些來掩蓋崩潰,但這個坑早晚總是要填的。所以讓我們來看看該怎麼解決這個問題。

三.解決問題

首先我們看下官方的解決方案

/*輔助解密由被廢棄的邏輯加密出來的資料的工具類*/
 private static SecretKey deriveKeyInsecurely(String password, int
 keySizeInBytes) {  
    byte[] passwordBytes = password.getBytes(StandardCharsets.US_ASCII);  
    return new SecretKeySpec(  
            InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(  
                     passwordBytes, keySizeInBytes),  
            "AES");  
 }  

上面的方法是為了輔助解密由被廢棄的邏輯加密出來的資料的工具類,當前中文註解是後期加上去的。
我們也可以直接使用java.security包中的內容進行加密演算法的重構,其實就是幾句簡單的話
EXAMPLE:

   // 給出字串的密碼
   String password = "password";  

   // 金鑰的位元位數,注意這裡是位元位數
   // AES 支援 128、192 和 256 位元長度的金鑰
   int keyLength = 256; 
   // 鹽值的位元組陣列長度,注意這裡是位元組陣列的長度
   // 其長度值需要和最終輸出的金鑰位元組陣列長度一致
   // 由於這裡金鑰的長度是 256 位元,則最終金鑰將以 256/8 = 32 位長度的位元組陣列存在
   // 所以鹽值的位元組陣列長度也應該是 32
   int saltLength = 32;
   byte[] salt;

   // 先獲取一個隨機的鹽值
   // 你需要將此次生成的鹽值儲存到磁碟上下次再從字串換算金鑰時傳入
   // 如果鹽值不一致將導致換算的金鑰值不同
   // 儲存金鑰的邏輯官方並沒寫,需要自行實現
   SecureRandom random = new SecureRandom();  
   byte[] salt = new byte[saltLength];  
   random.nextBytes(salt);  

   // 將密碼明文、鹽值等使用新的方法換算金鑰
   int iterationCount = 1000;
   KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,  
              iterationCount, keyLength);  
   SecretKeyFactory keyFactory = SecretKeyFactory  
              .getInstance("PBKDF2WithHmacSHA1");  
   // 到這裡你就能拿到一個安全的金鑰了
   byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();  
   SecretKey key = new SecretKeySpec(keyBytes, "AES");  

以上就是修改可逆的加密方式進行正確的解決Security "Crypto" provider deprecated in Android N的問題,若只是為了相容老版本也可使用官網中的例子。

Note 1: as a temporary measure to keep apps working, we decided to still create the instance for apps targeting SDK version 23, the SDK version for Marshmallow, or less. Please don't rely on the presence of the Crypto provider in the Android SDK, our plan is to delete it completely in the future.

Note 2: Because many parts of the system assume the existence of a SHA1PRNG algorithm, when an instance of SHA1PRNG is requested and the provider is not specified we return an instance of OpenSSLRandom, which is a strong source of random numbers derived from OpenSSL.

官方計劃將完全刪除Crypto和SHA1PRNG,一般情況下許多系統假定SHA1PRNG演算法存在,在去除後將返回一個OpenSSLRandom例項,例項是根據OpenSSL派生出的。

目前使用targetSdkVersion低版本的方式進行掩蓋,但個別手機也會出現首次加解密為空的情況,直接閃退或者重啟,並不會進行crash的報錯,即使是報錯也是某native欄位為空。親們趕緊相容Nougat(牛軋糖)(Android N) 接下來準備擁抱8.0Android O吧

PS:仔細檢視程式碼中是否使用了Crypto這個庫,也就是javax.crypto這個包下的內容都要仔細驗證。
你是不是想說??

2333435-a4dc107a8d48c3be.png

還不趕緊改Bug去???

還不趕緊改Bug去???

還不趕緊改Bug去???


關注微信公眾號 Android歷練記 或掃一掃二維碼:
讓我們一起來搞事情。

2333435-d5c1fa8c2c9e7850.jpg
Android歷練記

相關文章