天天看點

如何保護我們的Java程式安全?——《我的Java打怪日記》前言散列函數消息認證碼數字簽名公私鑰加解密資料第三方類庫總結

前言

前不久開發的政府項目中,政府邀請的安全測試組提出了明文傳輸漏洞,于是抽空研究了下Java加解密相關知識,記錄在此,以便後面查閱。

我也了解到,在

Java

後端接口開發中,涉及到使用者私密資訊(使用者名、密碼)等,我們不能傳輸明文,必須使用加密方式傳輸。

散列函數

Java提供了一個名為

MessageDigest

的類,它屬于

java.security

包。 此類支援諸如

SHA-1

SHA 256

MD5

之類的算法,以将任意長度的消息轉換為資訊摘要。

散列函數傳回的值稱為資訊摘要或簡稱散列值。 下圖說明了散列函數。

如何保護我們的Java程式安全?——《我的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打怪日記》前言散列函數消息認證碼數字簽名公私鑰加解密資料第三方類庫總結

在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);           

數字簽名

數字簽名允許驗證簽名的作者,日期和時間,驗證消息内容。 它還包括用于其他功能的身份驗證功能。
如何保護我們的Java程式安全?——《我的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

  • AES/ECB/NoPadding

  • AES/ECB/PKCS5Padding

  • DES/CBC/NoPadding

    (56)
  • DES/CBC/PKCS5Padding

  • DES/ECB/NoPadding

  • DES/ECB/PKCS5Padding

  • DESede/CBC/NoPadding

    (168)
  • DESede/CBC/PKCS5Padding

  • DESede/ECB/NoPadding

  • DESede/ECB/PKCS5Padding

  • RSA/ECB/PKCS1Padding

    (1024, 2048)
  • RSA/ECB/OAEPWithSHA-1AndMGF1Padding

  • RSA/ECB/OAEPWithSHA-256AndMGF1Padding

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值來随機生成加密勞工進行加密。
  • 保密性:加密使用的密鑰或者偏移量等,需要使用逾時、模糊目的等手段進行隐藏,加大破解成本。

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

👉 文中所有的示例代碼可以在這裡找到:

https://github.com/gcdd1993/java-security-sample