Android中常用的加密方式

吾乃韓小呆發表於2018-05-31

Android面試的時候,尤其是面試一些金融公司的時候經常性的問道:“你會不會加密?”,“加密方式是什麼?”,“大概什麼樣的加密原理呢?”。其實,大多數人都是一臉懵逼,我也懵逼過。痛下決心總結一下,寫的不好,大家見笑了。

一、Rsa加密

1、RSA是第一種既可以用於資料加密,也可以用於數字簽名的演算法;

2、演算法原理:

1)、隨機產生兩個大的質數m、n且m!=n,計算K1=mn;
2)、選擇一個大於1小於k1的自然數k2,k2必須與(m-1)(n-1)互為素數;
3)、計算得到d—>d x k2=1(mod(m-1)(n-1));
4)、銷燬mn;
最終產生的k1和k2為“公鑰”,d為“私鑰”,傳送方使用k1進行加密,接收方使用d進行解密。

3、注意:

1、RSA的安全性依賴於大數分解,小於1024位的k1被認為是不安全的;
2、RSA的計算速度慢。

4、使用

1、生成金鑰對


    /**
     * 隨機生成RSA金鑰對
     *
     * @param keyLength 金鑰長度,範圍:512~2048
     *                  一般1024
     * @return
     */
    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;
        }
    }

2、公鑰加密

  /**
     * 用公鑰對字串進行加密
     *
     * @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);
    }

3、私鑰加密

/**
     * 私鑰加密
     *
     * @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);
    }

有加密自然也得有解密
1、公鑰解密

 /**
     * 公鑰解密
     *
     * @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);
    }

2、私鑰解密

/**
     * 使用私鑰進行解密
     */
    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;
    }

用到的全域性變數

// 非對稱加密金鑰演算法
  public static final String RSA = "RSA";
   //加密填充方式
    public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";
   //祕鑰預設長度
    public static final int DEFAULT_KEY_SIZE = 2048;
   // 當要加密的內容超過bufferSize,則採用partSplit進行分塊加密
    public static final byte[] DEFAULT_SPLIT = "#PART#".getBytes();  
    // 當前祕鑰支援加密的最大位元組數
    public static final int DEFAULT_BUFFERSIZE = (DEFAULT_KEY_SIZE / 8) - 11;

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

二、DES加密

1、簡單介紹

DES是一種對稱加密演算法,所謂對稱加密演算法即:加密和解密使用相同金鑰的演算法。DES加密演算法出自IBM的研究,後來被美國政府正式採用,之後開始廣泛流傳,但是近些年使用越來越少,因為DES使用56位金鑰,以現代計算能力,24小時內即可被破解。

劃重點 :DES不太安全。

2、靜態祕鑰DES加密方式使用

1)、DES加密程式碼

 /**
     *DES 加密
     *
     * @param message 原文
     * @param key     金鑰,長度不能夠小於8位
     * @return
     * @throws Exception
     */
    public static String desEncrypt(String message, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        DESKeySpec desKeySpec = new DESKeySpec(key.getBytes("UTF-8"));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
        IvParameterSpec iv = new IvParameterSpec(key.getBytes("UTF-8"));
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
        byte[] encryptbyte = cipher.doFinal(message.getBytes());
        return new String(Base64.encode(encryptbyte, Base64.DEFAULT)).trim();
    }

2、DES解密程式碼

/**
     * DES解密
     *
     * @param message 密文
     * @param key     金鑰,長度不能夠小於8位
     * @return
     * @throws Exception
     */
    public static String desDecrypt(String message, String key) throws Exception {

        byte[] bytesrc = Base64.decode(message.getBytes(), Base64.DEFAULT);
        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        DESKeySpec desKeySpec = new DESKeySpec(key.getBytes("UTF-8"));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
        IvParameterSpec iv = new IvParameterSpec(key.getBytes("UTF-8"));
        cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
        byte[] retByte = cipher.doFinal(bytesrc);
        return new String(retByte);
    }

3、動態祕鑰加密方式

1、DES加密常用的常量

    private final static String HEX = "0123456789ABCDEF";
    //DES是加密方式 CBC是工作模式 PKCS5Padding是填充模式
    private final static String TRANSFORMATION = "DES/CBC/PKCS5Padding";
    //初始化向量引數,AES 為16bytes. DES 為8bytes.
    private final static String IVPARAMETERSPEC = "01020304";
    //DES是加密方式
    private final static String ALGORITHM = "DES";
   // SHA1PRNG 強隨機種子演算法, 要區別4.2以上版本的呼叫方法
    private static final String SHA1PRNG = "SHA1PRNG";

2、動態生成祕鑰

  /*
 * 生成隨機數,可以當做動態的金鑰 加密和解密的金鑰必須一致,不然將不能解密
 */
    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;
    }

    //二進位制轉字元
    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }

    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }

3、處理祕鑰
方式一、

  // 對金鑰進行處理
    private static Key getRawKey(String key) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance(ALGORITHM);
        //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(key.getBytes());
        kgen.init(64, sr); //DES固定格式為64bits,即8bytes。
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return new SecretKeySpec(raw, ALGORITHM);
    }

方式二

/ 對金鑰進行處理
    private static Key getRawKey(String key) throws Exception {
        DESKeySpec dks = new DESKeySpec(key.getBytes());
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
        return keyFactory.generateSecret(dks);
    }

4、加密實現

/**
     * DES演算法,加密
     *
     * @param data 待加密字串
     * @param key  加密私鑰,長度不能夠小於8位
     * @return 加密後的位元組陣列,一般結合Base64編碼使用
     */
    public static String encode(String key, String data) {
        return encode(key, data.getBytes());
    }


    /**
     * DES演算法,加密
     *
     * @param data 待加密字串
     * @param key  加密私鑰,長度不能夠小於8位
     * @return 加密後的位元組陣列,一般結合Base64編碼使用
     */
    public static String encode(String key, byte[] data) {
        try {
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            IvParameterSpec iv = new IvParameterSpec(IVPARAMETERSPEC.getBytes());
            cipher.init(Cipher.ENCRYPT_MODE, getRawKey(key), iv);
            byte[] bytes = cipher.doFinal(data);
            return Base64.encodeToString(bytes, Base64.DEFAULT);
        } catch (Exception e) {
            return null;
        }
    }

5、解密實現

 /**
     * 獲取編碼後的值
     *
     * @param key
     * @param data
     * @return
     */
    public static String decode(String key, String data) {
        return decode(key, Base64.decode(data, Base64.DEFAULT));
    }

    /**
     * DES演算法,解密
     *
     * @param data 待解密字串
     * @param key  解密私鑰,長度不能夠小於8位
     * @return 解密後的位元組陣列
     */
    public static String decode(String key, byte[] data) {
        try {
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            IvParameterSpec iv = new IvParameterSpec(IVPARAMETERSPEC.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, getRawKey(key), iv);
            byte[] original = cipher.doFinal(data);
            String originalString = new String(original);
            return originalString;
        } catch (Exception e) {
            return null;
        }
    }

三、AES加密

1、簡單介紹

高階加密標準

2、加密使用方法

1、AES用到的常量

  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";

2、動態生成祕鑰

  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;
    }

3、對祕鑰進行處理

   private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance(AES);
        //for android
        SecureRandom sr = null;
        // 在4.2以上版本中,SecureRandom獲取方式發生了改變
        int sdk_version = android.os.Build.VERSION.SDK_INT;
        // Android  6.0 以上
        if(sdk_version>23){
            sr = SecureRandom.getInstance(SHA1PRNG,new CryptoProvider());
        //4.2及以上
        }else 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);
        //256 bits or 128 bits,192bits
        kgen.init(128, sr); 
        //AES中128位金鑰版本有10個加密迴圈,192位元金鑰版本有12個加密迴圈,256位元金鑰版本則有14個加密迴圈。
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }

4、加密

 public static String encrypt(String key, String cleartext) {
        if (TextUtils.isEmpty(cleartext)) {
            return cleartext;
        }
        try {
            byte[] result = encrypt(key, cleartext.getBytes());
            return new String(Base64.encode(result,Base64.DEFAULT));
        } 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;
    }

5、解密

 public static String decrypt(String key, String encrypted) {
        if (TextUtils.isEmpty(encrypted)) {
            return encrypted;
        }
        try {
            byte[] enc = Base64.decode(encrypted,Base64.DEFAULT);
            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;
    }

6、輔助方法

  //二進位制轉字元
    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }

    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }


 // 增加  CryptoProvider  類

    public static  class CryptoProvider extends Provider {
        /**
         * Creates a Provider and puts parameters
         */
        public CryptoProvider() {
            super("Crypto", 1.0, "HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature)");
            put("SecureRandom.SHA1PRNG",
                    "org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl");
            put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
        }
    }

本文參考:
《安卓AES加解密(相容Android7.0)》
《Android資料加密之Rsa加密》


相關文章