1. 程式人生 > >面試官:說一下你常用的加密演算法

面試官:說一下你常用的加密演算法

加密演算法我們整體可以分為:可逆加密和不可逆加密,可逆加密又可以分為:對稱加密和非對稱加密。 ## 一、不可逆加密 常見的不可逆加密演算法有`MD5`,`HMAC`,`SHA1`、`SHA-224`、`SHA-256`、`SHA-384`,和`SHA-512`,其中`SHA-224`、`SHA-256`、`SHA-384`,和`SHA-512`我們可以統稱為`SHA2`加密演算法,`SHA`加密演算法的安全性要比`MD5`更高,而`SHA2`加密演算法比`SHA1`的要高。其中`SHA`後面的數字表示的是加密後的字串長度,`SHA1`預設會產生一個`160`位的資訊摘要。 不可逆加密演算法最大的特點就是金鑰,但是`HMAC`是需要金鑰的【手動狗頭】。 由於這些加密都是不可逆的,因此比較常用的場景就是使用者密碼加密,其驗證過程就是通過比較兩個加密後的字串是否一樣來確認身份的。網上也有很多自稱是可以破解`MD5`密碼的網站,其原理也是一樣,就是有一個巨大的資源庫,存放了許多字串及對應的`MD5`加密後的字串,通過你輸入的`MD5`加密串來進行比較,如果過你的密碼複雜度比較低,還是有很大機率驗證出來的。 ### 1.1 MD5 > **MD5資訊摘要演算法**(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼雜湊函式,可以產生出一個128位(16位元組)的雜湊值(hash value),用於確保資訊傳輸完整一致。 `MD5`演算法有以下特點: 1、壓縮性:無論資料長度是多少,計算出來的`MD5`值長度相同 2、容易計算性:由原資料容易計算出`MD5`值 3、抗修改性:即便修改一個位元組,計算出來的`MD5`值也會巨大差異 4、抗碰撞性:知道資料和`MD5`值,很小概率找到相同`MD5`值相同的原資料。 ```java public static String md5(String text) { MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } byte[] bytes = messageDigest.digest(text.getBytes()); return Hex.encodeHexString(bytes); } ``` ### 1.2 SHA系列 > **安全雜湊演算法**(英語:Secure Hash Algorithm,縮寫為SHA)是一個密碼雜湊函式家族,是FIPS所認證的安全雜湊演算法。能計算出一個數字訊息所對應到的,長度固定的字串(又稱訊息摘要)的演算法。且若輸入的訊息不同,它們對應到不同字串的機率很高。 > 2005年8月17日的CRYPTO會議尾聲中王小云、姚期智、姚儲楓再度發表更有效率的SHA-1攻擊法,能在2的63次方個計算複雜度內找到碰撞。 也就是說`SHA-1`加密演算法有碰撞的可能性,雖然很小。 ```java public static String sha256(String text) { MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } byte[] bytes = messageDigest.digest(text.getBytes()); return Hex.encodeHexString(bytes); } ``` ### 1.3 HMAC系列 > HMAC是金鑰相關的雜湊運算訊息認證碼(Hash-based Message Authentication Code)的縮寫,由H.Krawezyk,M.Bellare,R.Canetti於1996年提出的一種基於Hash函式和金鑰進行訊息認證的方法,並於1997年作為RFC2104被公佈,並在[IPSec](https://baike.baidu.com/item/IPSec/2472311)和其他網路協議(如[SSL](https://baike.baidu.com/item/SSL/320778))中得以廣泛應用,現在已經成為事實上的Internet安全標準。它可以與任何迭代雜湊函式捆綁使用。 > HMAC演算法更像是一種加密演算法,它引入了金鑰,其安全性已經不完全依賴於所使用的Hash演算法 ```java public static String hmacSha256(String text, SecretKeySpec sk) { Mac mac = null; try { mac = Mac.getInstance("HmacSHA256"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } try { mac.init(sk); } catch (InvalidKeyException e) { e.printStackTrace(); } byte[] rawHmac = mac.doFinal(text.getBytes()); return new String(Base64.encodeBase64(rawHmac)); } ``` 如果要使用不可逆加密,推薦使用SHA256、SHA384、SHA512以及HMAC-SHA256、HMAC-SHA384、HMAC-SHA512這幾種演算法。 ## 二、對稱加密演算法 對稱加密演算法是應用比較早的演算法,在資料加密和解密的時用的都是同一個金鑰,這就造成了金鑰管理困難的問題。常見的對稱加密演算法有`DES`、`3DES`、`AES128`、`AES192`、`AES256` (預設安裝的 `JDK` 尚不支援 `AES256`,需要安裝對應的 `jce` 補丁進行升級 `jce1.7`,`jce1.8`)。其中`AES`後面的數字代表的是金鑰長度。對稱加密演算法的安全性相對較低,比較適用的場景就是內網環境中的加解密。 ### 2.1 DES `DES`是對稱加密演算法領域中的典型演算法,其金鑰預設長度為`56`位。 ```java // 加密 public static String encrypt(byte[] dataSource, String password){ try { SecureRandom random = new SecureRandom(); DESKeySpec desKeySpec = new DESKeySpec(password.getBytes()); //建立一個密匙工廠,然後用它把DESKeySpec轉換成 SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec); //Cipher物件實際完成加密操作 Cipher cipher = Cipher.getInstance("DES"); //用密匙初始化Cipher物件 cipher.init(Cipher.ENCRYPT_MODE, secretKey, random); //正式執行加密操作 return Base64.encodeBase64String(cipher.doFinal(dataSource)); } catch (Throwable e) { e.printStackTrace(); } return null; } // 解密 public static String decrypt(String src, String password) throws Exception{ // DES演算法要求有一個可信任的隨機數源 SecureRandom random = new SecureRandom(); // 建立一個DESKeySpec物件 DESKeySpec desKeySpec = new DESKeySpec(password.getBytes()); // 建立一個密匙工廠 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); // 將DESKeySpec物件轉換成SecretKey物件 SecretKey secretKey = keyFactory.generateSecret(desKeySpec); // Cipher物件實際完成解密操作 Cipher cipher = Cipher.getInstance("DES"); // 用密匙初始化Cipher物件 cipher.init(Cipher.DECRYPT_MODE, secretKey, random); // 真正開始解密操作 return new String(cipher.doFinal(Base64.decodeBase64(src))); } ``` ### 2.2 3DES `3DES`(即Triple DES)是`DES`向`AES`過渡的加密演算法,它使用3條56位的金鑰對資料進行三次加密。是`DES`的一個更安全的變形。它以`DES`為基本模組,通過組合分組方法設計出分組加密演算法。比起最初的`DES`,`3DES`更為安全。金鑰長度預設為`168`位,還可以選擇`128`位。 ```java public static String encryptThreeDESECB(String src, String key) { try{ DESedeKeySpec dks = new DESedeKeySpec(key.getBytes("UTF-8")); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede"); SecretKey securekey = keyFactory.generateSecret(dks); Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, securekey); byte[] b = cipher.doFinal(src.getBytes("UTF-8")); String ss = new String(Base64.encodeBase64(b)); ss = ss.replaceAll("\\+", "-"); ss = ss.replaceAll("/", "_"); return ss; } catch(Exception ex){ ex.printStackTrace(); return src; } } public static String decryptThreeDESECB(String src, String key) { try{ src = src.replaceAll("-", "+"); src = src.replaceAll("_", "/"); byte[] bytesrc = Base64.decodeBase64(src.getBytes("UTF-8")); // --解密的key DESedeKeySpec dks = new DESedeKeySpec(key.getBytes("UTF-8")); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede"); SecretKey securekey = keyFactory.generateSecret(dks); // --Chipher物件解密 Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, securekey); byte[] retByte = cipher.doFinal(bytesrc); return new String(retByte, "UTF-8"); } catch(Exception ex){ ex.printStackTrace(); return src; } } ``` ## 2.3 AES `AES` 高階資料加密標準,能夠有效抵禦已知的針對`DES`演算法的所有攻擊,預設金鑰長度為`128`位,還可以供選擇`192`位,`256`位。這裡順便提一句這個位指的是bit。 ```java private static final String defaultCharset = "UTF-8"; private static final String KEY_AES = "AES"; private static final String KEY_MD5 = "MD5"; private static MessageDigest md5Digest; static { try { md5Digest = MessageDigest.getInstance(KEY_MD5); } catch (NoSuchAlgorithmException e) { } } /** * 加密 */ public static String encrypt(String data, String key) { return doAES(data, key, Cipher.ENCRYPT_MODE); } /** * 解密 */ public static String decrypt(String data, String key) { return doAES(data, key, Cipher.DECRYPT_MODE); } /** * 加解密 */ private static String doAES(String data, String key, int mode) { try { boolean encrypt = mode == Cipher.ENCRYPT_MODE; byte[] content; if (encrypt) { content = data.getBytes(defaultCharset); } else { content = Base64.decodeBase64(data.getBytes()); } SecretKeySpec keySpec = new SecretKeySpec(md5Digest.digest(key.getBytes(defaultCharset)) , KEY_AES); Cipher cipher = Cipher.getInstance(KEY_AES);// 建立密碼器 cipher.init(mode, keySpec);// 初始化 byte[] result = cipher.doFinal(content); if (encrypt) { return new String(Base64.encodeBase64(result)); } else { return new String(result, defaultCharset); } } catch (Exception e) { } return null; } ``` 推薦使用對稱加密演算法有:`AES128`、`AES192`、`AES256`。 ## 三、非對稱加密演算法 非對稱加密演算法有兩個金鑰,這兩個金鑰完全不同但又完全匹配。只有使用匹配的一對公鑰和私鑰,才能完成對明文的加密和解密過程。常見的非對稱加密有`RSA`、`SM2`等。 ### 3.1 RSA `RSA`金鑰至少為500位長,一般推薦使用1024位。 ```java //非對稱金鑰演算法 public static final String KEY_ALGORITHM = "RSA"; /** * 金鑰長度,DH演算法的預設金鑰長度是1024 * 金鑰長度必須是64的倍數,在512到65536位之間 */ private static final int KEY_SIZE = 1024; //公鑰 private static final String PUBLIC_KEY = "RSAPublicKey"; //私鑰 private static final String PRIVATE_KEY = "RSAPrivateKey"; /** * 初始化金鑰對 * * @return Map 甲方金鑰的Map */ public static Map