文章目錄
- 簡介
- AesGcmUtil
- RsaUtil
簡介
RSA其實是非對稱加密的一種,它可以生成一個公鑰和私鑰,使用RSA+公鑰加密的資料,可以使用RSA+私鑰進行解密。這就是非對稱加密的一種。我們可以擷取請求方的公鑰,再用RSA+對方公鑰進行資料加密,再把資料傳回給請求方,請求方拿到資料後,用自己手上的私鑰進行解密,而且隻有公鑰對應的私鑰才能解開加密。這是非常安全的一種資料方式。在JAVA裡,我們使用RSA實作。
雖然RSA的非對稱加密比對稱加密的方式要更安全,但是非對稱加密的開銷更大,特别是資料量大的時候對性能的影響比較大。在java中,我們不會直接就使用RSA對資料進行加密;而是配合對稱加密算法一起使用,對稱加密我打算使用AES加密算法,至于為什麼選擇使用AES,請看下方。
AES優先使用 AES/GCM/NoPadding模式,以下是來自SonarLint的提示:
具體翻譯是:
要進行安全加密,操作模式和填充方案是必須的,并應根據加密算法正确使用:
對于分組密碼加密算法(如AES),建議使用GCM (Galois計數器模式)模式,該模式在内部使用零/無填充模式。相反,這些模式和/或方案是非常不受歡迎的:
電子密碼本(ECB)模式很容易受到攻擊,因為它沒有提供嚴格的消息機密性:在給定密鑰下,任何給定的明文塊總是被加密到相同的密文塊。
帶有PKCS#5填充(或PKCS#7)的密碼塊連結(CBC)容易受到填充oracle攻擊。
RSA加密算法應與推薦的填充方案(OAEP)一起使用
是以在AES中選中使用 GCM模式/并且無填充,來實作資料加密。
那問題來了,我們該使用RSA+AES對資料實作加密呢?下面我來簡單的介紹一下該怎麼使用RSA+AES實作資料傳輸加密:
- A 使用者使用RSA生成公鑰和私鑰
- B 使用者使用RSA生成公鑰和私鑰
- A使用者 和 B使用者 交換對方的公鑰
- A 使用者生成一個Key,使用AES+key對資料進行加密
- A 使用者使用RSA+B公鑰對key進行加密,得到一個加密key(enKey)
- A 使用者把 加密資料 + enKey傳送給 B使用者
- B 使用者接收資料
- B 使用者使用RSA+自己的私鑰解密enKey,得到key
- B 使用者使用AES+key對加密資料進行解密,得到明文資料
以上就是資料傳輸加密的實作思路。RSA的性能開銷要比AES大的多,是以都是使用RSA來加密AES的加密key,資料加密實質上還是使用AES去完成。更多相關的内容請查閱更專業的書籍及資料。以下就是我封裝的AES和RSA的加密類,可以直接拿來用,如果有什麼不足,請大佬留言告知,我進行改進。
AesGcmUtil
import lombok.extern.slf4j.Slf4j;
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
/**
* AES對稱加密
* GCM模式
* NoPadding不填充
*
* @author a_fig [email protected]
* @version V0.1
* @className EncryptAES
* @date 2020/5/12
*/
@Slf4j
public class AesGcmUtil {
/**
* AES 加密算法
*
* @author a_fig [email protected]
* @date 2020/5/13 11:27
*/
private static final String AES = "AES";
/**
* 指定加解密需要使用的 算法/模式/填充
* 以下指: 使用AES算法 / GCM分組加密模式 / 不填充
*
* @author a_fig [email protected]
* @date 2020/5/13 11:31
*/
private static final String AES_CIPHER_GCM_NO_PADDING = "AES/GCM/NoPadding";
private AesGcmUtil() {
}
/**
* 初始化AES的GCM模式 加密/解密 操作器
*
* @param content 内容
* @param initKey 初始key
* @param modes 加密/解密 操作類型
* @return java.lang.String
* @author a_fig [email protected]
* @date 2020/5/13 11:24
*/
private static byte[] init(byte[] content, byte[] initKey, int modes) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
// key生成器
KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);
// 加密因子
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(initKey);
keyGenerator.init(secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
// 初始化GCM
GCMParameterSpec encryptSpec = new GCMParameterSpec(128, initKey);
Cipher cipher = Cipher.getInstance(AES_CIPHER_GCM_NO_PADDING);
cipher.init(modes, secretKey, encryptSpec);
return cipher.doFinal(content);
}
/**
* 使用AES的GCM模式進行資料加密
*
* @param content 原資料
* @param initKey 初始key
* @return java.lang.String
* @author a_fig [email protected]
* @date 2020/5/13 11:24
*/
public static String encrypt(String content, String initKey) {
String cipher = null;
try {
byte[] cipherBytes = init(content.getBytes(StandardCharsets.UTF_8), initKey.getBytes(StandardCharsets.UTF_8), Cipher.ENCRYPT_MODE);
cipher = Base64.getEncoder().encodeToString(cipherBytes);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return cipher;
}
/**
* 使用AES的GCM模式進行資料解密
*
* @param cipherStr 密文
* @param initKey 初始key
* @return java.lang.String
* @author a_fig [email protected]
* @date 2020/5/13 11:24
*/
public static String decrypt(String cipherStr, String initKey) {
String data = null;
try {
byte[] cipherBytes = Base64.getDecoder().decode(cipherStr);
byte[] dataBytes = init(cipherBytes, initKey.getBytes(StandardCharsets.UTF_8), Cipher.DECRYPT_MODE);
data = new String(dataBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return data;
}
}
RsaUtil
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* RSA非對稱加密
* 使用publicKey進行加密
* 使用privateKey進行解密
*
* @author a_fig [email protected]
* @version V0.1
* @className EncryptAesUtil
* @date 2020/5/12
*/
@Slf4j
public class RsaUtil {
private final KeyPair keyPair;
/**
* 指定加密算法RSA非對稱加密
*
* @author a_fig [email protected]
* @date 2020/5/13 12:20
*/
private static final String RSA = "RSA";
/**
* 指定RSA非對稱算法/ECB模式/填充方式
*
* @author a_fig [email protected]
* @date 2020/5/13 12:20
*/
private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
/**
* 初始化密碼器
*
* @author a_fig [email protected]
* @date 2020/5/13 12:14
*/
public AesRsaUtil() {
KeyPairGenerator keyPairGenerator = null;
try {
keyPairGenerator = KeyPairGenerator.getInstance(RSA);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
keyPair = keyPairGenerator == null ? null : keyPairGenerator.generateKeyPair();
}
/**
* 擷取私鑰(String形式)
*
* @return java.lang.String
* @author a_fig [email protected]
* @date 2020/5/13 12:15
*/
public String getPrivateKeyStr() {
return Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
}
/**
* 擷取公鑰(String形式)
*
* @return java.lang.String
* @author a_fig [email protected]
* @date 2020/5/13 12:15
*/
public String getPublicKeyStr() {
return Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
}
/**
* 把String的公鑰轉換為PublicKey
*
* @param key 字元串類型的公鑰
* @return java.security.PublicKey
* @author a_fig [email protected]
* @date 2020/5/13 12:18
*/
private PublicKey toPublicKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory kf = KeyFactory.getInstance(RSA);
return kf.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(key)));
}
/**
* 把String的私鑰轉換為PrivateKey
*
* @param key 字元串類型的私鑰
* @return java.security.PrivateKey
* @author a_fig [email protected]
* @date 2020/5/13 12:18
*/
private PrivateKey toPrivateKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory kf = KeyFactory.getInstance(RSA);
return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key)));
}
/**
* 使用公鑰對資料進行加密
*
* @param content 原資料
* @param publicKey 公鑰(字元串)
* @return java.lang.String
* @author a_fig [email protected]
* @date 2020/5/13 12:20
*/
public String encrypt(String content, String publicKey) {
String result = null;
try {
byte[] dataByte = content.getBytes(StandardCharsets.UTF_8);
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, toPublicKey(publicKey));
result = Base64.getEncoder().encodeToString(cipher.doFinal(dataByte));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return result;
}
/**
* 使用私鑰對密文進行解密
*
* @param cipherStr 密文
* @param privateKey 私鑰(字元串)
* @return java.lang.String
* @author a_fig [email protected]
* @date 2020/5/13 12:20
*/
public String decrypt(String cipherStr, String privateKey) {
String result = null;
try {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.DECRYPT_MODE, toPrivateKey(privateKey));
result = new String(cipher.doFinal(Base64.getDecoder().decode(cipherStr)), StandardCharsets.UTF_8);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return result;
}
}