一、加密与验签介绍
大多数公共网络是不安全的,一切基于HTTP协议的请求/响应(Request or Response)都是可以被截获的、篡改、重放(重发)的。因此我们需要考虑以下几点内容:
- 防伪装攻击(案例:在公共网络环境中,第三方 有意或恶意 的调用我们的接口)
- 防篡改攻击(案例:在公共网络环境中,请求头/查询字符串/内容 在传输过程被修改)
- 防重放攻击(案例:在公共网络环境中,请求被截获,稍后被重放或多次重放)
- 防数据信息泄漏(案例:截获用户登录请求,截获到账号、密码等)
区分签名与加密的概念
签名:目的是为了验证身份,检查篡改,防止重放,防止中间人假装自己对外发送假信息,不需要对数据进行加密处理,因此用私钥进行签名,用公钥进行解密验证。也有一些不可逆算法,两边生成签名之后比对结果
加密:目的是将明文变成密文进行传输,防止消息在传输中被别人破解,因此使用公钥加密,使用私钥解密
总结:公钥加密、私钥解密、私钥签名、公钥验签
加密算法我们整体可以分为:可逆加密和不可逆加密,可逆加密又可以分为:对称加密和非对称加密,不可逆算法不用于数据加密,常用于签名算法;可逆算法常用于消息加密算法
比较推荐的几个加密算法有:
- 不可逆加密:
SHA256
、SHA384
、SHA512
以及HMAC-SHA256
、HMAC-SHA384
、HMAC-SHA512
- 对称加密算法:
AES
、3DES
- 非对称加密算法:
RSA
可逆与不可逆算法,对称加密与非对称加密算法总结_柠檬不萌的技术博客_51CTO博客
面试官:说一下你常用的加密算法-阿里云开发者社区
二、签名算法也称摘要算法
- 应用场景:检查报文正确性、验证身份,防止重放(可通过加时间戳的方式)
- 方案:从报文文本中生成报文摘要
- 常用:SHA或者MD5作为签名算法
- 缺点:签名算法不是加密算法,不能用来加密
三、消息加密算法
一. 数字加密算法
数字加密算法中,通常可划分为对称加密和非对称加密。
1. 对称加密
概念:加密和解密使用同一个密钥,所以叫做对称加密。
常见的对称加密算法:DES,AES。
2. 非对称加密
概念:加密和解密使用两个的密钥,一把作为公开的公钥(Public Key),另一把作为私钥(Private Key)。因为加密和解密使用的不是同一把密钥,所以这种算法称为非对称加密算法。
公钥和私钥成对存在,通常【公钥加密,私钥解密】;
公钥是基于私钥而存在的。通过私钥经过一系列算法是可以推导出公钥,但无法通过公钥反向推倒出私钥。
常见的非对称加密算法:RSA,ECC。
3. 对称加密和非对称加密的区别
对称加密:加解密效率高。但在非安全信道中通讯时,密钥交换的安全性不能保障。
非对称加密:加解密效率低,但可以保证密钥安全性。
通常在实际的网络环境中,会将两者混合使用。两者混合使用的示例如下:
四、接口验签实操
1. 实操说明
接口加密与验签的方法有非常多,比如RSA(后期进行讲解),基于token等方式,而对于普通项目,我认为最重要的是防伪装攻击、防篡改攻击、防重放攻击。因为接下来的实操,主要围绕以下几点进行。
2. 验签实操--HMAC-摘要加密算法-不可逆-需密钥
入参sign字段-接口调用方按相同算法进行计算得出摘要
提供方通过如下方式计算得出摘要,比如入参摘要和计算得出的摘要比对一致验签通过。
验签通过的前提下,校验入参传入ts,时间戳若时间差超过5s则不通过。
/**
* 摘要验证
* @param vo 带摘要的请求vo
* @return
*/
protected void verifyDigest(BaseDigestReqVO vo) {
Assert.notBlank(vo.getSign(), "[接口回调] 入参有误,sign不能为空");
Assert.notNull(vo.getTs(), "[接口回调] 入参有误,ts不能为空");
// 获取HMAC对象,该对象本身不保证线程安全,每次都需要进行实例化
//利用hutool工具类cn.hutool.crypto.SecureUtil
//getKey()方法为获取密钥String字符串的方法,也可以写死,调用方和提供方使用的密钥相同:cn.hutool.crypto.KeyUtil生成密钥的工具类
HMac hMac = SecureUtil.hmacSha256(getKey());
// sign本身不参与签名
String beforeStr = Arrays.stream(ReflectUtil.getFields(vo.getClass(), field -> ObjectUtil.notEqual(field.getName(), "sign")))
// 按照字段名字典排序
.sorted(Comparator.comparing(Field::getName))
// 取出所有value
.map(field -> ReflectUtil.getFieldValue(vo, field).toString())
// 用&进行拼接
.collect(Collectors.joining("&"));
byte[] digest = hMac.digest(beforeStr);
Assert.isTrue(hMac.verify(digest, HexUtil.decodeHex(vo.getSign())), "[接口回调] 入参有误,签名错误");
//防止重放攻击-判断ts在5秒内误差
Assert.isTrue(verifyTs(vo.getTs()), "[接口回调] 入参有误,ts不在有效的范围内");
}
3.消息加密算法--可逆-生成私钥和公钥并用私钥加密原文实操举例
//生成密钥对的方式也可以使用hutool中的工具类cn.hutool.crypto.KeyUtil生成密钥的工具类,代码会更简洁
public class RsaDemo {
public static void main(String[] args) throws Exception {
String input = "读你千遍";
// 算法
String algorithm = "RSA";
// 生成密钥对
KeyPair keyPair = getPrivatePublicKey(algorithm);
// 使用私钥对原文加密
byte[] bytes = privateKeyEncrypt(input, algorithm, keyPair.getPrivate());
// 使用私钥对原文解密,使用私钥加密必须用公钥解密
privateKeyDecrypt(bytes, algorithm, keyPair.getPublic());
}
/**
* 使用私钥对原文加密
*
* @param input
* @param algorithm
* @param privateKey
*/
private static byte[] privateKeyEncrypt(String input, String algorithm, PrivateKey privateKey) throws Exception {
// 创建加密对象
Cipher cipher = Cipher.getInstance(algorithm);
// 加密初始化:加密模式,想使用私钥进行加密
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
// 使用私钥对原文加密
byte[] bytes = cipher.doFinal(input.getBytes());
System.out.println("加密:\n" + Base64.encode(bytes));
return bytes;
}
/**
* 使用私钥对原文解密
*
* @param input
* @param algorithm
* @param publicKey 使用私钥加密必须用公钥解密
*/
private static void privateKeyDecrypt(byte[] input, String algorithm, PublicKey publicKey) throws Exception {
// 创建加密对象
Cipher cipher = Cipher.getInstance(algorithm);
// 解密初始化:解密模式,想使用公钥进行解密
cipher.init(Cipher.DECRYPT_MODE, publicKey);
// 使用私钥对原文加密
byte[] bytes = cipher.doFinal(input);
System.out.println("解密:\n" + new String(bytes));
}
/**
* 生成密钥对
*
* @param algorithm
* @return
*/
private static KeyPair getPrivatePublicKey(String algorithm) throws Exception {
// 创建密钥对
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
// 生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
byte[] privateKeyEncoded = privateKey.getEncoded();
byte[] publicKeyEncoded = publicKey.getEncoded();
System.out.println("私钥:\n" + Base64.encode(privateKeyEncoded));
System.out.println("公钥:\n" + Base64.encode(publicKeyEncoded));
return keyPair;
}
}
输出结果
私钥:
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
公钥:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjocZhsKdnR0GAmR5MczuBFW9JX3+pf+yuKftc2RyygVXTOx3o5vhHRT3+wiX4uOHeSR2HGUnoLLCPG5vNLINbN/BfMnzZsmXXv9dWh9+dRxRKzamENynMBfLzhIPu2MJuD1XxdLgKC00eCgYMF8Lh1MhMPNFtAA3KWPE1UHt9TYyQVzSvUJbaEEY61Q1fDPXSZD9EJydrhFVOqzsiSIDMxZfgnRZpcpuQMgGRVsag7ZEm1YzLJPzeWKzE8zhIVD5LBLRbuE77/ay3ukfdtvOy2Nm+uGjkXNzj+NE+YPiSeGq7Jhu5TY71C/24ONw6tbpLW4zCUilpTs4iBgfWPTqXQIDAQAB
加密:
a1UuipQu38r5lTtBVA3JnGPjLqHrAbMwXacPjOl3pzKl9Vkv/1D3tjAJw4dKlt/VHD1kPJmlvjQG4op+JODR/MrwyhZCB6bpVG0HMkqCrk8RBiGQYrxAN0H6fuEx2KtoV9HjroU+hIXM1C7SD6Kx5ss26Wb82UXsSmuIx+aGr8794fsamK9SxGTcyKYiPRrKK+PnJHDif1+2FHKrPVmWFJPFR8gUSGXYsyGTyBZarFK1bXgKGBJBF6nZ6ju3uc4g7KWbxQLsj3U2Lzz2m3U+SHEqobtWi0ssoEWdAQJUWfKdLBcvghKEyGbzQyHUfyY7sW4HIbcCYFDEA4buYZqWgw==
解密:
读你千遍
4.数字签名-是非对称密钥加密技术与摘要算法的结合应用。
张三有俩好哥们A、B。由于工作原因,张三和 AB 写邮件的时候为了安全都需要加密。于是张三想到了数字签名:
- 加密采用非对称加密,张三有三把钥匙,两把公钥,送给朋友。一把私钥留给自己。
- A或者B写邮件给张三:A 先用公钥对邮件加密,然后张三收到邮件之后使用私钥解密。
- 张三写邮件给 A 或者 B:
- 张三写完邮件,先用 hash 函数生成邮件的摘要,附着在文章上面,这就完成了数字签名,然后张三再使用私钥加密。就可以把邮件发出去了。
- A 或者是 B 收到邮件之后,先把数字签名取下来,然后使用自己的公钥解密即可。这时候取下来的数字签名中的摘要若和张三的一致,那就认为是张三发来的,再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。
文章来源:https://www.toymoban.com/news/detail-466166.html
文章来源地址https://www.toymoban.com/news/detail-466166.html
到了这里,关于加密和验签的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!