Java安全筆記

ITLearner發表於2020-02-10

前言

後端介面開發中,涉及到使用者私密資訊(使用者名稱、密碼)等,我們不能傳輸明文,必須使用加密方式傳輸。這次政府專案中,安全測試組提出了明文傳輸漏洞,抽空研究了下Java加解密相關知識,記錄下。

雜湊函式

Java提供了一個名為MessageDigest的類,它屬於java.security包。 此類支援諸如SHA-1SHA 256MD5之類的演算法,以將任意長度的訊息轉換為資訊摘要。

雜湊函式返回的值稱為資訊摘要或簡稱雜湊值。 下圖說明了雜湊函式。

Java安全筆記

要使用雜湊函式加密資料,我們通常按照以下步驟執行:

建立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安全筆記

在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);
複製程式碼

數字簽名

數字簽名允許驗證簽名的作者,日期和時間,驗證訊息內容。 它還包括用於其他功能的身份驗證功能。

Java安全筆記

優點

  • 認證

    數字簽名有助於驗證訊息來源。

  • 完整性

    郵件簽名後,郵件中的任何更改都將使簽名無效。

  • 不可否認

    通過此屬性,任何已簽署某些資訊的實體都不能在以後拒絕簽名。

建立數字簽名

建立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加解密總結

實際專案中,可以按照以下方式實現對稱加密

  1. 服務端提供一個介面,該介面負責隨機生成key(密碼)和iv(偏移量),並將其存入redis(設定超時時間)
  2. 客戶端呼叫介面,獲得key和iv以及一個redis_key,進行資料加密,將加密後的資料以及redis_key傳到服務端
  3. 服務端使用redis_key獲得key和iv,進行解密

總結

在Java EE安全裡,主要是進行客戶端加密,以及服務端解密的過程來實現資料安全傳輸的目的。在這個過程中,特別要注意以下幾點:

  • 隨機性:加密方式不可單一,可通過更換Cipher.getInstance()的String值來隨機生成加密工人進行加密。
  • 保密性:加密使用的金鑰或者偏移量等,需要使用超時、模糊目的等手段進行隱藏,加大破解成本。

沒有完全有效的加密,但是隻要做到破解成本大於加密成本,就是有效的加密。這樣,我們可以不斷地更換加密方式達到我們想要的效果。

? 程式碼倉庫

相關文章