前奏
最近公司一個專案在傳輸資料的時候,測試部門安全掃描後,發現密碼型別的資料是明文傳輸的,果斷不符合要求,讓加密,就有了接下來的故事。
使用場景
前後端使用HTTP協議進行互動的時候,由於HTTP報文為明文,所以通常情況下對於比較敏感的資訊可以通過加密在前端加密,然後在後端解密實現"混淆"的效果,避免在傳輸過程中敏感資訊的洩露(如,密碼,證件資訊等)。不過前端加密只能保證傳輸過程中資訊是‘混淆’過的,對於高手來說,打個debugger,照樣可以獲取到資料,並不安全,所謂的前端加密只是稍微增加了攻擊者的成本,並不能保證真正的安全。
綜上,服務端絕對不能相信前端傳遞過來的密文直接儲存入庫,只能通過服務端自己加密進行加密儲存。那麼前端加密是不是就沒有意義了呢?答案是否定的,至少可以保證傳輸過程中不是明文傳輸,如果前後端互動需要安全的通道建議使用HTTPS協議進行通訊。
分類
簡單來說,加密分兩種方式
- 對稱加密
對稱加密採用了對稱密碼編碼技術,它的特點是檔案加密和解密使用相同的金鑰加密也就是金鑰也可以用作解密金鑰,這種方法在密碼學中叫做對稱加密演算法,對稱加密演算法使用起來簡單快捷,金鑰較短,且破譯困難,除了資料加密標準(DES),另一個對稱金鑰加密系統是國際資料加密演算法(IDEA),它比DES的加密性好,而且對計算機功能要求也沒有那麼高
常見的對稱加密演算法有DES、3DES、Blowfish、IDEA、RC4、RC5、RC6和AES - 非對稱加密
非對稱加密演算法需要兩個金鑰:公鑰(publickey)和私鑰(privatekey)。 公鑰與私鑰是一對,如果用公鑰對資料進行加密,只有用對應的私鑰才能解密;如果用私鑰對資料進行加密,那麼只有用對應的公鑰才能解密。因為加密和解密使用的是兩個不同的金鑰,所以這種演算法叫作非對稱加密演算法。 非對稱加密演算法實現機密資訊交換的基本過程是:甲方生成一對金鑰並將其中的一把作為公鑰向其它方公開;得到該公鑰的乙方使用該金鑰對機密資訊進行加密後再傳送給甲方;甲方再用自己儲存的另一把專用金鑰對加密後的資訊進行解密。甲方只能用其專用金鑰解密由其公鑰加密後的任何資訊。
常見的非對稱加密演算法有:RSA、ECC(移動裝置用)、Diffie-Hellman、El Gamal、DSA(數字簽名用)
對稱加密DES實現方式
(1)使用Crypto-JS通過DES演算法在前端加密
npm install crypto-js
複製程式碼
(2)加解密
使用DES演算法,工作方式為ECB,填充方式為PKcs7
var CryptoJS = require("crypto-js");
const secretKey = 'com.sevenlin.foo.key';
var afterEncrypt = CryptoJS.DES.encrypt('encryptCode', CryptoJS.enc.Utf8.parse(secretKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString()
console.log(afterEncrypt);//8/nZ2vZXxOzPhU7ZHBwz7w==
var afterDecrypt = CryptoJS.DES.decrypt(afterEncrypt, CryptoJS.enc.Utf8.parse(secretKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
console.log(afterDecrypt);//encryptCode
複製程式碼
(3)使用BC通過DES演算法在後端解密
a.安裝
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
複製程式碼
b.加解密工具
public class DesCipherUtil {
private DesCipherUtil() {
throw new AssertionError("No DesCipherUtil instances for you!");
}
static {
// add BC provider
Security.addProvider(new BouncyCastleProvider());
}
/**
* 加密
*
* @param encryptText 需要加密的資訊
* @param key 加密金鑰
* @return 加密後Base64編碼的字串
*/
public static String encrypt(String encryptText, String key) {
if (encryptText == null || key == null) {
throw new IllegalArgumentException("encryptText or key must not be null");
}
try {
DESKeySpec desKeySpec = new DESKeySpec(key.getBytes());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS7Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] bytes = cipher.doFinal(encryptText.getBytes(Charset.forName("UTF-8")));
return Base64.getEncoder().encodeToString(bytes);
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | NoSuchPaddingException
| BadPaddingException | NoSuchProviderException | IllegalBlockSizeException e) {
throw new RuntimeException("encrypt failed", e);
}
}
/**
* 解密
*
* @param decryptText 需要解密的資訊
* @param key 解密金鑰,經過Base64編碼
* @return 解密後的字串
*/
public static String decrypt(String decryptText, String key) {
if (decryptText == null || key == null) {
throw new IllegalArgumentException("decryptText or key must not be null");
}
try {
DESKeySpec desKeySpec = new DESKeySpec(key.getBytes());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS7Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(decryptText));
return new String(bytes, Charset.forName("UTF-8"));
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | NoSuchPaddingException
| BadPaddingException | NoSuchProviderException | IllegalBlockSizeException e) {
throw new RuntimeException("decrypt failed", e);
}
}
}
複製程式碼
c.解密前端的加密資訊
String fromWeb = "8/nZ2vZXxOzPhU7ZHBwz7w==";
String key = "com.sevenlin.foo.key";
String afterDecrypt = DesCipherUtil.decrypt(fromWeb, key);
System.out.println(afterDecrypt);//encryptCode
複製程式碼
非對稱加密
(1)下載加密檔案依賴
(2) 引入依賴
<script src="bin/jsencrypt.min.js"></script>
複製程式碼
或者
import '../../assets/js/jsencrypt.min.js';
複製程式碼
(3)使用方式
this.privateKey=`-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQ
WMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNR
aY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB
AoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fv
xTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeH
m7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd
8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAF
z/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5
rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIM
V7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATe
aTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5Azil
psLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Oz
uku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876
-----END RSA PRIVATE KEY-----`;
this.publicKye=`-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN
FOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76
xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4
gwQco1KRMDSmXSMkDwIDAQAB
-----END PUBLIC KEY-----`;
var encrypt = new JSEncrypt();
encrypt.setPublicKey(this.publicKye);
var encrypted = encrypt.encrypt('encryptCode');//w1a1FXmlbFj9yOxLCoqIzNo2ytXypyupZABsi/e4kMA9mERngmaDwlOuHsUDQKC0nK1v7Ehr3vYKcALFQvjscWEkGIW/UWCk73jArwqEYF1wd45eHSCPwUeB85Ellr+IYTqhZXcfmHZUCuprF2gayPUecq7F51aWxpfqMP0uvtY=
// Decrypt with the private key...
var decrypt = new JSEncrypt();
decrypt.setPrivateKey(this.privateKey);
var uncrypted = decrypt.decrypt(encrypted);//encryptCode
複製程式碼
生成publickey和privateKey的線上地址
雖然前端可以加密,終歸不是安全方式,如果為了更加的安全還是使用https傳輸,後端加密儲存吧!