前言
後端介面開發中,涉及到使用者私密資訊(使用者名稱、密碼)等,我們不能傳輸明文,必須使用加密方式傳輸。這次政府專案中,安全測試組提出了明文傳輸漏洞,抽空研究了下Java加解密相關知識,記錄下。
雜湊函式
Java提供了一個名為MessageDigest
的類,它屬於java.security
包。 此類支援諸如SHA-1
,SHA 256
,MD5
之類的演算法,以將任意長度的訊息轉換為資訊摘要。
雜湊函式返回的值稱為資訊摘要或簡稱雜湊值。 下圖說明了雜湊函式。
要使用雜湊函式加密資料,我們通常按照以下步驟執行:
建立MessageDigest物件
MessageDigest md = MessageDigest.getInstance("MD5");
複製程式碼
MessageDigest
提供了getInstance
靜態方法來獲得MessageDigest
例項,支援的型別可參考Wiki-SHA家族
將資料傳遞給建立的MessageDigest物件
md.update("gcdd1993".getBytes());
複製程式碼
生成訊息摘要
byte[] digest = md.digest();
複製程式碼
通常我們會將其轉換為Hex字串
StringBuffer hexString = new StringBuffer();
for (byte aDigest : digest) {
hexString.append(Integer.toHexString(0xFF & aDigest));
}
System.out.println("Hex format : " + hexString.toString());
複製程式碼
訊息認證碼
MAC(訊息認證碼)演算法是一種對稱金鑰加密技術,用於提供訊息認證。要建立MAC過程,傳送方和接收方共享對稱金鑰K。
實質上,MAC是在基礎訊息上生成的加密校驗和,它與訊息一起傳送以確保訊息驗證。
使用MAC進行身份驗證的過程如下圖所示
在Java中,javax.crypto
包的Mac類提供了訊息認證程式碼的功能。按照以下步驟使用此類建立訊息身份驗證程式碼。
建立KeyGenerator物件
KeyGenerator keyGen = KeyGenerator.getInstance("DES");
複製程式碼
KeyGenerator
支援以下型別:
- AES (128)
- DES (56)
- DESede (168)
- HmacSHA1
- HmacSHA256
建立SecureRandom物件
SecureRandom secureRandom = new SecureRandom();
複製程式碼
初始化KeyGenerator
keyGen.init(secureRandom);
複製程式碼
生成金鑰
Key key = keyGen.generateKey();
複製程式碼
使用金鑰初始化Mac物件
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);
複製程式碼
Mac
支援以下型別:
- HmacMD5
- HmacSHA1
- HmacSHA256
完成mac操作
String msg = "gcdd1993";
byte[] bytes = msg.getBytes();
byte[] macResult = mac.doFinal(bytes);
複製程式碼
數字簽名
數字簽名允許驗證簽名的作者,日期和時間,驗證訊息內容。 它還包括用於其他功能的身份驗證功能。
優點
-
認證
數字簽名有助於驗證訊息來源。
-
完整性
郵件簽名後,郵件中的任何更改都將使簽名無效。
-
不可否認
通過此屬性,任何已簽署某些資訊的實體都不能在以後拒絕簽名。
建立數字簽名
建立KeyPairGenerator物件
KeyPairGenerator
類提供getInstance()
方法,該方法接受表示所需金鑰生成演算法的String變數,並返回生成金鑰的KeyPairGenerator
物件。
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DSA");
複製程式碼
初始化KeyPairGenerator物件
KeyPairGenerator
類提供了一個名為initialize()
的方法,該方法用於初始化金鑰對生成器。 此方法接受表示金鑰大小的整數值。
keyPairGen.initialize(2048);
複製程式碼
生成KeyPair
使用
generateKeyPair()
方法生成金鑰對
KeyPair pair = keyPairGen.generateKeyPair();
複製程式碼
從金鑰對中獲取私鑰
PrivateKey privateKey = pair.getPrivate();
複製程式碼
建立簽名物件
Signature
類的getInstance()
方法接受表示所需簽名演算法的字串引數,並返回相應的Signature
物件。Signature支援以下型別:
- SHA1withDSA
- SHA1withRSA
- SHA256withRSA
Signature sign = Signature.getInstance("SHA256withDSA");
複製程式碼
初始化簽名物件
sign.initSign(privateKey);
複製程式碼
將資料新增到Signature物件
String msg = "gcdd1993";
sign.update(msg.getBytes());
複製程式碼
計算簽名
byte[] signature = sign.sign();
複製程式碼
驗證簽名
我們建立簽名後,通常可以將私鑰傳送到客戶端,以進行簽名操作。服務端儲存公鑰,以進行簽名驗證
初始化簽名物件以進行驗證
使用公鑰初始化簽名物件
sign.initVerify(pair.getPublic());
複製程式碼
更新要驗證的資料
sign.update(msg.getBytes());
複製程式碼
驗證簽名
boolean verify = sign.verify(signature);
Assert.assertTrue(verify);
複製程式碼
公私鑰加解密資料
可以使用
javax.crypto
包的Cipher類加密給定資料。
獲取公私鑰的步驟,與簽名類似
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048);
KeyPair pair = keyPairGen.generateKeyPair();
PublicKey publicKey = pair.getPublic();
複製程式碼
加密資料
建立一個Cipher物件
Cipher
類的getInstance()
方法接受表示所需轉換的String變數,並返回實現給定轉換的Cipher
物件。Cipher支援以下型別:
- AES/CBC/NoPadding (128)
- AES/CBC/PKCS5Padding (128)
- AES/ECB/NoPadding (128)
- AES/ECB/PKCS5Padding (128)
- DES/CBC/NoPadding (56)
- DES/CBC/PKCS5Padding (56)
- DES/ECB/NoPadding (56)
- DES/ECB/PKCS5Padding (56)
- DESede/CBC/NoPadding (168)
- DESede/CBC/PKCS5Padding (168)
- DESede/ECB/NoPadding (168)
- DESede/ECB/PKCS5Padding (168)
- RSA/ECB/PKCS1Padding (1024, 2048)
- RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
- RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
複製程式碼
使用公鑰初始化Cipher物件
Cipher
類的init()
方法接受兩個引數,一個表示操作模式的整數引數(加密/解密)和一個表示公鑰的Key物件。
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
複製程式碼
將資料新增到Cipher物件
Cipher
類的update()
方法接受表示要加密的資料的位元組陣列,並使用給定的資料更新當前物件。
String msg = "gcdd1993";
cipher.update(msg.getBytes());
複製程式碼
加密資料
byte[] cipherText = cipher.doFinal();
複製程式碼
解密資料
使用私鑰初始化Cipher物件
cipher.init(Cipher.DECRYPT_MODE, pair.getPrivate());
複製程式碼
解密資料
byte[] decipheredText = cipher.doFinal(cipherText);
Assert.assertEquals(msg, new String(decipheredText));
複製程式碼
第三方類庫
前後端適用且應用廣泛的是
Crypto-JS
,使用Crypto-JS
可以非常方便地在 JavaScript 進行 MD5、SHA1、SHA2、SHA3、RIPEMD-160 雜湊雜湊,進行 AES、DES、Rabbit、RC4、Triple DES 加解密。
AES加密
高階加密標準(英語:Advanced Encryption Standard,縮寫:AES),在密碼學中又稱Rijndael加密法,是美國聯邦政府採用的一種區塊加密標準。這個標準用來替代原先的DES,已經被多方分析且廣為全世界所使用。
一般來說,我們可以在服務端隨機生成金鑰,然後將金鑰傳送給客戶端進行加密,上傳密文到服務端,服務端進行解密。
本文只討論Java的AES加解密方式。
引入Jar包
compile group: 'org.webjars.npm', name: 'crypto-js', version: '3.1.8'
複製程式碼
生成金鑰
Random random = new Random();
byte[] key = new byte[16];
random.nextBytes(key);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
複製程式碼
生成偏移量
byte[] iv = new byte[16];
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
複製程式碼
建立Cipher物件
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
複製程式碼
初始化Cipher為加密工作過程
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
複製程式碼
加密
byte[] original = cipher.doFinal(encrypted1);
複製程式碼
AES解密
初始化Cipher為解密工作過程
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
複製程式碼
解密
byte[] bytes = cipher.doFinal(original);
Assert.assertEquals(data, new String(bytes, StandardCharsets.UTF_8));
複製程式碼
AES加解密總結
實際專案中,可以按照以下方式實現對稱加密
- 服務端提供一個介面,該介面負責隨機生成key(密碼)和iv(偏移量),並將其存入redis(設定超時時間)
- 客戶端呼叫介面,獲得key和iv以及一個redis_key,進行資料加密,將加密後的資料以及redis_key傳到服務端
- 服務端使用redis_key獲得key和iv,進行解密
總結
在Java EE安全裡,主要是進行客戶端加密,以及服務端解密的過程來實現資料安全傳輸的目的。在這個過程中,特別要注意以下幾點:
- 隨機性:加密方式不可單一,可通過更換
Cipher.getInstance()
的String值來隨機生成加密工人進行加密。 - 保密性:加密使用的金鑰或者偏移量等,需要使用超時、模糊目的等手段進行隱藏,加大破解成本。
沒有完全有效的加密,但是隻要做到破解成本大於加密成本,就是有效的加密。這樣,我們可以不斷地更換加密方式達到我們想要的效果。
? 程式碼倉庫