1. 程式人生 > >Android 資料加密演算法總結

Android 資料加密演算法總結

安卓中,不管是內網還是外網,資料的傳輸首要考慮就是安全問題,尤其是使用者資訊,以及各種密碼等敏感資訊。
所以說,對資料的加密是很有必要的,尤其是當下物聯網蓬勃發展的今天,資料安全尤為重要。


因此本人總結了一下安卓中幾種加密方式的實現:

① MD5:一種不可逆的加密演算法,常用於只需加密無需解密的資料上,比如使用者密碼,也常用來保證資料的完整性,因為資料被篡改後,其加密後的MD5也會隨之改變,對比篡改前的MD5可確定資料是否完整;

public final static String getMD5String(String s) {

		final char HEX_DIGITS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
				'9', 'A', 'B', 'C', 'D', 'E', 'F' };

		if (s == null)
			return null;

		try {
			MessageDigest mdInst = MessageDigest.getInstance("MD5");
			mdInst.update(s.getBytes());
			byte[] md = mdInst.digest();
			int len = md.length;
			StringBuilder buf = new StringBuilder(len * 2);
			for (int j = 0; j < len; j++) {
				buf.append(HEX_DIGITS[(md[j] >> 4) & 0x0f]);
				buf.append(HEX_DIGITS[md[j] & 0x0f]);
			}
			return buf.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
② DES:對稱加密演算法;
	/**
     * 返回可逆演算法DES的金鑰
     *
     * @param key 前8位元組將被用來生成金鑰。
     * @return 生成的金鑰
     * @throws Exception
     */
    public static Key getDESKey(byte[] key) throws Exception {
        DESKeySpec des = new DESKeySpec(key);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        return keyFactory.generateSecret(des);
    }

    /**
     * 根據指定的金鑰及演算法,將字串進行解密。
     *
     * @param data      要進行解密的資料,它是由原來的byte[]陣列轉化為字串的結果。
     * @param key       金鑰。
     * @param algorithm 演算法--"DES/CBC/PKCS5Padding"
     * @return 解密後的結果。它由解密後的byte[]重新建立為String物件。如果解密失敗,將返回null。
     * @throws Exception
     */
    public static String decrypt(String data, Key key, String algorithm)
            throws Exception {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, key);
        String result = new String(cipher.doFinal(StringUtils
                .hexStringToByteArray(data)), "utf8");
        return result;
    }

    /**
     * 根據指定的金鑰及演算法對指定字串進行可逆加密。
     *
     * @param data      要進行加密的字串。
     * @param key       金鑰。
     * @param algorithm 演算法。
     * @return 加密後的結果將由byte[]陣列轉換為16進製表示的陣列。如果加密過程失敗,將返回null。
     */
    public static String encrypt(String data, Key key, String algorithm)
            throws Exception {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return StringUtils.byteArrayToHexString(cipher.doFinal(data
                .getBytes("utf8")));
    }
    /**
     * byte[]陣列轉換為16進位制的字串
     *
     * @param data 要轉換的位元組陣列
     * @return 轉換後的結果
     */
    public static final String byteArrayToHexString(byte[] data) {
        StringBuilder sb = new StringBuilder(data.length * 2);
        for (byte b : data) {
            int v = b & 0xff;
            if (v < 16) {
                sb.append('0');
            }
            sb.append(Integer.toHexString(v));
        }
        return sb.toString().toUpperCase(Locale.getDefault());
    }

    /**
     * 16進製表示的字串轉換為位元組陣列
     *
     * @param s 16進製表示的字串
     * @return byte[] 位元組陣列
     */
    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] d = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            // 兩位一組,表示一個位元組,把這樣表示的16進位制字串,還原成一個進位制位元組
            d[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
                    .digit(s.charAt(i + 1), 16));
        }
        return d;
    }

③ AES:對稱加密演算法,為了防止網路(包含內網和外網)傳遞AES加密的祕文時導致的資料丟失(last block incomplete in decryption),一般需對祕文做base64處理,加密時候需要新增一個類似bcprov-jdk的庫和兩個policy檔案,解密則不需要,
由於客戶端攜帶了金鑰,靜態的金鑰存在於客戶端總是不安全的,那麼可以考慮ECDH公鑰交換協商隨機金鑰,基點或者全部邏輯加密演算法寫在加殼的SO裡,此隨機金鑰可作為資料aes加密的金鑰,這樣可保證安全,當然亦可考慮使用非對稱加密演算法。
AES128和AES256主要區別是金鑰長度不同(分別是128bits,256bits)、加密處理輪數不同(分別是10輪,14輪),後者強度高於前者;
 /** 
         * 金鑰演算法 
         * java6支援56位金鑰,bouncycastle支援64位 
         * */ 
        public static final String KEY_ALGORITHM="AES";  
           
        /** 
         * 加密/解密演算法/工作模式/填充方式 
         *  
         * JAVA6 支援PKCS5PADDING填充方式 
         * Bouncy castle支援PKCS7Padding填充方式 
         * */ 
        public static final String CIPHER_ALGORITHM="AES/ECB/PKCS7Padding";  
           
        /** 
         *  
         * 生成金鑰,java6只支援56位金鑰,bouncycastle支援64位金鑰 
         * @return byte[] 二進位制金鑰 
         * */ 
        public static byte[] initkey() throws Exception{  
            return new byte[] { 0x08, 0x08, 0x04, 0x0b, 0x02, 0x0f, 0x0b, 0x0c,
                    0x01, 0x03, 0x09, 0x07, 0x0c, 0x03, 0x07, 0x0a, 0x04, 0x0f,
                    0x06, 0x0f, 0x0e, 0x09, 0x05, 0x01, 0x0a, 0x0a, 0x01, 0x09,
                    0x06, 0x07, 0x09, 0x0d };
        }
        //Base64:byte[]→String
    	public static String encryptBASE64(byte[] key) throws Exception {
    		return (new BASE64Encoder()).encodeBuffer(key);
    	}
    	//Base64:String→byte[]
    	public static byte[] decryptBASE64(String key) throws Exception {
    		return (new BASE64Decoder()).decodeBuffer(key);
    	}
        /** 
         * 轉換金鑰 
         * @param key 二進位制金鑰 
         * @return Key 金鑰 
         * */ 
        public static Key toKey(byte[] key) throws Exception{ 
            //生成金鑰  
            SecretKey secretKey=new SecretKeySpec(key,KEY_ALGORITHM);  
            return secretKey;  
        }  
           
        /** 
         * 加密資料 
         * @param data 待加密資料 
         * @param key 金鑰 
         * @return byte[] 加密後的資料 
         * */ 
        public static byte[] encrypt(byte[] data,byte[] key) throws Exception{  
            //還原金鑰  
            Key k=toKey(key);  
            /** 
             * 例項化 
             * 使用 PKCS7PADDING 填充方式,按如下方式實現,就是呼叫bouncycastle元件實現 
             * Cipher.getInstance(CIPHER_ALGORITHM,"BC") 
             */ 
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            Cipher cipher=Cipher.getInstance(CIPHER_ALGORITHM, "BC");  
            //初始化,設定為加密模式  
            cipher.init(Cipher.ENCRYPT_MODE, k);  
            //執行操作  
            return cipher.doFinal(data);  
        }
       /** 
         * 解密資料 
         * @param data 待解密資料 
         * @param key 金鑰 
         * @return byte[] 解密後的資料 
         * */ 
        public static byte[] decrypt(byte[] data,byte[] key) throws Exception{  
            //歡迎金鑰  
            Key k =toKey(key);  
            /** 
             * 例項化 
             * 使用 PKCS7PADDING 填充方式,按如下方式實現,就是呼叫bouncycastle元件實現 
             * Cipher.getInstance(CIPHER_ALGORITHM,"BC") 
             */ 
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            Cipher cipher=Cipher.getInstance(CIPHER_ALGORITHM);  
            //初始化,設定為解密模式
            cipher.init(Cipher.DECRYPT_MODE, k);  
            //執行操作  
            return cipher.doFinal(data);  
        } 
④ RSA:非對稱加密演算法,RSA的安全性依賴於大數的分解,公鑰和私鑰都是兩個大素數(大於100的十進位制位)的函式。公鑰對資料進行加密後傳輸,接收方用私鑰進行解密,而從一個公鑰和密文推斷出明文的難度等同於分解兩個大素數的積。由於進行的都是大數計算,使得RSA最快的情況也比DES慢上100倍,無論是軟體還是硬體實現。速度一直是RSA的缺陷。一般來說只用於少量資料加密。
// 金鑰對
	private KeyPair keyPair = null;

	/**
	 * 初始化金鑰對
	 */
	public RSAUtil() {
		try {
			this.keyPair = this.generateKeyPair();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 生成金鑰對
	 * 
	 * @return KeyPair
	 * @throws Exception
	 */
	private KeyPair generateKeyPair() throws Exception {
		try {
			KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",
					new org.bouncycastle.jce.provider.BouncyCastleProvider());
			// 這個值關係到塊加密的大小,可以更改,但是不要太大,否則效率會低
			final int KEY_SIZE = 1024; //1024-bit金鑰----目前較流行
			keyPairGen.initialize(KEY_SIZE, new SecureRandom());
			KeyPair keyPair = keyPairGen.genKeyPair();
			return keyPair;
		} catch (Exception e) {
			throw new Exception(e.getMessage());
		}

	}

	/**
	 * 生成公鑰
	 * 
	 * @param modulus
	 * @param publicExponent
	 * @return RSAPublicKey
	 * @throws Exception
	 */
	private RSAPublicKey generateRSAPublicKey(byte[] modulus,
			byte[] publicExponent) throws Exception {

		KeyFactory keyFac = null;
		try {
			keyFac = KeyFactory.getInstance("RSA",
					new org.bouncycastle.jce.provider.BouncyCastleProvider());
		} catch (NoSuchAlgorithmException ex) {
			throw new Exception(ex.getMessage());
		}
		RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(
				modulus), new BigInteger(publicExponent));
		try {
			return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
		} catch (InvalidKeySpecException ex) {
			throw new Exception(ex.getMessage());
		}

	}

	/**
	 * 生成私鑰
	 * 
	 * @param modulus
	 * @param privateExponent
	 * @return RSAPrivateKey
	 * @throws Exception
	 */
	private RSAPrivateKey generateRSAPrivateKey(byte[] modulus,
			byte[] privateExponent) throws Exception {
		KeyFactory keyFac = null;
		try {
			keyFac = KeyFactory.getInstance("RSA",
					new org.bouncycastle.jce.provider.BouncyCastleProvider());
		} catch (NoSuchAlgorithmException ex) {
			throw new Exception(ex.getMessage());
		}
		RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(
				modulus), new BigInteger(privateExponent));
		try {
			return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
		} catch (InvalidKeySpecException ex) {
			throw new Exception(ex.getMessage());
		}
	}

	/**
	 * 加密
	 * 
	 * @param key
	 *            加密的金鑰
	 * @param data
	 *            待加密的明文資料
	 * @return 加密後的資料
	 * @throws Exception
	 */
	public String encrypt(Key key, byte[] data) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance("RSA",
					new org.bouncycastle.jce.provider.BouncyCastleProvider());
			cipher.init(Cipher.ENCRYPT_MODE, key);
			// 獲得加密塊大小,如:加密前資料為128個byte,而key_size=1024 加密塊大小為127
			// byte,加密後為128個byte;
			// 因此共有2個加密塊,第一個127 byte第二個為1個byte
			int blockSize = cipher.getBlockSize();
			int outputSize = cipher.getOutputSize(data.length);// 獲得加密塊加密後塊大小
			int leavedSize = data.length % blockSize;
			int blocksSize = leavedSize != 0 ? data.length / blockSize + 1
					: data.length / blockSize;
			byte[] raw = new byte[outputSize * blocksSize];
			int i = 0;
			while (data.length - i * blockSize > 0) {
				if (data.length - i * blockSize > blockSize)
					cipher.doFinal(data, i * blockSize, blockSize, raw, i
							* outputSize);
				else
					cipher.doFinal(data, i * blockSize, data.length - i
							* blockSize, raw, i * outputSize);
				// 這裡面doUpdate方法不可用,檢視原始碼後發現每次doUpdate後並沒有什麼實際動作除了把byte[]放到ByteArrayOutputStream中
				// ,而最後doFinal的時候才將所有的byte[]進行加密,可是到了此時加密塊大小很可能已經超出了OutputSize所以只好用dofinal方法。
				i++;
			}
			return encryptBASE64(raw);
		} catch (Exception e) {
			throw new Exception(e.getMessage());
		}
	}

	/**
	 * 解密
	 * 
	 * @param key
	 *            解密的金鑰
	 * @param raw
	 *            已經加密的資料
	 * @return 解密後的明文
	 * @throws Exception
	 */
	public byte[] decrypt(Key key, String enRsaStr) throws Exception {
		byte[]raw = decryptBASE64(enRsaStr);
		try {
			Cipher cipher = Cipher.getInstance("RSA",new org.bouncycastle.jce.provider.BouncyCastleProvider());
			cipher.init(cipher.DECRYPT_MODE, key);
			int blockSize = cipher.getBlockSize();
			ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
			int j = 0;
			while (raw.length - j * blockSize > 0) {
				bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
				j++;
			}
			return bout.toByteArray();
		} catch (Exception e) {
			throw new Exception(e.getMessage());
		}
	}

	/**
	 * 返回公鑰
	 * 
	 * @return
	 * @throws Exception
	 */
	public RSAPublicKey getRSAPublicKey() throws Exception {

		// 獲取公鑰
		RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
		// 獲取公鑰係數(位元組陣列形式)
		byte[] pubModBytes = pubKey.getModulus().toByteArray();
		// 返回公鑰公用指數(位元組陣列形式)
		byte[] pubPubExpBytes = pubKey.getPublicExponent().toByteArray();
		// 生成公鑰
		RSAPublicKey recoveryPubKey = this.generateRSAPublicKey(pubModBytes,
				pubPubExpBytes);
		return recoveryPubKey;
	}
	//key to Base64String
	public static String getBase64Key(Key key)
			throws Exception {
		return encryptBASE64(key.getEncoded());
	}

	//byte[]→String
	public static String encryptBASE64(byte[] key) throws Exception {
		return (new BASE64Encoder()).encodeBuffer(key);
	}
	//String→byte[]
	public static byte[] decryptBASE64(String key) throws Exception {
		return (new BASE64Decoder()).decodeBuffer(key);
	}

	/**
	 * 獲取私鑰
	 * 
	 * @return
	 * @throws Exception
	 */
	public RSAPrivateKey getRSAPrivateKey() throws Exception {

		// 獲取私鑰
		RSAPrivateKey priKey = (RSAPrivateKey) keyPair.getPrivate();
		// 返回私鑰係數(位元組陣列形式)
		byte[] priModBytes = priKey.getModulus().toByteArray();
		// 返回私鑰專用指數(位元組陣列形式)
		byte[] priPriExpBytes = priKey.getPrivateExponent().toByteArray();
		// 生成私鑰
		RSAPrivateKey recoveryPriKey = this.generateRSAPrivateKey(priModBytes,
				priPriExpBytes);
		return recoveryPriKey;
	}
	   /**
     * 得到公鑰
     * @param key 金鑰字串(經過base64編碼)
     * @throws Exception
     */
    public static PublicKey getPublicKey(String key) throws Exception {
          byte[] keyBytes;
          keyBytes = (new BASE64Decoder()).decodeBuffer(key);

          X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
          KeyFactory keyFactory = KeyFactory.getInstance("RSA");
          PublicKey publicKey = keyFactory.generatePublic(keySpec);
          return publicKey;
    }
    /**
     * 得到私鑰
     * @param key 金鑰字串(經過base64編碼)
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String key) throws Exception {
          byte[] keyBytes;
          keyBytes = (new BASE64Decoder()).decodeBuffer(key);

          PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
          KeyFactory keyFactory = KeyFactory.getInstance("RSA");
          PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
          return privateKey;
    }

具體到專案中需根據服務端具體情況進行修改,關於資料加密的總結就這麼多。