• 加密和验签


    一、加密与验签介绍

      大多数公共网络是不安全的,一切基于HTTP协议的请求/响应(Request or Response)都是可以被截获的、篡改、重放(重发)的。因此我们需要考虑以下几点内容:

    1. 防伪装攻击(案例:在公共网络环境中,第三方 有意或恶意 的调用我们的接口)
    2. 防篡改攻击(案例:在公共网络环境中,请求头/查询字符串/内容 在传输过程被修改)
    3. 防重放攻击(案例:在公共网络环境中,请求被截获,稍后被重放或多次重放)
    4. 防数据信息泄漏(案例:截获用户登录请求,截获到账号、密码等)

    ​ 区分签名与加密的概念

       ​   签名:目的是为了验证身份,检查篡改,防止重放,防止中间人假装自己对外发送假信息,不需要对数据进行加密处理,因此用私钥进行签名,用公钥进行解密验证。也有一些不可逆算法,两边生成签名之后比对结果

    ​      加密:目的是将明文变成密文进行传输,防止消息在传输中被别人破解,因此使用公钥加密,使用私钥解密

    总结:公钥加密、私钥解密、私钥签名、公钥验签

    加密算法我们整体可以分为:可逆加密和不可逆加密,可逆加密又可以分为:对称加密和非对称加密,不可逆算法不用于数据加密,常用于签名算法;可逆算法常用于消息加密算法 

    比较推荐的几个加密算法有:

    • 不可逆加密:SHA256SHA384SHA512以及HMAC-SHA256HMAC-SHA384HMAC-SHA512
    • 对称加密算法:AES3DES
    • 非对称加密算法:RSA

    可逆与不可逆算法,对称加密与非对称加密算法总结_柠檬不萌的技术博客_51CTO博客

    面试官:说一下你常用的加密算法-阿里云开发者社区

    二、签名算法也称摘要算法

    • 应用场景:检查报文正确性、验证身份,防止重放(可通过加时间戳的方式)
    • 方案:从报文文本中生成报文摘要
    • 常用:SHA或者MD5作为签名算法
    • 缺点:签名算法不是加密算法,不能用来加密

    三、消息加密算法

    一. 数字加密算法
    数字加密算法中,通常可划分为对称加密和非对称加密。
    1. 对称加密
         概念:加密和解密使用同一个密钥,所以叫做对称加密。
         常见的对称加密算法:DES,AES。

    在这里插入图片描述

    2. 非对称加密
          概念:加密和解密使用两个的密钥,一把作为公开的公钥(Public Key),另一把作为私钥(Private Key)。因为加密和解密使用的不是同一把密钥,所以这种算法称为非对称加密算法。
    公钥和私钥成对存在,通常【公钥加密,私钥解密】;
    公钥是基于私钥而存在的。通过私钥经过一系列算法是可以推导出公钥,但无法通过公钥反向推倒出私钥。
          常见的非对称加密算法:RSA,ECC。

    在这里插入图片描述
    3. 对称加密和非对称加密的区别
    对称加密:加解密效率高。但在非安全信道中通讯时,密钥交换的安全性不能保障。
    非对称加密:加解密效率低,但可以保证密钥安全性。
    通常在实际的网络环境中,会将两者混合使用。两者混合使用的示例如下:

     在这里插入图片描述

    四、接口验签实操

    1. 实操说明

      接口加密与验签的方法有非常多,比如RSA(后期进行讲解),基于token等方式,而对于普通项目,我认为最重要的是防伪装攻击、防篡改攻击、防重放攻击。因为接下来的实操,主要围绕以下几点进行。

    2. 验签实操--HMAC-摘要加密算法-不可逆-需密钥

      入参sign字段-接口调用方按相同算法进行计算得出摘要

      提供方通过如下方式计算得出摘要,比如入参摘要和计算得出的摘要比对一致验签通过。

      验签通过的前提下,校验入参传入ts,时间戳若时间差超过5s则不通过。

    1. /**
    2. * 摘要验证
    3. * @param vo 带摘要的请求vo
    4. * @return
    5. */
    6. protected void verifyDigest(BaseDigestReqVO vo) {
    7. Assert.notBlank(vo.getSign(), "[接口回调] 入参有误,sign不能为空");
    8. Assert.notNull(vo.getTs(), "[接口回调] 入参有误,ts不能为空");
    9. // 获取HMAC对象,该对象本身不保证线程安全,每次都需要进行实例化
    10. //利用hutool工具类cn.hutool.crypto.SecureUtil
    11. //getKey()方法为获取密钥String字符串的方法,也可以写死,调用方和提供方使用的密钥相同:cn.hutool.crypto.KeyUtil生成密钥的工具类
    12. HMac hMac = SecureUtil.hmacSha256(getKey());
    13. // sign本身不参与签名
    14. String beforeStr = Arrays.stream(ReflectUtil.getFields(vo.getClass(), field -> ObjectUtil.notEqual(field.getName(), "sign")))
    15. // 按照字段名字典排序
    16. .sorted(Comparator.comparing(Field::getName))
    17. // 取出所有value
    18. .map(field -> ReflectUtil.getFieldValue(vo, field).toString())
    19. // 用&进行拼接
    20. .collect(Collectors.joining("&"));
    21. byte[] digest = hMac.digest(beforeStr);
    22. Assert.isTrue(hMac.verify(digest, HexUtil.decodeHex(vo.getSign())), "[接口回调] 入参有误,签名错误");
    23. //防止重放攻击-判断ts在5秒内误差
    24. Assert.isTrue(verifyTs(vo.getTs()), "[接口回调] 入参有误,ts不在有效的范围内");
    25. }

    3.消息加密算法--可逆-生成私钥和公钥并用私钥加密原文实操举例

    //生成密钥对的方式也可以使用hutool中的工具类cn.hutool.crypto.KeyUtil生成密钥的工具类,代码会更简洁

    1. public class RsaDemo {
    2. public static void main(String[] args) throws Exception {
    3. String input = "读你千遍";
    4. // 算法
    5. String algorithm = "RSA";
    6. // 生成密钥对
    7. KeyPair keyPair = getPrivatePublicKey(algorithm);
    8. // 使用私钥对原文加密
    9. byte[] bytes = privateKeyEncrypt(input, algorithm, keyPair.getPrivate());
    10. // 使用私钥对原文解密,使用私钥加密必须用公钥解密
    11. privateKeyDecrypt(bytes, algorithm, keyPair.getPublic());
    12. }
    13. /**
    14. * 使用私钥对原文加密
    15. *
    16. * @param input
    17. * @param algorithm
    18. * @param privateKey
    19. */
    20. private static byte[] privateKeyEncrypt(String input, String algorithm, PrivateKey privateKey) throws Exception {
    21. // 创建加密对象
    22. Cipher cipher = Cipher.getInstance(algorithm);
    23. // 加密初始化:加密模式,想使用私钥进行加密
    24. cipher.init(Cipher.ENCRYPT_MODE, privateKey);
    25. // 使用私钥对原文加密
    26. byte[] bytes = cipher.doFinal(input.getBytes());
    27. System.out.println("加密:\n" + Base64.encode(bytes));
    28. return bytes;
    29. }
    30. /**
    31. * 使用私钥对原文解密
    32. *
    33. * @param input
    34. * @param algorithm
    35. * @param publicKey 使用私钥加密必须用公钥解密
    36. */
    37. private static void privateKeyDecrypt(byte[] input, String algorithm, PublicKey publicKey) throws Exception {
    38. // 创建加密对象
    39. Cipher cipher = Cipher.getInstance(algorithm);
    40. // 解密初始化:解密模式,想使用公钥进行解密
    41. cipher.init(Cipher.DECRYPT_MODE, publicKey);
    42. // 使用私钥对原文加密
    43. byte[] bytes = cipher.doFinal(input);
    44. System.out.println("解密:\n" + new String(bytes));
    45. }
    46. /**
    47. * 生成密钥对
    48. *
    49. * @param algorithm
    50. * @return
    51. */
    52. private static KeyPair getPrivatePublicKey(String algorithm) throws Exception {
    53. // 创建密钥对
    54. KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
    55. // 生成密钥对
    56. KeyPair keyPair = keyPairGenerator.generateKeyPair();
    57. PrivateKey privateKey = keyPair.getPrivate();
    58. PublicKey publicKey = keyPair.getPublic();
    59. byte[] privateKeyEncoded = privateKey.getEncoded();
    60. byte[] publicKeyEncoded = publicKey.getEncoded();
    61. System.out.println("私钥:\n" + Base64.encode(privateKeyEncoded));
    62. System.out.println("公钥:\n" + Base64.encode(publicKeyEncoded));
    63. return keyPair;
    64. }
    65. }

    输出结果

    1. 私钥:
    2. MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCOhxmGwp2dHQYCZHkxzO4EVb0lff6l/7K4p+1zZHLKBVdM7Hejm+EdFPf7CJfi44d5JHYcZSegssI8bm80sg1s38F8yfNmyZde/11aH351HFErNqYQ3KcwF8vOEg+7Ywm4PVfF0uAoLTR4KBgwXwuHUyEw80W0ADcpY8TVQe31NjJBXNK9QltoQRjrVDV8M9dJkP0QnJ2uEVU6rOyJIgMzFl+CdFmlym5AyAZFWxqDtkSbVjMsk/N5YrMTzOEhUPksEtFu4Tvv9rLe6R92287LY2b64aORc3OP40T5g+JJ4arsmG7lNjvUL/bg43Dq1uktbjMJSKWlOziIGB9Y9OpdAgMBAAECggEAX2jCxro6xjTqk+lsU9EKOC6o+pMYR0ke+4u2YXuJeAERSrY09HnhvRUPV6WN/10ukAK/agyw9VsUuV135f+XCx3SKVYLMPCb+wTZGf5hKcY188aWbk03QfN8A8uFanxGwmWV/yBF278/E3xLjP3i+sjEPD9tlqxaQMO/vL5wq6eD06RENDhmV3Di3sPPk0LMJPg4gtrWvM+TNGzHvsliADC2yHYuTSbAnTvetQJvVUizMJi2YALyFSOBnPeEiEsecTXBFwpHcU+GK60IIGanW6U3xSC7BjZu/vEpWiewVfFWSIj6ELZeCgXYpINrU6ABIheNr34oVrynONjwEcFmAQKBgQDhIY6EvOpV9iGbY00DnNgT+0TRQx2QGDIcM/KMcuwptKgGsWMBPvoRJnwQvgLLmgCPX7SQrAUk+/QvhTgXQgNoeJMb2slB17rKAdM3/qKTCBN+oZ8WbC9HrcWB5AuNWQLSaRmqIN47CdI2rN24GfpYOw5urIwINdDr7CIvdTHdgQKBgQCiEgo17wtlEdJnhFAZVC9CuIPjtQ/Ys8an9gKQDiymCPHqVgrkkzPhTSGE9fg0chkLjvi4hrNVwGHjRTyXUQFyB2o+zuZJYBQLw2i7P001hHZz/y76mTL8Oue1QhqbvJ4MpBtrV8yMBKrRxWt/BF4lY+DN2aRezkAawWMmnWWy3QKBgDqLsndcdYhDSLwTF80PtbWEi4Hr9T0qvaVN8Q6LOFUkMOoEqV2Clh9tpafo8esmsmyWk+tngLL8fqT4/Pw3Y5GAgaklvV7NDjtIPDh8lKSt3fv5Zdi765O1Yf6EYmiwtCYpxM3UXFZ4GF21mLcuskbNnNQ1NPlRnXIs5zr2PDCBAoGBAIPj9GVBvF8qqrRNK7YBGVjNuZ8UGOo8Gt1SyXEb59/ShbQzAzhSRrUBxNZkKPHdLF6IogXgsI6HOeHu1Uk6xddbC25Dh4qM4qNUCaXf9OAphRAOyddr1t8GvMt4GRlF3MTiw+GOGMqVfcGqTDmYf1kkN6ytgiMY63gaiqrBCiMNAoGBAJzoOmipXLXrpH6WS/8NN2cHOIpfK/Nco84XUpTQaihkW3nGsypVRCLmtJgsa55Efa0/T9wVqgPfYf++IHVBidvH4+KrPBetqWF0Q8km0K00DV9OXUnhRDabM4+jlaxnf4VaxWTJDNLf2cAsHwzlVMZkwT/4EsxwRggjUZTMsn6N
    3. 公钥:
    4. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjocZhsKdnR0GAmR5MczuBFW9JX3+pf+yuKftc2RyygVXTOx3o5vhHRT3+wiX4uOHeSR2HGUnoLLCPG5vNLINbN/BfMnzZsmXXv9dWh9+dRxRKzamENynMBfLzhIPu2MJuD1XxdLgKC00eCgYMF8Lh1MhMPNFtAA3KWPE1UHt9TYyQVzSvUJbaEEY61Q1fDPXSZD9EJydrhFVOqzsiSIDMxZfgnRZpcpuQMgGRVsag7ZEm1YzLJPzeWKzE8zhIVD5LBLRbuE77/ay3ukfdtvOy2Nm+uGjkXNzj+NE+YPiSeGq7Jhu5TY71C/24ONw6tbpLW4zCUilpTs4iBgfWPTqXQIDAQAB
    5. 加密:
    6. a1UuipQu38r5lTtBVA3JnGPjLqHrAbMwXacPjOl3pzKl9Vkv/1D3tjAJw4dKlt/VHD1kPJmlvjQG4op+JODR/MrwyhZCB6bpVG0HMkqCrk8RBiGQYrxAN0H6fuEx2KtoV9HjroU+hIXM1C7SD6Kx5ss26Wb82UXsSmuIx+aGr8794fsamK9SxGTcyKYiPRrKK+PnJHDif1+2FHKrPVmWFJPFR8gUSGXYsyGTyBZarFK1bXgKGBJBF6nZ6ju3uc4g7KWbxQLsj3U2Lzz2m3U+SHEqobtWi0ssoEWdAQJUWfKdLBcvghKEyGbzQyHUfyY7sW4HIbcCYFDEA4buYZqWgw==
    7. 解密:
    8. 读你千遍

    4.数字签名-是非对称密钥加密技术与摘要算法的结合应用。

    张三有俩好哥们A、B。由于工作原因,张三和 AB 写邮件的时候为了安全都需要加密。于是张三想到了数字签名:

    1. 加密采用非对称加密,张三有三把钥匙,两把公钥,送给朋友。一把私钥留给自己。
    2. A或者B写邮件给张三:A 先用公钥对邮件加密,然后张三收到邮件之后使用私钥解密。
    3. 张三写邮件给 A 或者 B:
      • 张三写完邮件,先用 hash 函数生成邮件的摘要,附着在文章上面,这就完成了数字签名,然后张三再使用私钥加密。就可以把邮件发出去了。
      • A 或者是 B 收到邮件之后,先把数字签名取下来,然后使用自己的公钥解密即可。这时候取下来的数字签名中的摘要若和张三的一致,那就认为是张三发来的,再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。

  • 相关阅读:
    随手记面试录
    nodejs篇 内置模块path 常用api
    Mongodb 安装脚本(附服务器自启动)
    蒂姆·库克喜提《时代》杂志2022百大影响力人物封面,谷爱凌、杨紫琼也入选
    Greenplum广播与重分布原理
    Golioth 发布基于乐鑫 ESP-IDF 的开源 SDK
    设计模式之解释器模式
    flink的计时器
    离职原因千万不要这样说!
    字符函数和字符串函数(1)
  • 原文地址:https://blog.csdn.net/LyySwx/article/details/125663530