1. 程式人生 > >淺談android資料儲存加密

淺談android資料儲存加密

寫在開頭

在移動端的開發中,資料安全的問題一直是大家備受關注的,資料加密技術也受到了大家的青睞。專案中也用到了一些,在這裡學習總結下,完善下自己的知識,也分享給大家,一起交流!(末尾也會說下自己在專案中的使用)

  • 按可逆性:加密可分為可逆演算法不可逆演算法
  • 按對稱性:加密可分為對稱演算法非對稱演算法

一般的加密分為以下幾種,下面會分別簡單講解原理和其使用方法:

  • Base64編碼演算法 (可逆)
  • MD5加密 (不可逆)(還有一個sha1值,可能做過支付寶的會比較熟悉)
  • Des加密 (對稱,可逆)
  • Aes加密 (對稱,可逆)
  • Rsa加密(非對稱,可逆)

淺談對稱非對稱

對稱

對稱加密演算法是較傳統的加密體制,即通訊雙方在加/解密過程中使用他們共享的單一金鑰,鑑於其演算法簡單加密速度快的優點,目前仍然在使用,但是安全性方面就差一點可能。最常用的對稱密碼演算法是DES演算法,而DES金鑰長度較短,已經不適合當今分散式開放網路對資料加密安全性的要求。一種新的基於Rijndael演算法(自己腦補,本人也不太熟悉)對稱高階資料加密標準AES取代了資料加密標準DES,彌補了DES的缺陷,目前使用比較多一點

非對稱

非對稱加密由於加/解金鑰不同(公鑰加密,私鑰解密),金鑰管理簡單,得到了很廣泛的應用。RSA是非對稱加密系統最著名的公鑰密碼演算法。但是由於RSA演算法進行的都是大數計算,使得RSA最快的情況也比AES慢上倍,這是RSA最大的缺陷。但是其安全性較高,這也是大家比較喜歡的地方吧!

演算法講解及使用

Base64演算法

Base64其實並不是安全領域的加密演算法,因為它的加密解密演算法都是公開的,典型的防菜鳥不防程式猿的例子哈哈,Base64編碼本質上是一種將二進位制資料轉成文字資料的方案。用處就是將一些不適合傳輸的資料內容進行編碼來適合傳輸

字串進行Base64編碼

 String encodedString = Base64.encodeToString("wenwen".getBytes(), Base64.DEFAULT);
 第一個引數就是位元組陣列

字串進行Base64解碼

 String decodedString =new
String(Base64.decode(encodedString,Base64.DEFAULT)); decodedString 就是wenwen

MD5演算法

它是一種單向加密演算法,只能加密、無法解密。多用於密碼的儲存等等。對於MD5的安全性,網上有關MD5解密的網站數不勝數,破解機制採用窮舉法,就是手機所有可能的MD5值跑字典。所以常常採用對資料進行多次MD5加密或者採取加鹽(就是加一段獨有的字串在進行加密)的操作。

使用:

    public static String md5(String string) {
        if (TextUtils.isEmpty(string)) {
            return "";
        }
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            byte[] bytes = md5.digest(string.getBytes());
            String result = "";
            for (byte b : bytes) {
                String temp = Integer.toHexString(b & 0xff);
                if (temp.length() == 1) {
                    temp = "0" + temp;
                }
                result += temp;
            }
            return result;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

對稱加密(這裡只說下應用廣泛的AES)

對稱加密祕鑰是唯一的,加密解密都是一個祕鑰。AES速度上佔優於RSA,但是隻有一個祕鑰,安全性較低一些。

使用

    //常量介紹
    private final static String HEX = "0123456789ABCDEF";
    //AES是加密方式 CBC是工作模式 PKCS5Padding是填充模式
    private  static final String CBC_PKCS5_PADDING = "AES/CBC/PKCS5Padding";
    //AES 加密
    private  static final String AES = "AES";
    // SHA1PRNG 強隨機種子演算法, 要區別4.2以上版本的呼叫方法
    private  static final String  SHA1PRNG="SHA1PRNG";
    //生成隨機數,可以當做動態的金鑰 加密和解密的金鑰必須一致,不然將不能解密
    public static String generateKey() {
        try {
                SecureRandom localSecureRandom = SecureRandom.getInstance(SHA1PRNG);
                byte[] bytes_key = new byte[20];
                localSecureRandom.nextBytes(bytes_key);
                String str_key = toHex(bytes_key);
                return str_key;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

     // 對金鑰進行處理
        private static byte[] getRawKey(byte[] seed) throws Exception {
            KeyGenerator kgen = KeyGenerator.getInstance(AES);
            //for android
            SecureRandom sr = null;
            // 在4.2以上版本中,SecureRandom獲取方式發生了改變
            if (android.os.Build.VERSION.SDK_INT >= 17) {
                sr = SecureRandom.getInstance(SHA1PRNG, "Crypto");
            } else {
                sr = SecureRandom.getInstance(SHA1PRNG);
            }
            // for Java
            // secureRandom = SecureRandom.getInstance(SHA1PRNG);
            sr.setSeed(seed);
            kgen.init(128, sr); //256 bits or 128 bits,192bits
            //AES中128位金鑰版本有10個加密迴圈,192位元金鑰版本有12個加密迴圈,256位元金鑰版本則有14個加密迴圈。
            SecretKey skey = kgen.generateKey();
            byte[] raw = skey.getEncoded();
            return raw;
        }
    //加密
      public static String encrypt(String key, String cleartext) {
        if (TextUtils.isEmpty(cleartext)) {
            return cleartext;
        }
        try {
            byte[] result = encrypt(key, cleartext.getBytes());
            return Base64Encoder.encode(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static byte[] encrypt(String key, byte[] clear) throws Exception {
        byte[] raw = getRawKey(key.getBytes());
        SecretKeySpec skeySpec = new SecretKeySpec(raw, AES);
        Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }
 /*
     * 解密
     */
    public static String decrypt(String key, String encrypted) {
        if (TextUtils.isEmpty(encrypted)) {
            return encrypted;
        }
        try {
            byte[] enc = Base64Decoder.decodeToBytes(encrypted);
            byte[] result = decrypt(key, enc);
            return new String(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
     * 解密
     */
    private static byte[] decrypt(String key, byte[] encrypted) throws Exception {
        byte[] raw = getRawKey(key.getBytes());
        SecretKeySpec skeySpec = new SecretKeySpec(raw, AES);
        Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }
}

RSA加密

RSA演算法是最流行的公鑰密碼演算法,使用長度可以變化的金鑰。RSA是第一個既能用於資料加密也能用於數字簽名的演算法。它在很多密碼協議中都有應用,如SSL和S/MIME。RSA演算法是基於大質數的因數分解的公匙體系。簡單的講,就是兩個很大的質數,一個作為公鑰,另一個作為私鑰,如用其中一個加密,則用另一個解密。金鑰長度從40到2048位可變,金鑰越長,加密效果越好,但加密解密的開銷也大。所以他在加密的速度上回小於AES等對稱加密。

使用:

    //全域性變數
    public static final String RSA = "RSA";// 非對稱加密金鑰演算法
    public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";//加密填充方式
    public static final int DEFAULT_KEY_SIZE = 2048;//祕鑰預設長度
    public static final byte[] DEFAULT_SPLIT = "#PART#".getBytes();    // 當要加密的內容超過bufferSize,則採用partSplit進行分塊加密
    public static final int DEFAULT_BUFFERSIZE = (DEFAULT_KEY_SIZE / 8) - 11;// 當前祕鑰支援加密的最大位元組數
     /**
     * 隨機生成RSA金鑰對
     * @param keyLength 金鑰長度,範圍:512~2048  一般1024
     */
    public static KeyPair generateRSAKeyPair(int keyLength) {
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
            kpg.initialize(keyLength);
            return kpg.genKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

公鑰加密私鑰解密

     /**
     * 用公鑰對字串進行加密
     * @param data 原文
     */
    public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
        // 得到公鑰
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
        KeyFactory kf = KeyFactory.getInstance(RSA);
        PublicKey keyPublic = kf.generatePublic(keySpec);
        // 加密資料
        Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
        cp.init(Cipher.ENCRYPT_MODE, keyPublic);
        return cp.doFinal(data);
    }

    /**
     * 使用私鑰進行解密
     */
    public static byte[] decryptByPrivateKey(byte[] encrypted, byte[] privateKey) throws Exception {
        // 得到私鑰
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
        KeyFactory kf = KeyFactory.getInstance(RSA);
        PrivateKey keyPrivate = kf.generatePrivate(keySpec);

        // 解密資料
        Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
        cp.init(Cipher.DECRYPT_MODE, keyPrivate);
        byte[] arr = cp.doFinal(encrypted);
        return arr;
    }

私鑰加密公鑰解密

      /**
     * 私鑰加密
     * @param data       待加密資料
     * @param privateKey 金鑰
     * @return byte[] 加密資料
     */
    public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) throws Exception {
        // 得到私鑰
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
        KeyFactory kf = KeyFactory.getInstance(RSA);
        PrivateKey keyPrivate = kf.generatePrivate(keySpec);
        // 資料加密
        Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);
        return cipher.doFinal(data);
    }

     /**
     * 公鑰解密
     *
     * @param data      待解密資料
     * @param publicKey 金鑰
     * @return byte[] 解密資料
     */
    public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
        // 得到公鑰
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
        KeyFactory kf = KeyFactory.getInstance(RSA);
        PublicKey keyPublic = kf.generatePublic(keySpec);
        // 資料解密
        Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, keyPublic);
        return cipher.doFinal(data);
    }

RSA所遇問題

關於加密填充方式:之前以為上面這些操作就能實現rsa加解密,以為萬事大吉了,呵呵,這事還沒完,悲劇還是發生了,Android這邊加密過的資料,伺服器端死活解密不了,原來android系統的RSA實現是”RSA/None/NoPadding”,而標準JDK實現是”RSA/None/PKCS1Padding” ,這造成了在android機上加密後無法在伺服器上解密的原因,所以在實現的時候這個一定要注意。

實現分段加密:搞定了填充方式之後又自信的認為萬事大吉了,可是意外還是發生了,RSA非對稱加密內容長度有限制,1024位key的最多隻能加密127位資料,否則就會報錯(javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes) , RSA 是常用的非對稱加密演算法。最近使用時卻出現了“不正確的長度”的異常,研究發現是由於待加密的資料超長所致。RSA 演算法規定:待加密的位元組數不能超過金鑰的長度值除以 8 再減去 11(即:KeySize / 8 - 11),而加密後得到密文的位元組數,正好是金鑰的長度值除以 8(即:KeySize / 8)。

分段公鑰加密私鑰加密

     /**
     * 用公鑰對字串進行分段加密
     *
     */
    public static byte[] encryptByPublicKeyForSpilt(byte[] data, byte[] publicKey) throws Exception {
        int dataLen = data.length;
        if (dataLen <= DEFAULT_BUFFERSIZE) {
            return encryptByPublicKey(data, publicKey);
        }
        List<Byte> allBytes = new ArrayList<Byte>(2048);
        int bufIndex = 0;
        int subDataLoop = 0;
        byte[] buf = new byte[DEFAULT_BUFFERSIZE];
        for (int i = 0; i < dataLen; i++) {
            buf[bufIndex] = data[i];
            if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
                subDataLoop++;
                if (subDataLoop != 1) {
                    for (byte b : DEFAULT_SPLIT) {
                        allBytes.add(b);
                    }
                }
                byte[] encryptBytes = encryptByPublicKey(buf, publicKey);
                for (byte b : encryptBytes) {
                    allBytes.add(b);
                }
                bufIndex = 0;
                if (i == dataLen - 1) {
                    buf = null;
                } else {
                    buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
                }
            }
        }
        byte[] bytes = new byte[allBytes.size()];
        {
            int i = 0;
            for (Byte b : allBytes) {
                bytes[i++] = b.byteValue();
            }
        }
        return bytes;
    }

     /**
     * 使用私鑰分段解密
     *
     */
    public static byte[] decryptByPrivateKeyForSpilt(byte[] encrypted, byte[] privateKey) throws Exception {
        int splitLen = DEFAULT_SPLIT.length;
        if (splitLen <= 0) {
            return decryptByPrivateKey(encrypted, privateKey);
        }
        int dataLen = encrypted.length;
        List<Byte> allBytes = new ArrayList<Byte>(1024);
        int latestStartIndex = 0;
        for (int i = 0; i < dataLen; i++) {
            byte bt = encrypted[i];
            boolean isMatchSplit = false;
            if (i == dataLen - 1) {
                // 到data的最後了
                byte[] part = new byte[dataLen - latestStartIndex];
                System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
                byte[] decryptPart = decryptByPrivateKey(part, privateKey);
                for (byte b : decryptPart) {
                    allBytes.add(b);
                }
                latestStartIndex = i + splitLen;
                i = latestStartIndex - 1;
            } else if (bt == DEFAULT_SPLIT[0]) {
                // 這個是以split[0]開頭
                if (splitLen > 1) {
                    if (i + splitLen < dataLen) {
                        // 沒有超出data的範圍
                        for (int j = 1; j < splitLen; j++) {
                            if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
                                break;
                            }
                            if (j == splitLen - 1) {
                                // 驗證到split的最後一位,都沒有break,則表明已經確認是split段
                                isMatchSplit = true;
                            }
                        }
                    }
                } else {
                    // split只有一位,則已經匹配了
                    isMatchSplit = true;
                }
            }
            if (isMatchSplit) {
                byte[] part = new byte[i - latestStartIndex];
                System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
                byte[] decryptPart = decryptByPrivateKey(part, privateKey);
                for (byte b : decryptPart) {
                    allBytes.add(b);
                }
                latestStartIndex = i + splitLen;
                i = latestStartIndex - 1;
            }
        }
        byte[] bytes = new byte[allBytes.size()];
        {
            int i = 0;
            for (Byte b : allBytes) {
                bytes[i++] = b.byteValue();
            }
        }
        return bytes;
    }

分段私鑰加密公鑰加密

     /**
     * 私鑰分段加密
     * @param data       要加密的原始資料
     * @param privateKey 祕鑰
     */
    public static byte[] encryptByPrivateKeyForSpilt(byte[] data, byte[] privateKey) throws Exception {
        int dataLen = data.length;
        if (dataLen <= DEFAULT_BUFFERSIZE) {
            return encryptByPrivateKey(data, privateKey);
        }
        List<Byte> allBytes = new ArrayList<Byte>(2048);
        int bufIndex = 0;
        int subDataLoop = 0;
        byte[] buf = new byte[DEFAULT_BUFFERSIZE];
        for (int i = 0; i < dataLen; i++) {
            buf[bufIndex] = data[i];
            if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
                subDataLoop++;
                if (subDataLoop != 1) {
                    for (byte b : DEFAULT_SPLIT) {
                        allBytes.add(b);
                    }
                }
                byte[] encryptBytes = encryptByPrivateKey(buf, privateKey);
                for (byte b : encryptBytes) {
                    allBytes.add(b);
                }
                bufIndex = 0;
                if (i == dataLen - 1) {
                    buf = null;
                } else {
                    buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
                }
            }
        }
        byte[] bytes = new byte[allBytes.size()];
        {
            int i = 0;
            for (Byte b : allBytes) {
                bytes[i++] = b.byteValue();
            }
        }
        return bytes;
    }

     /**
     * 公鑰分段解密
     *
     * @param encrypted 待解密資料
     * @param publicKey 金鑰
     */
    public static byte[] decryptByPublicKeyForSpilt(byte[] encrypted, byte[] publicKey) throws Exception {
        int splitLen = DEFAULT_SPLIT.length;
        if (splitLen <= 0) {
            return decryptByPublicKey(encrypted, publicKey);
        }
        int dataLen = encrypted.length;
        List<Byte> allBytes = new ArrayList<Byte>(1024);
        int latestStartIndex = 0;
        for (int i = 0; i < dataLen; i++) {
            byte bt = encrypted[i];
            boolean isMatchSplit = false;
            if (i == dataLen - 1) {
                // 到data的最後了
                byte[] part = new byte[dataLen - latestStartIndex];
                System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
                byte[] decryptPart = decryptByPublicKey(part, publicKey);
                for (byte b : decryptPart) {
                    allBytes.add(b);
                }
                latestStartIndex = i + splitLen;
                i = latestStartIndex - 1;
            } else if (bt == DEFAULT_SPLIT[0]) {
                // 這個是以split[0]開頭
                if (splitLen > 1) {
                    if (i + splitLen < dataLen) {
                        // 沒有超出data的範圍
                        for (int j = 1; j < splitLen; j++) {
                            if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
                                break;
                            }
                            if (j == splitLen - 1) {
                                // 驗證到split的最後一位,都沒有break,則表明已經確認是split段
                                isMatchSplit = true;
                            }
                        }
                    }
                } else {
                    // split只有一位,則已經匹配了
                    isMatchSplit = true;
                }
            }
            if (isMatchSplit) {
                byte[] part = new byte[i - latestStartIndex];
                System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
                byte[] decryptPart = decryptByPublicKey(part, publicKey);
                for (byte b : decryptPart) {
                    allBytes.add(b);
                }
                latestStartIndex = i + splitLen;
                i = latestStartIndex - 1;
            }
        }
        byte[] bytes = new byte[allBytes.size()];
        {
            int i = 0;
            for (Byte b : allBytes) {
                bytes[i++] = b.byteValue();
            }
        }
        return bytes;
    }

專案中使用

在實際開發中,不是太重要的資料用一種加密方式感覺就可以了。但是比較重要的資料建議用多種加密方式結合的方式,比如我用的RSA+AES加密。他主要解決了兩個問題:1.RSA加解密速度慢,不適合大量資料檔案加密 2.AES加密速度很快,但是安全性沒有RSA加密方式的安全。其主要思想就是服務端生成公鑰私鑰,並提供介面將公鑰給android端,android端生成AES祕鑰,並用AES祕鑰對大量資料進行加密(解決RSA加解密速度慢的問題),然後用呼叫介面拿到的RSA公鑰對自己生成AES祕鑰進行加密,客戶端將得到的祕鑰和通過AES加密的資料傳送給伺服器。(祕鑰可以放在請求頭中,資料放在請求體中,這個隨意了)。服務拿到你的祕鑰和資料後,用私鑰加密得到AES祕鑰,再通過祕鑰得到傳送的資料就好了。

感謝教我學習的大神