当前位置:   article > 正文

密码加密传输_密码明文传输解决办法

密码明文传输解决办法

1、背景

        我们的系统一般都有用户、密码,用户登录向后端传送密码,明文传输过程中很容易被抓包盗取。我们可以有一下几种解决办法。

1.1 使用https

        https诞生了,它不仅可以将你要传输的密码加密传输,甚至你的所有请求数据在网络传输中都是加密的,别人拿到数据包也没用。

  https真的安全吗?

  https是加密传输, 所以抓到包也没有用, 是密文的。 真是的这样吗? 以百度为例,如下图,可以看到传的用户名,密码(经过了加密)还是可以正常看到明文的。

 

        为什么用https抓包, 还是能看到明文内容呢? 这里我们要理解https的栈流程, 梳理一下就知道, 加密层位于http层和tcp层之间, 所以抓到的http层的数据并没有加密。 同理, 在后台接收端, 经历解密后, 到达http层的数据也是明文。 要注意, https不是对http报文进行加密, 而是对传输数据进行加密, 最终还原http原始报文。 

  这里不得不再次强调,https保证的是传输过程中第三方抓包看到的是密文。客户端和服务端,无论如何都可以拿到明文。其实大多数人认为https加密传输安全是因为没有真正理解https的真实原理。为了更安全就需要自定义加密了。

1.2 自定义加密

        上边说道,https能避免传输的过程中,如果有人截获到数据包只能看到加密后的信息,但是防不了在服务端和客户端截取数据的人。服务器端自不必说,如果黑客都能取到服务器的数据了那你加不加密估计也没什么意义了,但客户端就不一样了,许多密码泄露都是在客户端泄露的。所以客户端密码保护很重要!显然https这点就做不到了。那么,就只有写程序的人自己定义加密方式了。

        还是以百度为例,在登录之前,百度前端获取了一次公钥,用公钥加密后传输加密后的密文密码,后端可以进行解密,这样做到了全流程加密传输。

 

2、后端实现

我们以非对称加密RSA为例。

2.1 引入依赖

引入redis,是因为分布式系统,公钥、私钥需要保存在一个公共缓存中。

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.projectlombok</groupId>
  13. <artifactId>lombok</artifactId>
  14. <optional>true</optional>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-test</artifactId>
  19. <scope>test</scope>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.apache.commons</groupId>
  23. <artifactId>commons-collections4</artifactId>
  24. <version>4.4</version>
  25. </dependency>
  26. <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
  27. <dependency>
  28. <groupId>org.apache.commons</groupId>
  29. <artifactId>commons-lang3</artifactId>
  30. <version>3.12.0</version>
  31. </dependency>
  32. <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
  33. <dependency>
  34. <groupId>commons-codec</groupId>
  35. <artifactId>commons-codec</artifactId>
  36. <version>1.15</version>
  37. </dependency>
  38. <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-ext-jdk15on -->
  39. <dependency>
  40. <groupId>org.bouncycastle</groupId>
  41. <artifactId>bcprov-ext-jdk15on</artifactId>
  42. <version>1.70</version>
  43. </dependency>
  44. <!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
  45. <dependency>
  46. <groupId>com.alibaba.fastjson2</groupId>
  47. <artifactId>fastjson2</artifactId>
  48. <version>2.0.11</version>
  49. </dependency>
  50. <!--redis start-->
  51. <dependency>
  52. <groupId>org.springframework.boot</groupId>
  53. <artifactId>spring-boot-starter-data-redis</artifactId>
  54. </dependency>
  55. <!--spring2.0集成redis所需common-pool2 -->
  56. <dependency>
  57. <groupId>org.apache.commons</groupId>
  58. <artifactId>commons-pool2</artifactId>
  59. </dependency>
  60. <!--redis end-->
  61. </dependencies>

2.2 service

  1. package com.ybw.rsa.demo.service;
  2. /**
  3. * RSA接口
  4. *
  5. * @author ybwei
  6. * @version V1.0
  7. * @className RsaService
  8. * @date 2022/8/13
  9. **/
  10. public interface RsaService {
  11. /**
  12. * 私钥解密
  13. *
  14. * @param encryptText
  15. * @methodName: decryptWithPrivate
  16. * @return: java.lang.String
  17. * @author: ybwei
  18. * @date: 2022/8/12
  19. **/
  20. String decryptWithPrivate(String encryptText) throws Exception;
  21. /**
  22. * 公钥加密-测试
  23. *
  24. * @param plaintext 明文内容
  25. * @methodName: encrypt
  26. * @return: byte[]
  27. * @author: ybwei
  28. * @date: 2022/8/12
  29. **/
  30. byte[] encrypt(String plaintext) throws Exception;
  31. /**
  32. * 私钥解密-测试
  33. *
  34. * @param cipherText 加密后的字节数组
  35. * @methodName: decrypt
  36. * @return: java.lang.String
  37. * @author: ybwei
  38. * @date: 2022/8/12
  39. **/
  40. String decrypt(byte[] cipherText) throws Exception;
  41. /**
  42. * 获取公钥
  43. *
  44. * @methodName: getPublicKey
  45. * @return: java.lang.String
  46. * @author: ybwei
  47. * @date: 2022/8/12
  48. **/
  49. String getPublicKey() throws Exception;
  50. }
  1. package com.ybw.rsa.demo.service.impl;
  2. import com.ybw.rsa.demo.constant.RedisPreConstant;
  3. import com.ybw.rsa.demo.constant.RsaConstant;
  4. import com.ybw.rsa.demo.service.RsaService;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.apache.commons.codec.binary.Base64;
  7. import org.apache.commons.lang3.StringUtils;
  8. import org.springframework.data.redis.core.RedisTemplate;
  9. import org.springframework.stereotype.Service;
  10. import javax.annotation.PostConstruct;
  11. import javax.annotation.Resource;
  12. import javax.crypto.Cipher;
  13. import java.io.UnsupportedEncodingException;
  14. import java.math.BigInteger;
  15. import java.net.URLDecoder;
  16. import java.net.URLEncoder;
  17. import java.security.*;
  18. import java.security.interfaces.RSAPrivateKey;
  19. import java.security.interfaces.RSAPublicKey;
  20. import java.security.spec.InvalidKeySpecException;
  21. import java.security.spec.PKCS8EncodedKeySpec;
  22. import java.security.spec.X509EncodedKeySpec;
  23. import java.util.concurrent.TimeUnit;
  24. /**
  25. * rsa加密
  26. *
  27. * @author ybw
  28. * @version V1.0
  29. * @className RsaServiceImpl
  30. * @date 2022/8/12
  31. **/
  32. @Service
  33. @Slf4j
  34. public class RsaServiceImpl implements RsaService {
  35. @Resource
  36. private RedisTemplate redisTemplate;
  37. /**
  38. * 初始化
  39. *
  40. * @methodName: init
  41. * @return: void
  42. * @author: ybw
  43. * @date: 2022/8/12
  44. **/
  45. @PostConstruct
  46. public void init() throws Exception {
  47. log.info("RsaServiceImpl init start");
  48. Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
  49. Security.addProvider(provider);
  50. SecureRandom random = new SecureRandom();
  51. KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", provider);
  52. generator.initialize(RsaConstant.KEY_SIZE, random);
  53. KeyPair keyPair = generator.generateKeyPair();
  54. //将公钥和私钥存放,登录时会不断请求获取公钥,我们可以将其放到缓存中,而不放入数据库了
  55. //我在想,这个是不是有必要存放到Redis,在分布式场景中?
  56. //貌似有些必要,万一获取到的pubkey是server1中的,拿着server1的pubkey去server2去解密?
  57. storeRsa(keyPair);
  58. log.info("RsaServiceImpl init end");
  59. }
  60. /**
  61. * 将RSA存入缓存
  62. *
  63. * @param keyPair
  64. * @methodName: storeRsa
  65. * @return: void
  66. * @author: ybwei
  67. * @date: 2022/8/13
  68. **/
  69. private void storeRsa(KeyPair keyPair) {
  70. //1、存储公钥key
  71. String publicRedisKey = getRedisKey(RsaConstant.PUBLIC_KEY);
  72. PublicKey publicKey = keyPair.getPublic();
  73. //公钥字符串
  74. String publicKeyStr = new String(Base64.encodeBase64(publicKey.getEncoded()));
  75. redisTemplate.opsForValue().set(publicRedisKey, publicKeyStr, 1, TimeUnit.DAYS);
  76. //2、存储私钥key
  77. String privateRedisKey = getRedisKey(RsaConstant.PRIVATE_KEY);
  78. PrivateKey privateKey = keyPair.getPrivate();
  79. //私钥字符串
  80. String privateKeyStr = new String(Base64.encodeBase64(privateKey.getEncoded()));
  81. redisTemplate.opsForValue().set(privateRedisKey, privateKeyStr, 1, TimeUnit.DAYS);
  82. }
  83. /**
  84. * @className RsaServiceImpl
  85. * @author ybw
  86. * @date 2022/8/12
  87. * @version V1.0
  88. **/
  89. private String getRedisKey(String publicKey) {
  90. return new StringBuilder()
  91. .append(RedisPreConstant.RSA)
  92. .append(publicKey)
  93. .toString();
  94. }
  95. /**
  96. * 从字符串中加载公钥
  97. *
  98. * @methodName: loadPublicKeyByStr
  99. * @return: java.security.interfaces.RSAPublicKey
  100. * @author: ybwei
  101. * @date: 2022/8/13
  102. **/
  103. public RSAPublicKey loadPublicKeyByStr() throws Exception {
  104. try {
  105. //公钥数据字符串
  106. String publicKeyStr = getPublicKey();
  107. byte[] buffer = Base64.decodeBase64(publicKeyStr);
  108. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  109. X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
  110. return (RSAPublicKey) keyFactory.generatePublic(keySpec);
  111. } catch (NoSuchAlgorithmException e) {
  112. throw new Exception("无此算法");
  113. } catch (InvalidKeySpecException e) {
  114. throw new Exception("公钥非法");
  115. } catch (NullPointerException e) {
  116. throw new Exception("公钥数据为空");
  117. }
  118. }
  119. /**
  120. * 从字符串中加载私钥
  121. *
  122. * @methodName: loadPrivateKeyByStr
  123. * @return: java.security.interfaces.RSAPrivateKey
  124. * @author: ybwei
  125. * @date: 2022/8/13
  126. **/
  127. public RSAPrivateKey loadPrivateKeyByStr() throws Exception {
  128. try {
  129. //私钥数据字符串
  130. String privateKeyStr = getPrivateKey();
  131. byte[] buffer = Base64.decodeBase64(privateKeyStr);
  132. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
  133. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  134. return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
  135. } catch (NoSuchAlgorithmException e) {
  136. throw new Exception("无此算法");
  137. } catch (InvalidKeySpecException e) {
  138. throw new Exception("私钥非法");
  139. } catch (NullPointerException e) {
  140. throw new Exception("私钥数据为空");
  141. }
  142. }
  143. /**
  144. * 私钥解密(解密前台公钥加密的密文)
  145. *
  146. * @param encryptText 公钥加密的数据
  147. * @return 私钥解密出来的数据
  148. * @throws Exception e
  149. */
  150. @Override
  151. public String decryptWithPrivate(String encryptText) throws Exception {
  152. if (StringUtils.isBlank(encryptText)) {
  153. return null;
  154. }
  155. byte[] en_byte = Base64.decodeBase64(encryptText.getBytes());
  156. Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
  157. Security.addProvider(provider);
  158. Cipher ci = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
  159. PrivateKey privateKey = loadPrivateKeyByStr();
  160. ci.init(Cipher.DECRYPT_MODE, privateKey);
  161. byte[] res = ci.doFinal(en_byte);
  162. return new String(res);
  163. }
  164. /**
  165. * 公钥加密
  166. *
  167. * @param plaintext 明文内容
  168. * @return byte[]
  169. * @throws UnsupportedEncodingException e
  170. */
  171. @Override
  172. public byte[] encrypt(String plaintext) throws Exception {
  173. String encode = URLEncoder.encode(plaintext, "utf-8");
  174. RSAPublicKey rsaPublicKey = loadPublicKeyByStr();
  175. //获取公钥指数
  176. BigInteger e = rsaPublicKey.getPublicExponent();
  177. //获取公钥系数
  178. BigInteger n = rsaPublicKey.getModulus();
  179. //获取明文字节数组
  180. BigInteger m = new BigInteger(encode.getBytes());
  181. //进行明文加密
  182. BigInteger res = m.modPow(e, n);
  183. return res.toByteArray();
  184. }
  185. /**
  186. * 私钥解密
  187. *
  188. * @param cipherText 加密后的字节数组
  189. * @return 解密后的数据
  190. * @throws UnsupportedEncodingException e
  191. */
  192. @Override
  193. public String decrypt(byte[] cipherText) throws Exception {
  194. RSAPrivateKey prk = loadPrivateKeyByStr();
  195. // 获取私钥参数-指数/系数
  196. BigInteger d = prk.getPrivateExponent();
  197. BigInteger n = prk.getModulus();
  198. // 读取密文
  199. BigInteger c = new BigInteger(cipherText);
  200. // 进行解密
  201. BigInteger m = c.modPow(d, n);
  202. // 解密结果-字节数组
  203. byte[] mt = m.toByteArray();
  204. //转成String,此时是乱码
  205. String en = new String(mt);
  206. //再进行编码,最后返回解密后得到的明文
  207. return URLDecoder.decode(en, "UTF-8");
  208. }
  209. /**
  210. * 获取公钥
  211. *
  212. * @methodName: getPublicKey
  213. * @return: java.lang.String
  214. * @author: ybw
  215. * @date: 2022/8/12
  216. **/
  217. @Override
  218. public String getPublicKey() throws Exception {
  219. //1、获取redis key
  220. String publicRedisKey = getRedisKey(RsaConstant.PUBLIC_KEY);
  221. //2、获取公钥字符串
  222. String publicKeyStr = (String) redisTemplate.opsForValue().get(publicRedisKey);
  223. if (StringUtils.isNotBlank(publicKeyStr)) {
  224. log.info("RsaServiceImpl getPublicKey publicKeyStr:{}", publicKeyStr);
  225. return publicKeyStr;
  226. }
  227. //3、初始化
  228. init();
  229. //4、重新获取公钥字符串
  230. return getPublicKey();
  231. }
  232. /**
  233. * 获取私钥
  234. *
  235. * @methodName: getPrivateKey
  236. * @return: java.lang.String
  237. * @author: ybw
  238. * @date: 2022/8/12
  239. **/
  240. public String getPrivateKey() throws Exception {
  241. //1、获取redis key
  242. String privateRedisKey = getRedisKey(RsaConstant.PRIVATE_KEY);
  243. //2、获取私钥数据字符串
  244. String privateKeyStr = (String) redisTemplate.opsForValue().get(privateRedisKey);
  245. if (StringUtils.isNotBlank(privateKeyStr)) {
  246. log.info("RsaServiceImpl getPrivateKey privateKeyStr:{}", privateKeyStr);
  247. return privateKeyStr;
  248. }
  249. //3、初始化
  250. init();
  251. //4、重新获取公钥字符串
  252. return getPrivateKey();
  253. }
  254. }
  • 项目启动,初始化公钥、私钥。
  • 公钥、私钥有效期1天。
  • 当公钥、私钥失效后,会重新初始化公钥、私钥。
  • 公钥、私钥存入Redis缓存后,可以支持多节点部署。

2.3 接口

获取公钥

  1. package com.ybw.rsa.demo.controller;
  2. import com.ybw.rsa.demo.service.RsaService;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. import javax.annotation.Resource;
  6. /**
  7. * @author ybwei
  8. * @version V1.0
  9. * @className RSAController
  10. * @date 2022/8/12
  11. **/
  12. @RestController
  13. public class RSAController {
  14. @Resource
  15. private RsaService rsaService;
  16. /**
  17. * 获取公钥
  18. *
  19. * @methodName: getPublicKey
  20. * @return: java.lang.String
  21. * @author: ybw
  22. * @date: 2022/8/12
  23. **/
  24. @GetMapping("/getPublicKey")
  25. public String getPublicKey() throws Exception {
  26. return rsaService.getPublicKey();
  27. }
  28. }

登录接口(解密逻辑)

  1. package com.ybw.rsa.demo.controller;
  2. import com.alibaba.fastjson2.JSON;
  3. import com.ybw.rsa.demo.service.RsaService;
  4. import com.ybw.rsa.demo.vo.req.LoginReqVO;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.springframework.stereotype.Controller;
  7. import org.springframework.ui.Model;
  8. import org.springframework.web.bind.annotation.*;
  9. import javax.annotation.Resource;
  10. @Controller
  11. @Slf4j
  12. public class LoginController {
  13. @Resource
  14. private RsaService rsaService;
  15. /**
  16. * @param loginReqVO
  17. * @methodName: login
  18. * @return: java.lang.String
  19. * @author: ybw
  20. * @date: 2022/8/12
  21. **/
  22. @PostMapping(value = "/login")
  23. @ResponseBody
  24. public String login(@RequestBody LoginReqVO loginReqVO) throws Exception {
  25. log.info("loginReqVO:{}", JSON.toJSONString(loginReqVO));
  26. //解密后的密码
  27. String password = rsaService.decryptWithPrivate(loginReqVO.getPassword());
  28. log.info("解密后密码,password:{}", password);
  29. return "OK";
  30. }
  31. }

3、测试验证

密码解密成功

  1. [INFO ] 2022-08-13 23:46:02.239 [http-nio-8081-exec-8] c.y.r.d.service.impl.RsaServiceImpl - RsaServiceImpl getPublicKey publicKeyStr:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJuAH3mGIYUm2CRWifRbRWFef9dKaK3M9WtI0o7ikjQI5NQocEimaqHhoIxYOjb3Z5XIT1ZkI0Roa7L+J8psfVcXeKr3UxT3OnQCdJphC8bRfv9Hzu0sayqtLj7T9t7nptyAfDcME7cRaGiNJoXrMYl8hkFXzDDRbO2tFwymwGkwIDAQAB
  2. [INFO ] 2022-08-13 23:46:02.254 [http-nio-8081-exec-9] c.y.r.d.controller.LoginController - loginReqVO:{"password":"JYZckB2tGtpuJQfqYRLYVGd/jHHPtPtWmEOf+BAtLTydyhvoQoNbejVE5ufoeV1FrQzOgTfx0aUCH3sBrm/xcKP2QWHxzp68tqD9n4ZpWkSP+D8tJmTQgIPHkCOtYekpqVa3/QKKI0c8fU7ADu4FrMRbQLadmIYdi1wsrfIqvK8=","username":"admin"}
  3. [INFO ] 2022-08-13 23:46:02.726 [http-nio-8081-exec-9] c.y.r.d.service.impl.RsaServiceImpl - RsaServiceImpl getPrivateKey privateKeyStr:MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMm4AfeYYhhSbYJFaJ9FtFYV5/10porcz1a0jSjuKSNAjk1ChwSKZqoeGgjFg6NvdnlchPVmQjRGhrsv4nymx9Vxd4qvdTFPc6dAJ0mmELxtF+/0fO7SxrKq0uPtP23uem3IB8NwwTtxFoaI0mhesxiXyGQVfMMNFs7a0XDKbAaTAgMBAAECgYATN3Jd3Kh2Wgk7VdK9CZjqcop353r405xKTZz8/ziatnWtVTR48ZkM2nKZz1HWagdGpye2G8McKR6AtUka8uXVJj+8z3eeGaS+TMlzPbdOK+COp9OZex5HZHTsBqsCGUZ+BJjWMgc8+2Y60e8F53jaCAyVvd8TGNZmjBO7wKccQQJBAPFodMkO7wlDeJJdQvSqXsBvs0TQn2AdTzqj5VUNTy1LVf7b+YOR2aSYjonp1RGi4P2LDONL1gJWoWXmHK0YrnECQQDV6WaSwvjvY8Rxmx3hbxA1/WTFqv/RCOf8n0UxQuZ1YoNjv1enlX6cqMuDfmHiiC/pWf9D7Qnph+sX1Eqc+c9DAkEApSL+WJc1nxGfhgf0CGgO/vaqHBXWICqMiyGYfFDpa6OQRRH3IjCAQF73ipIBZdoUrHwVKdszn0/hglIiJaqvkQJAVfJ9gCJOmwDfATZt/xH81XSGdNWMC5UkgOANkQlsR2XZnM5YjcEHKjK38pFpCvflKEE8yzIGdYpi7yQhBolouQJBAIQaLXw68AYtohzJvxFtjyer+sgzN2JYYHKaGQtT0WGxTnuMrJuQteFxlFGvLOJl+EcfPetdzu5uF7MnZfz+MhU=
  4. [INFO ] 2022-08-13 23:46:02.730 [http-nio-8081-exec-9] c.y.r.d.controller.LoginController - 解密后密码,password:123456

4、代码demo

share: 分享仓库 - Gitee.com

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/948474
推荐阅读
相关标签
  

闽ICP备14008679号