一、對稱加密
對稱加密的特點:
1)、速度快,通常在消息發送方需要加密大量數據時使用。
2)、密鑰是控制加密及解密過程的指令。
3)、算法是一組規則,規定如何進行加密和解密。
典型應用場景:離線的大量數據加密(用於存儲的)
對稱加密的工作過程如下圖所示
加密的安全性不僅取決於加密算法本身,密鑰管理的安全性更是重要。如何把密鑰安全地傳遞到解密者手上就成了必須要解決的問題。
二、非對稱加密
非對稱加密算法是一種密鑰的保密方法,加密和解密使用兩個不同的密鑰,公開密鑰(publickey:簡稱公鑰)和私有密鑰(privatekey:簡稱私鑰)。公鑰與私鑰是一對,如果用公鑰對數據進行加密,只有用對應的私鑰才能解密。
非對稱加密算法的特點:
1)、算法強度復雜
2)、加密解密速度沒有對稱密鑰算法的速度快
經典應用場景:1、數字簽名(私鑰加密,公鑰驗證) 。2、加解密(公鑰加密,私鑰解密)
常用的算法:RSA、Elgamal、背包算法、Rabin、D-H、ECC(橢圓曲線加密算法)。
三、非對稱加密之加解密
加解密示意圖如下

四、非對稱加密之數字簽名
數字簽名通常使用私鑰生成簽名,使用公鑰驗證簽名。
簽名及驗證過程:
1. 發送方用一個哈希函數(例如MD5)從報文文本中生成報文摘要,然后用自己的私鑰對這個摘要進行加密
2. 將加密后的摘要作為報文的數字簽名和原始報文一起發送給接收方
3. 接收方用與發送方一樣的哈希函數從接收到的原始報文中計算出報文摘要,
4. 接收方再用發送方的公用密鑰來對報文附加的數字簽名進行解密
5. 如果這兩個摘要相同、接收方就能確認該數字簽名是發送方的。
數字簽名驗證的兩個作用:
1)、確定消息確實是由發送方簽名並發出來的
2)、確定消息的完整性
五、公鑰和私鑰到底誰來加密?
第一種用法:公鑰加密,私鑰解密。----用於加解密
第二種用法:私鑰簽名,公鑰驗簽。----用於簽名
可以理解如下:
既然是加密,那肯定是不希望別人知道我的消息,所以只有我才能解密,所以可以得出公鑰加密,私鑰解密。
既然是簽名,那肯定不希望別人冒充我發消息,只有我才能發布這個簽名,所以可以得出私鑰簽名,公鑰驗簽。用於讓所有公鑰所有者驗證私鑰所有者的身份,防止私鑰所有者發布的內容被篡改。但是不用來保證內容不被他人獲得。
六、加解密的應用----登錄時對密碼進行加密
使用前后端分離的系統架構,它們的聯系是通過接口調用把數據聯系起來的,所有東西都走接口需要保證接口調用數據的安全,黑客可以通過抓包工具獲取我們訪問的信息(請求數據和響應數據)。因為http協議傳輸的數據是明文的,雖然https確實對傳輸的數據進行了一次加密,但是可以破解的。
前后端rsa加密的流程:
第一步:返回publicKey給前端,用來對password等敏感字段的加密。
第二步,前端進行password敏感字段的加密。
第三步:通過post請求將數據給后端。
第四步:用privateKey進行解密。
由於安裝的jsencrypt.js是沒有壓縮得,里面包含YUI,會產生安全漏洞:JavaScript庫YUI版本過低,jsencrypt.min.js文件中不含有YUI,故用jsencrypt.min.js
1、將jsencrypt.min.js文件拷貝到utils目錄中
2、在index.html中引入jsencrypt.min.js文件
為什么在index.html中引入jsencrypt.min.js?
vue中的頁面都是單頁面,但是都是index.html上承載的,這就是為什么你能在index.html中看到id為app的div,其實就是和App.vue對應,App.vue里面的標簽將會把路由相關內容(index.js)渲染在這個地方,總之index.html是項目運行的入口。
項目加載的過程是index.tml->main.js->app.vue->index.js->單頁面(XXX.vue)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title>vue_project_01</title> </head> <body> <noscript> <strong>We're sorry but vue_project_01 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
3、前端代碼
<el-form-item prop="password"> <span class="svg-container"> <svg-icon icon-class="password" /> </span> <el-input :type="pwdType" v-model.trim="loginForm.password" name="password" auto-complete="off" placeholder="密碼" /> <span class="show-pwd" @click="showPwd"> <svg-icon icon-class="eye" /> </span> </el-form-item>
<el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
登錄
</el-button>
</el-form-item>
handleLogin方法:
handleLogin() { this.$refs.loginForm.validate(valid => { if (valid) { ..... const loginData = { .... password: this.encryptedData(this.loginForm.password), ..... } this.$store.dispatch('Login', loginData).then((res) => { .... }).catch((error) => { console.log(error) }).finally(() => { this.loading = false }) } else { return false } }) },
encryptedData方法:
encryptedData(data) { // 私鑰 和后端溝通寫死了 var publicKey = '省略不寫' // 新建JSEncrypt對象 const encryptor = new JSEncrypt() // 設置公鑰 encryptor.setPublicKey(publicKey) // 加密數據 return encryptor.encrypt(data) },
login方法
export function login(.., password, ...) { const params = { .., 'password': password,.... } return request({ url: '/user/loginForNew', method: 'post', data: params }) }
4、后台代碼
controller
@PostMapping("/loginForNew") @ResponseBody public Result loginForNew(@RequestBody JSONObject map) { String password = map.getString("password"); .... try { password = rsaService.RSADecryptDataPEM(password, RsaKeys.getServerPrvKeyPkcs8());
RSADecryptDataPEM方法:
public class RsaServiceImpl implements RsaService { /*** * RSA解密 * * @param encryptData * @return * @throws Exception */ public String RSADecryptDataPEM(String encryptData, String prvKey) throws Exception { byte[] encryptBytes = encryptData.getBytes(); byte[] prvdata = RSA.decryptByPrivateKey(Base64Utils.decode(encryptData), prvKey); String outString = new String(prvdata, "UTF-8"); return outString; } @Override public String RSADecryptDataBytes(byte[] encryptBytes, String prvKey) throws Exception { // TODO Auto-generated method stub byte[] prvdata = RSA.decryptByPrivateKey(encryptBytes, prvKey); String outString = new String(prvdata, "utf-8"); return outString; } /*** * RSA加密 * * @param data * @return * @throws Exception */ public String RSAEncryptDataPEM(String data, String pubKey) throws Exception { byte[] pubdata = RSA.encryptByPublicKey(data.getBytes("UTF-8"), pubKey); String outString = new String(Base64Utils.encode(pubdata)); return outString; } @Override public String getRsaAlgorithm() { // TODO Auto-generated method stub KeyFactory keyFactory = null; try { keyFactory = KeyFactory.getInstance("RSA"); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } return keyFactory.getAlgorithm(); } }
RSA工具類
public class RSA { /** */ /** * 加密算法RSA */ public static final String KEY_ALGORITHM = "RSA"; /** */ /** * 簽名算法 */ public static final String SIGNATURE_ALGORITHM = "MD5withRSA"; /** */ /** * 獲取公鑰的key */ private static final String PUBLIC_KEY = "RSAPublicKey"; /** */ /** * 獲取私鑰的key */ private static final String PRIVATE_KEY = "RSAPrivateKey"; /** */ /** * RSA最大加密明文大小 */ //private static final int MAX_ENCRYPT_BLOCK = 117; private static final int MAX_ENCRYPT_BLOCK = 245; /** */ /** * RSA最大解密密文大小 */ //private static final int MAX_DECRYPT_BLOCK = 128; private static final int MAX_DECRYPT_BLOCK = 256; /** */ /** * <p> * 生成密鑰對(公鑰和私鑰) * </p> * * @return * @throws Exception */ public static Map<String, Object> genKeyPair() throws Exception { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); keyPairGen.initialize(1024); KeyPair keyPair = keyPairGen.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); Map<String, Object> keyMap = new HashMap<String, Object>(2); keyMap.put(PUBLIC_KEY, publicKey); keyMap.put(PRIVATE_KEY, privateKey); return keyMap; } /** */ /** * <p> * 用私鑰對信息生成數字簽名 * </p> * * @param data 已加密數據 * @param privateKey 私鑰(BASE64編碼) * @return * @throws Exception */ public static String sign(byte[] data, String privateKey) throws Exception { byte[] keyBytes = Base64Utils.decode(privateKey); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec); Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); signature.initSign(privateK); signature.update(data); return Base64Utils.encode(signature.sign()); } /** */ /** * 112. * <p> * 113. * 校驗數字簽名 * 114. * </p> * 115. * * 116. * @param data 已加密數據 * 117. * @param publicKey 公鑰(BASE64編碼) * 118. * @param sign 數字簽名 * 119. * * 120. * @return * 121. * @throws Exception * 122. * * 123. */ public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { byte[] keyBytes = Base64Utils.decode(publicKey); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); PublicKey publicK = keyFactory.generatePublic(keySpec); Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); signature.initVerify(publicK); signature.update(data); return signature.verify(Base64Utils.decode(sign)); } /** */ /** * 137. * <P> * 138. * 私鑰解密 * 139. * </p> * 140. * * 141. * @param encryptedData 已加密數據 * 142. * @param privateKey 私鑰(BASE64編碼) * 143. * @return * 144. * @throws Exception * 145. */ public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception { // byte[] keyBytes = Base64Utils.decode(privateKey); // PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key privateK = getPrivateKey(privateKey); //Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, privateK); int inputLen = encryptedData.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // 對數據分段解密 while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_DECRYPT_BLOCK) { cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); } else { cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_DECRYPT_BLOCK; } byte[] decryptedData = out.toByteArray(); out.close(); return decryptedData; } /** * 得到私鑰 * * @param key 密鑰字符串(經過base64編碼) * @throws Exception */ public static PrivateKey getPrivateKey(String key) throws Exception { byte[] keyBytes; keyBytes = (new BASE64Decoder()).decodeBuffer(key); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); return privateKey; } /** * 得到公鑰 * * @param key 密鑰字符串(經過base64編碼) * @throws Exception */ public static PublicKey getPublicKey(String key) throws Exception { byte[] keyBytes; keyBytes = (new BASE64Decoder()).decodeBuffer(key); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(keySpec); return publicKey; } /** */ /** * 176. * <p> * 177. * 公鑰解密 * 178. * </p> * 179. * * 180. * @param encryptedData 已加密數據 * 181. * @param publicKey 公鑰(BASE64編碼) * 182. * @return * 183. * @throws Exception * 184. */ public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception { byte[] keyBytes = Base64Utils.decode(publicKey); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key publicK = keyFactory.generatePublic(x509KeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, publicK); int inputLen = encryptedData.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // 對數據分段解密 while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_DECRYPT_BLOCK) { cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); } else { cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_DECRYPT_BLOCK; } byte[] decryptedData = out.toByteArray(); out.close(); return decryptedData; } /** */ /** * 215. * <p> * 216. * 公鑰加密 * 217. * </p> * 218. * * 219. * @param data 源數據 * 220. * @param publicKey 公鑰(BASE64編碼) * 221. * @return * 222. * @throws Exception * 223. */ public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception { // byte[] keyBytes = Base64Utils.decode(publicKey); // X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key publicK = getPublicKey(publicKey); // 對數據加密 //Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, publicK); int inputLen = data.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // 對數據分段加密 while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); } else { cache = cipher.doFinal(data, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_ENCRYPT_BLOCK; } byte[] encryptedData = out.toByteArray(); out.close(); return encryptedData; } /** */ /** * 255. * <p> * 256. * 私鑰加密 * 257. * </p> * 258. * * 259. * @param data 源數據 * 260. * @param privateKey 私鑰(BASE64編碼) * 261. * @return * 262. * @throws Exception * 263. */ public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception { byte[] keyBytes = Base64Utils.decode(privateKey); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, privateK); int inputLen = data.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // 對數據分段加密 while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); } else { cache = cipher.doFinal(data, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_ENCRYPT_BLOCK; } byte[] encryptedData = out.toByteArray(); out.close(); return encryptedData; } /** */ /** * 309. * <p> * 310. * 獲取公鑰 * 311. * </p> * 312. * * 313. * @param keyMap 密鑰對 * 314. * @return * 315. * @throws Exception * 316. */ public static String getPublicKey(Map<String, Object> keyMap) throws Exception { Key key = (Key) keyMap.get(PUBLIC_KEY); return Base64Utils.encode(key.getEncoded()); } }
公鑰和私鑰所在實體類
生成公鑰和私鑰的方法參見https://www.cnblogs.com/zwh0910/p/15214672.html的方法一。
public class RsaKeys {//服務器公鑰 private static final String serverPubKey = "省略"; //服務器私鑰(經過pkcs8格式處理) private static final String serverPrvKeyPkcs8 = "省略"; public static String getServerPubKey() { return serverPubKey; } public static String getServerPrvKeyPkcs8() { return serverPrvKeyPkcs8; } }
七、加解密的應用----使用私鑰對JWT進行簽名,使用公鑰驗簽
創建測試類,測試jwt令牌的生成與驗證。
生成公鑰的方法參見https://www.cnblogs.com/zwh0910/p/15214672.html的方法二。
1、生成令牌
//生成一個jwt令牌 @Test public void testCreateJwt(){ //證書文件 String key_location = "xc.keystore"; //密鑰庫密碼 String keystore_password = "xuechengkeystore"; //訪問證書路徑 ClassPathResource resource = new ClassPathResource(key_location); //密鑰工廠 KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, keystore_password.toCharArray()); //密鑰的密碼,此密碼和別名要匹配 String keypassword = "xuecheng"; //密鑰別名 String alias = "xckey"; //密鑰對(密鑰和公鑰) KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias,keypassword.toCharArray()); //私鑰 RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate(); //定義payload信息 Map<String, Object> tokenMap = new HashMap<>(); tokenMap.put("id", "123"); tokenMap.put("name", "mrt"); tokenMap.put("roles", "r01,r02"); tokenMap.put("ext", "1"); //生成jwt令牌 Jwt jwt = JwtHelper.encode(JSON.toJSONString(tokenMap), new RsaSigner(aPrivate)); // 使用私鑰簽名 //取出jwt令牌 String token = jwt.getEncoded(); System.out.println("token="+token); }
2、驗證jwt令牌
//資源服務使用公鑰驗證jwt的合法性,並對jwt解碼 @Test public void testVerify(){ //jwt令牌 String token ="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHQiOiIxIiwicm9sZXMiOiJyMDEscjAyIiwibmFtZSI6Im1ydCIsI mlkIjoiMTIzIn0. KK7_67N5d1Dthd1PgDHMsbi0UlmjGRcm_XJUUwseJ2eZyJJWoPP2IcEZgAU3tUaaKEHUf9wSRwaDgwhrw fyIcSHbs8oy3zOQEL8j5AOjzBBs7vnRmB7DbSaQD7eJ iQVJOXO1QpdmEFgjhc_IBCVTJCVWgZw60IEW1_Lg5tqaLvCiIl26K 48pJB5f‐le2zgYMzqR1L2LyTFkq39rG57VOqqSCi3dapsZQd4ctq95SJCXgGdrUDWtD52rp5 o6_0uq‐ mrbRdRxkrQfsa1j8C5IW2‐T4eUmiN3f9wF9JxUK1__XC1OQkOn‐ZTBCdqwWIygDFbU7sf6KzfHJTm5vfjp6NIA"; //公鑰 String publickey = "‐‐‐‐‐BEGIN PUBLIC KEY‐‐‐‐‐ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAijyxMdq4S6L1Af1rtB8SjCZHNgsQG8JTfGy55e YvzG0B/E4AudR2 prSRBvF7NYPL47scRCNPgLnvbQczBHbBug6uOr78qnWsYxHlW6Aa5dI5NsmOD4DLtSw8eX0hFyK5Fj6ScYOSFBz9cd1nNTvx 2+oIv0lJDcpQdQ hsfgsEr1ntvWterZt/8r7xNN83gHYuZ6TM5MYvjQNBc5qC7Krs9wM7UoQuL+s0X6RlOib7/mcLn/lFLsLD dYQAZkSDx/6+t+1oHdMarChIPYT1sx9Dwj2j2mvFNDT KKKKAq0cv14Vrhz67Vjmz2yMJePDqUi0JYS2r0iIo7n8vN7s83v5u OQIDAQAB‐‐‐‐‐END PUBLIC KEY‐‐‐‐‐"; //校驗jwt Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey)); // 使用公鑰校驗簽名 //獲取jwt原始內容 String claims = jwt.getClaims(); //獲取jwt令牌 String encoded = jwt.getEncoded(); System.out.println(encoded); }