前言
前不久開發的政府項目中,政府邀請的安全測試組提出了明文傳輸漏洞,于是抽空研究了下Java加解密相關知識,記錄在此,以便後面查閱。
我也了解到,在
Java
後端接口開發中,涉及到使用者私密資訊(使用者名、密碼)等,我們不能傳輸明文,必須使用加密方式傳輸。
散列函數
Java提供了一個名為
MessageDigest
的類,它屬于
java.security
包。 此類支援諸如
SHA-1
,
SHA 256
MD5
之類的算法,以将任意長度的消息轉換為資訊摘要。
散列函數傳回的值稱為資訊摘要或簡稱散列值。 下圖說明了散列函數。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SNmFmZ2YWNwUGZhNWNvwFMx8CX0AzLclTMwIzLcRXZu5Sas9Gbuk2Lc9CX6MHc0RHaiojIsJye.png)
要使用散列函數加密資料,我們通常按照以下步驟執行:
建立MessageDigest對象
MessageDigest md = MessageDigest.getInstance("MD5");
提供了
MessageDigest
靜态方法來獲得
getInstance
執行個體,支援的類型可參考 Wiki-SHA家族
MessageDigest
将資料傳遞給建立的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
完成mac操作
String msg = "gcdd1993";
byte[] bytes = msg.getBytes();
byte[] macResult = mac.doFinal(bytes);
數字簽名
數字簽名允許驗證簽名的作者,日期和時間,驗證消息内容。 它還包括用于其他功能的身份驗證功能。
優點
- 認證
數字簽名有助于驗證消息來源。
- 完整性
郵件簽名後,郵件中的任何更改都将使簽名無效。
- 不可否認
通過此屬性,任何已簽署某些資訊的實體都不能在以後拒絕簽名。
建立數字簽名
建立KeyPairGenerator對象
類提供
KeyPairGenerator
方法,該方法接受表示所需密鑰生成算法的String變量,并傳回生成密鑰的
getInstance()
對象。
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
方法接受表示所需轉換的String變量,并傳回實作給定轉換的
getInstance()
Cipher支援以下類型:
Cipher
(128)
AES/CBC/NoPadding
AES/CBC/PKCS5Padding
AES/ECB/NoPadding
AES/ECB/PKCS5Padding
(56)
DES/CBC/NoPadding
DES/CBC/PKCS5Padding
DES/ECB/NoPadding
DES/ECB/PKCS5Padding
(168)
DESede/CBC/NoPadding
DESede/CBC/PKCS5Padding
DESede/ECB/NoPadding
DESede/ECB/PKCS5Padding
(1024, 2048)
RSA/ECB/PKCS1Padding
RSA/ECB/OAEPWithSHA-1AndMGF1Padding
RSA/ECB/OAEPWithSHA-256AndMGF1Padding
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
使用公鑰初始化Cipher對象
Cipher
方法接受兩個參數,一個表示操作模式的整數參數(加密/解密)和一個表示公鑰的Key對象。
init()
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
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
安全裡,主要是進行用戶端加密,以及服務端解密的過程來實作資料安全傳輸的目的。在這個過程中,特别要注意以下幾點:
- 随機性:加密方式不可單一,可通過更換
的String值來随機生成加密勞工進行加密。Cipher.getInstance()
- 保密性:加密使用的密鑰或者偏移量等,需要使用逾時、模糊目的等手段進行隐藏,加大破解成本。
沒有完全有效的加密,但是隻要做到破解成本大于加密成本,就是有效的加密。這樣,我們可以不斷地更換加密方式達到我們想要的效果。
👉 文中所有的示例代碼可以在這裡找到:
https://github.com/gcdd1993/java-security-sample