• 密码加密传输


    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.bootgroupId>
    4. <artifactId>spring-boot-starter-webartifactId>
    5. dependency>
    6. <dependency>
    7. <groupId>org.springframework.bootgroupId>
    8. <artifactId>spring-boot-starter-thymeleafartifactId>
    9. dependency>
    10. <dependency>
    11. <groupId>org.projectlombokgroupId>
    12. <artifactId>lombokartifactId>
    13. <optional>trueoptional>
    14. dependency>
    15. <dependency>
    16. <groupId>org.springframework.bootgroupId>
    17. <artifactId>spring-boot-starter-testartifactId>
    18. <scope>testscope>
    19. dependency>
    20. <dependency>
    21. <groupId>org.apache.commonsgroupId>
    22. <artifactId>commons-collections4artifactId>
    23. <version>4.4version>
    24. dependency>
    25. <dependency>
    26. <groupId>org.apache.commonsgroupId>
    27. <artifactId>commons-lang3artifactId>
    28. <version>3.12.0version>
    29. dependency>
    30. <dependency>
    31. <groupId>commons-codecgroupId>
    32. <artifactId>commons-codecartifactId>
    33. <version>1.15version>
    34. dependency>
    35. <dependency>
    36. <groupId>org.bouncycastlegroupId>
    37. <artifactId>bcprov-ext-jdk15onartifactId>
    38. <version>1.70version>
    39. dependency>
    40. <dependency>
    41. <groupId>com.alibaba.fastjson2groupId>
    42. <artifactId>fastjson2artifactId>
    43. <version>2.0.11version>
    44. dependency>
    45. <dependency>
    46. <groupId>org.springframework.bootgroupId>
    47. <artifactId>spring-boot-starter-data-redisartifactId>
    48. dependency>
    49. <dependency>
    50. <groupId>org.apache.commonsgroupId>
    51. <artifactId>commons-pool2artifactId>
    52. dependency>
    53. 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

  • 相关阅读:
    Neo4j:入门基础—APOC插件
    Elasticsearch实战(五)---高级搜索 Match/Match_phrase/Term/Must/should 组合使用
    SpringMVC 学习(六)乱码问题
    c语言--结构体
    冯唐 成事心法
    MySQL数据库基本操作1
    请问嵌入式或迁移学习要学什么?
    QT+OSG/osgEarth编译之二十五:libzip+Qt编译(一套代码、一套框架,跨平台编译,版本:libzip-1.9.2)
    SparkCore、SparkSQL、SparkStreaming三者之间的区别和联系
    [论文阅读] Center-based 3D Object Detection andTracking-CenterPoint
  • 原文地址:https://blog.csdn.net/xixingzhe2/article/details/126325348