一、单向散列函数
1.1 概念及术语
单向散列函数(one-way hash function)有一个输入和一个输出,其中输入称为消息(message),输出称为散列值(hash value)。单向散列函数可以根据消息的内容计算出散列值,而散列值就可以被用来检查消息的完整性。这里的消息可以是文档图像视频音频等,散列函数会将它们都当成单纯的bit序列来处理。这样,要确认完整性就不需要对比消息本身,对比小的散列值即可。
注意:单向散列函数并不是一种加密,因此无法通过解密将散列值还原为原来的消息。
术语:单向散列函数也称为消息摘要函数(message digest function)、哈希函数或者杂凑函数。输入的消息也称为原像(pre-image),输出的散列值也成为消息摘要(message digest)或者指纹(fingerprint),完整性也称为一致性。
1.2 使用场景及常见函数
单向散列函数的使用场景
- 检测软件是否被篡改:有些软件会将计算出的散列值公布在自己的官方网站上,用户下载软件后,可以自行计算散列值,然后与官网上的散列值对比来判断是否被篡改。当软件作者通过多个站点(镜像站点)来发布软件时,单向散列函数就会在检测是否被篡改方面发挥重要作用。
- 被用于基于口令的加密(Password Based Encryption,PBE):PBE原理是将口令和盐(salt,通过伪随机数生成器产生的随机值)混合后计算其散列值,然后将这个散列值用作加密的密钥。通过这样的方法能够防御针对口令的字典攻击。
- 构造消息认证码:消息认证码是将“发送者和接收者之间的共享密钥”和“消息”进行混合后计算出的散列值。使用消息认证码可以检测并防止通信过程中的错误、篡改以及伪装。消息认证码在SSL/TLS中也得到了运用。
- 数字签名:数字签名的处理过程非常耗时,因此一般不会对整个消息内容直接施加数字签名,而是先通过单向散列函数计算出消息的散列值,然后再对这个散列值加数字签名。
- 伪随机数生成器:密码技术中所使用的随机数需要具备“事实上不可能根据过去的随机数列预测未来的随机数列”这样的性质。为了保证不可预测性,可以利用单向散列函数的单向性。
- 一次性口令(one-time password):一次性口令经常被用于服务器对客户端的合法性认证。在这种方式中,通过单向散列函数可以保证口令只在通信链路上传送一次,因此即使窃听者窃取了口令也无法使用。
- 存储密码:加盐操作配合单向散列函数可以存储用户密码,满足普通项目需求。
常见的单向散列函数
**MD5:**MD是消息摘要 MessageDigest 的缩写,MD5是由Rivest于1991年设计的单向散列函数,能够产生128bit的散列值。MD5的强抗碰撞性已经被攻破,也就是说,现在已经能够产生具备相同散列值的两条不同的消息,因此它已经不安全了。
**SHA-1:**是由NIST(National Institute of Standards and Technology,美国国家标准技术研究所)设计的一种能够产生160bit散列值的单向散列函数。SHA-1处理的消息长度存在上限,这个值接近于264bit。该函数的强抗碰撞性已于2005年被攻破。
**SHA-256、SHA-384、SHA-512:**NIST设计的单向散列函数,它们的散列值长度分别为256bit、384bit、512bit。这些函数合起来统称 **SHA-2 **,它们处理的消息长度上限为:SHA-256上限接近于264bit,SHA-384和SHA-512上限接近2128bit。目前 SHA-2 的强抗碰撞性还未被攻破。
单向散列函数无法辨别“伪装”
假设主动攻击者M伪装成了A,向B同时发送了消息和散列值,这时B能够用函数检查消息完整性,但是这只是对M发出的消息进行检查,没办法试别出是其他人伪装成了A。
也就是说单向散列函数无法直接用于认证。需要结合消息认证码和数字签名技术,保证消息完整性和进行身份认证。
二、对称密码
根据密钥的使用方法,可以将密码分为对称密码和公钥密码两种。
对称密码是指在加密和解密时使用同一密钥的方式。
非对称密码又叫公钥密码,指在加密和解密时使用不同密钥的方式。
用相同的密钥进行加密和解密就是对称加密。
常见对称加密算法:
DES(Data Encryption Standard)
DES是1977年美国联邦信息处理标准(FIPS)中所采用的一种对称密码,一直以来被美国以及其他国家的政府和银行等广泛使用。然而随着计算机的进步,现在DES已经能够被暴力破解,强度大不如前了。
DES是一种将64bit的明文加密成64bit的密文的对称密码算法,它的密钥长度是56bit。尽管从规格上来说,DES的密钥长度是64bit,但由于每隔7bit会设置一个用于错误检查的bit,因此实质上长度是56bit。
DES是以64bit的明文(bit序列)为单位来进行加密的,这个64bit的单位称为分组。一般来说,以分组为单位进行处理的密码算法称为分组密码(block cipher),DES就是分组密码的一种。
DES每次只能加密64bit的数据,如果要加密的明文比较长,就需要对DES加密进行迭代(反复),而迭代的具体方式就称为模式(mode)。
三重DES
三重DES(triple-DES)是为了增加DES的强度,将DES重复3次得到的一种密码算法,通常缩写为3DES。
明文经过三次DES处理才能变成最后的密文,由于DES密钥的长度实质上是56bit,因此三重DES的密钥长度就是56*3=168bit。加密中加入解密,这个方法是IBM公司设计的,目的是为了3DES能够兼容普通的DES,当三个密钥都相同时,就可以向下兼容DES了。
尽管3DES目前还被银行等机构使用,但其处理速度不高,而且在安全性方面也逐渐显现出了一些问题。
对称密码的新标准——AES
AES(Advanced Encryption Standard)是取代旧标准DES而成为新标准的一种对称密码算法。全世界的企业和密码学家提交了多个对称密码算法作为AES的候选,最终在2000年从这些候选算法中选出了一种名为Rijndael的对称密码算法,并将其确定为了AES。
应该使用哪种对称密码?
现在DES由于安全性问题最好是不要使用了。出于兼容性因素3DES还会使用一段时间,但是会逐渐被AES所取代。
一般来说现在不应该使用使用任何自制的算法,而是应该使用AES,因为AES在其选定过程中,经过了全世界密码学家所进行的高品质的检验工作,而对于自制密码算法则很难进行这样的验证。
三、非对称密码
在对称密码中,加密密钥和解密密钥是相同的,但非对称密码,也叫公钥密码中,加密密钥和解密密钥却是不同的。
公钥密码(public-key cryptography)中,密钥分为加密密钥和解密密钥两种。发送者用加密密钥(公钥)对消息进行加密
,接收者用解密密钥(私钥)对密文进行解密。常见公钥密码有:RSA、EIGamal方式、Rabin方式、椭圆曲线密码。
不难发现,公钥和私钥有如下特点:
- 发送者只需要公钥
- 接收者只需要私钥
- 私钥由接收者持有,不能被窃听者获取
- 公钥被窃听者获取也没问题
得出结论:公钥密码解决了使用对称密码时的密钥配送问题。
公钥加解密流程图示如下:
3.1 公钥密码无法解决的问题
公钥密码解决了密钥配送问题,但这并不意味着它能够解决所有问题,因为我们需要判断所得到的公钥是否正确合法,这个问题被称为公钥认证问题;
还有个问题是,它的处理速度很慢,只有对称密码的几百分之一。
四、混合密码——用对称密码提高速度,用公钥密码保护会话密钥
通过使用对称密码,能够在通信中确保机密性。然而在实际中运用时,就必须解决密钥配送问题。
而使用公钥密码就可以避免解密密钥(私钥)配送,从而也就解决了对称密码所存在的密钥配送问题。但是,公钥密码还有两个很大的问题:
- 公钥密码处理速度远远低于对称密码
- 公钥密码难以抵御中间人攻击
混合密码系统可以解决问题1,而要解决问题2,则需要对公钥进行认证。
混合密码系统(hybrid cryptosystem)是将对称密码和公钥密码的优势相结合的方法。具体加解密流程如下:
4.1 加密
会话密钥(session key)是指为本次通信而生成的的临时密钥,它一般是通过伪随机数生成器产生的。会话密钥也会作为对称密码的密钥使用。
简言之,会话密钥是对称密码的密钥,同时也是公钥密码的明文。
4.2 解密
分离:双方要事先约定好密文结构,这样对密文的分离操作就很容易完成。
著名的密码软件PGP、以及网络上的密码通信所使用的SSL/TLS都运用了混合密码系统。
五、数字签名——消息到底是谁写的
数字签名是根据消息内容生成的一串“只有自己才能计算出来的数值”,因此数字签名的内容是随消息的改变而改变的。
数字签名跟公钥密码有着非常紧密的联系。简言之,数字签名就是通过将公钥密码“反过来用”而实现的。如下图所示:
下面再来对比一下公钥密码和私钥密码:
公钥密码包括一个由公钥和私钥组成的密钥对,其中公钥加密,私钥解密。如图:
数字签名中也同样会使用公钥密码和私钥密码组成的密钥对,不过这两个密钥的用法和公钥密码是相反的,即用私钥加密相当于生成签名,
而用公钥解密则相当于验证签名(严格来说,RSA算法中公钥加密和数字签名正好是完全相反的关系,但在其他算法中有可能不是这样完全相反的关系)。
公钥密码中,任何人都能够进行加密;
数字签名中,任何人都能够验证签名。
5.1 数字签名的方法
一般有两种生成和验证数字签名的方法:
- 直接对消息签名的方法
- 对消息的散列值签名的方法
直接对消息签名的方法比较容易理解,但实际上并不会使用;对消息的散列值签名的方法稍微复杂一点,但实际中一般都使用这种方法。
直接对消息签名很简单,就是用私钥对消息签名就行,接收者接收到消息和签名后,用对应公钥解签,再对比消息,一致则验签成功,否则失败。
5.1.1 对消息的散列值签名
上面说过,公钥密码算法是很慢的,那么对长消息加密也很慢。那么,能不能生成一条很短的数据来代替消息本身呢?
回想一下,上面说过的单向散列函数就非常契合。长消息的散列值永远都很短,对其加签是很轻松的。
对散列值签名的流程图如下:
5.2 数字签名无法解决的问题
数字签名既可以识别出篡改和伪装,还可以防止否认。也就是说,同时实现了确认消息的完整性、进行认证以及否认防止。
然而要正确使用数字签名,有一个大前提,那就是用于验证签名的**公钥必须属于真正的发送者。**即便数字签名算法再强大,如果你得到的公钥是伪造的,那么数字签名也会完全失效。
为了能够确认自己得到的公钥是否合法,我们需要使用**证书。**所谓证书,就是将公钥当作一条消息,由一个可信的第三方对其签名后所得到的公钥。
六、证书——为公钥加上数字签名
公钥证书(Public-Key Certificate,PKC)其实和驾照很相似,里面记有姓名、组织、邮箱地址等个人信息,以及属于此人的公钥,并由认证机构(Certification Authority、Certifying Authority,CA)施加数字签名。只要看到公钥证书,我们就可以知道认证机构认定该公钥的确属于此人。公钥证书也简称为证书(certificate)。
认证机构就是能够认定“公钥确实属于此人”并能够生成数字签名的个人或者组织。世界上最有名的认证机构当属VeriSign(verisign.com)公司。
6.1 证书应用场景
认证机构必须是可信的,对于“可信的第三方”,这里用 Trent 这个名字代表,那么场景示例如下图所示
七、RSA
7.1 RSA加密
RSA是现在使用最广泛的公钥密码算法 。它的名字是由它的三位开发者,即Ron Rivest、Adi Shamir 和 Leonard Adleman 的姓氏的首字母组成。
RSA可以被用于公钥密码和数字签名。1983年,RSA公司为RSA算法在美国取得了专利,但现在专利已过期。
RSA加密过程:
在RSA中,明文、密钥和密文都是数字。
RSA的加密解密公式如下:
7.2 RSA数字签名
RSA的签名生成和验证公式如下:
7.3 对RSA的攻击
RSA的加密是求“E次方的 mod N”,解密是求“D次方的 mod N”,原理很简单,但它是否容易被破译呢?
通过密文直接破译原文的方法有如下几种(详细说明见参考书目对应章节):
- 通过密文求得明文
- 暴力破解找出 D
- 通过 E 和 N 求出 D
- 对 N 进行质因数分解攻击
- 通过推测 p 和 q 进行攻击
- 其他攻击。只要对 N 进行质因数分解并求出 p 和 q,就能够求出 D
除了直接对密文破译以外,还有一种攻击方式,叫**中间人攻击(main-in-the-middle attack)。**这种方法虽然不能破译 RSA,但却是一种针对机密性的有效攻击。中间人攻击方式如下图:
上图的过程可以被重复多次,Bob 向 Alice 发送加密邮件时也可能受到同样的攻击,因此 Bob 即便要发邮件给 Alice 以询问她真正的想法,也会被 Mallory 随意篡改。
这种攻击不仅针对 RSA,而是可以针对任何公钥密码。在这个过程中,公钥密码并没有被破译,保证了信息的机密性。然而,所谓的机密性并非在 Alice 和 Bob 之间,而是在 Alice 和 Mallory 之间,以及 Mallory 和 Bob 之间成立的。仅靠公钥密码本身,是无法防御中间人攻击的。
要防御中间人攻击,还需要一种手段来确认所收到的公钥是否真的属于 Bob,这种手段称为认证,这里就用到了证书。
7.4 整个加密签名,解密验签过程
根据个人理解,整合上述的所有流程,在不考虑中间人攻击的情况下,在开发中的使用流程如下:
接收者Bob收到消息后的处理流程如下:
7.4.1 代码模拟
根据上面流程的代码实现如下:
依赖
Hutool、JDK8、Spring Boot
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.5</version>
</dependency>
明文DP(消息发送方进行加密签名):
package com.jiangxb.rsa.dp;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
/**
* 明文DP
*
* @author: jiangxiangbo
* @date: 2021/9/1
*/
@Data
@Slf4j
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PlainText {
private final static String ENCRYPT_DATA_SEPARATOR = "----SEPARATE----";
private final static String publicKey_B = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv8GjRufWGPI7Xe6caZ5h5PbnRIQVzD4P1gDjKZaibcxcApGEaqFkT3Am2U6iKv6paELuwxy+dUL1Jvbs09QljuHgDB9SV0VxSM5LscpCmWJ5P1V6Y/QiholCQHCFR6ok6oE2HWGRw/bPQWr/gHfa2zNPu+CB64cbOxLHIQYIRji47tyywAL5ABhF1msZY2vW8xaFKHGq74sxNpf8s0NUnRnVRANjHtuDa/zvrHim45gqBWg+3gPVSQyPU3ydMoj0AiORJQmqprHaZDB7BufpTEZA6I2WElsKJcsGMdwfSd1s0B1iCzrkMmT30n/XXxyw8qQGsvJvQ2V90QiAV9bV+wIDAQAB";
private final static String privateKey_A = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCyd6fTUJ1BzbOH8eKEI01gjq7sAVd/82tn/1q4+8WwlwrVxaPkQUFTeWu2zY5O+uZhW6uEAGDaD9hruCXNS3oPBSiMc5a2nAXFIfW/sVQky/EOxt3gZcpF1aoySqRwIxBgOkamyFsvcgBZgjDqmCcFwqSxvOQOtkJkwDIdrVsHfOHm4Sb2ZytGUFQaKts81W377fO0h91zhWnmFvD751o0wWv9UYOiExWDcOWThmS0OAdzmdqReQTyFKvpQNPDjkml9lnR0/1dOL8JJVB2hgP52wkhny6V62VusStF9kQk3H+TTDzw/iR8a140EaGXPNWjjm0yQdPfc7xAHonEjjFVAgMBAAECggEAec/qIPXZIF0CuTuEXKSr38gD5NpVmuPO38EPb0uJ96pgnuCzqMxRhmRN/Qv4ojfmn3UucH7BnJVMJtoeEy39NdtTfeo3aJS963vufNTQlf0NoARk1RElKt1XudPwwQlt2ABu0M/YTV4GlxGhyb3ohKoCN76x+si0MIhurIryovyabZCtlhGD2fg3V1t8RBlCEuz68FtB9fSh4zk7u6RhAL5LCOGNbVAiY4hx/NhrDiBfvQBhJZmPG+3gWjjZFgZEH5B0tGByuG2M+dj2qT5LFepkhGyI/upJwOhJrjiRrvR7LmSYCz0lI8/2fVCF8jN/TJav/1xVR82d/165Movm0QKBgQDZv/H1bhPoCoeh8z3ww8Um5FjFb1MMjmh4oB1d2+0QlYTbSVx6mOxBoh0yk+jKztotJyWs61nnHekOhdHFx9Ij1L8oMxybBK+heTuzl2WIs9/2CRBV4XfKMwNiYxJYkaXxUgeHx/2IVXTuFMKMrWhf7kxk2iFrK+Gv63oY0dmvhwKBgQDR0TWXRXC2qEqH/NV/6d24UHl4i/+UP1aKE9jA8xArJYBlKtTWCgM7g3/wxr0IRB6RocVupop/kZJ9RUFjprfaykDOj+A0oC+IDwUmGIjGbR4P921qjWEVQGIFSJvnIwHwGfEAPxvw0uW2tqz9C2GUZ9OB17lecfIdeJQX2Hb3QwKBgA9bWCclAkZlJ7emPgIS7H6XsCMMfODv0jJfqHKMJiX7RYlpnRoQWukuE70TbWGQQRbaIfAWERsZouwhR/AY7ZsVT/33zNap9/D9adZ6oPCJLwxdC0fjRN1/x4dS0WJpszhXvqw20Iyi6kI4OJhPSoMpfT3HnH/AcoRDqTLC6gVVAoGBAI3f9GfseZHZbERV75wF7HoEWI7tw41f4smNMAUQln9GZXKDKtXsgVEN00ZhbFMZlL4O8GyoyoAGVFLGsLeMdUfJeVbzrLyJEHrlBStEbcAW6rwLJ/5jySDQnzdJaLo7TsUnFXKAOgl24gPRtFmLB5mNN1TWJS86x2esMB+LrK33AoGADEDHIUtulc4zclLH9MJj7JcZPkgVz5llJ1jQj3fOu4iPc9TNvV2gV2kWU446gyMmRrQ2We1awnrjaeSzeFnf0OhL+yTzNUmRLMYWZhja/KMhr7b9vVRCCrysZJod+MWodEH+HIJlu9RGIxv7fNNy1S4yRU92OQU43XQ1S93eaEE=";
/**
* 消息明文json
*/
private String msg;
/**
* 加密签名
*/
public Map<String, Object> encrypt() {
assert StringUtils.isNotBlank(msg);
Map<String, Object> map = Maps.newHashMap();
try {
// 一、 AES密钥加密
// 1. 随机生成AES密钥
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
// 2. 明文加密
String encryptMsg = aes.encryptBase64(msg);
// 3. 用B公钥加密AES密钥
RSA rsa = new RSA(null, publicKey_B);
String encryptKey = rsa.encryptBase64(key, KeyType.PublicKey);
// 4. 组合
String content = encryptKey + ENCRYPT_DATA_SEPARATOR + encryptMsg;
log.info("A发送的content为:{}", content);
// 二、签名
// 1. 计算消息散列值
String msgHash = DigestUtil.sha256Hex(msg, CharsetUtil.UTF_8);
// 2. 对消息散列值用自己私钥签名
Sign sign = SecureUtil.sign(SignAlgorithm.SHA1withRSA, privateKey_A, null);
String signed = Base64.encodeBase64String(sign.sign(msgHash));
log.info("签名为:{}", signed);
map.put("content", content);
map.put("signed", signed);
log.info("发送的消息:{}", map);
} catch (Exception e) {
log.error("消息明文加密失败", e);
}
return map;
}
}
密文DP(消息接收方进行解密验签):
package com.jiangxb.rsa.dp;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import javax.validation.constraints.NotBlank;
/**
* 密文DP
*
* @author: jiangxiangbo
* @date: 2021/8/4
*/
@Data
@Slf4j
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CipherText {
private final static String ENCRYPT_DATA_SEPARATOR = "----SEPARATE----";
private final static String publicKey_A = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsnen01CdQc2zh/HihCNNYI6u7AFXf/NrZ/9auPvFsJcK1cWj5EFBU3lrts2OTvrmYVurhABg2g/Ya7glzUt6DwUojHOWtpwFxSH1v7FUJMvxDsbd4GXKRdWqMkqkcCMQYDpGpshbL3IAWYIw6pgnBcKksbzkDrZCZMAyHa1bB3zh5uEm9mcrRlBUGirbPNVt++3ztIfdc4Vp5hbw++daNMFr/VGDohMVg3Dlk4ZktDgHc5nakXkE8hSr6UDTw45JpfZZ0dP9XTi/CSVQdoYD+dsJIZ8uletlbrErRfZEJNx/k0w88P4kfGteNBGhlzzVo45tMkHT33O8QB6JxI4xVQIDAQAB";
private final static String privateKey_B = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/waNG59YY8jtd7pxpnmHk9udEhBXMPg/WAOMplqJtzFwCkYRqoWRPcCbZTqIq/qloQu7DHL51QvUm9uzT1CWO4eAMH1JXRXFIzkuxykKZYnk/VXpj9CKGiUJAcIVHqiTqgTYdYZHD9s9Bav+Ad9rbM0+74IHrhxs7EschBghGOLju3LLAAvkAGEXWaxlja9bzFoUocarvizE2l/yzQ1SdGdVEA2Me24Nr/O+seKbjmCoFaD7eA9VJDI9TfJ0yiPQCI5ElCaqmsdpkMHsG5+lMRkDojZYSWwolywYx3B9J3WzQHWILOuQyZPfSf9dfHLDypAay8m9DZX3RCIBX1tX7AgMBAAECggEBAIkRYyMGCTYfwGvuagPdYOCH1NxXBjXOjwdL7xUFRenyUDrNxbdq0gcuhbaDzMuq6XFLltwFKecsC4zkqHjqhkZSExLXOMaFLur5+4WErIJzr3OkKC5Wjm9YofDp/XsyldzCq+nomodXXuLGFwi/o8NYNEB5xKSVGNPrIkfqxfNazdR63738zq0ZPQmMjxEb/AK5uc+fdF9qosDrNI0SqQng00mhfpilvwHZbOYPfKNfh26lpqTEAGk0gaFGfr/QnhUDAnxfaoLhr9zELr4utrkwpaCzX958MrRB5naeScocYSl1h4Bi6htjjpdLWDKkk/vQ8Keno6GtF9Iha8MdvnECgYEA9p5UzaP3qYek4pe96milqmPiYkQeqSAUWSp1TpC5ppIoyPGMYl7Ia8SOsIYBw+9WL9iIR6jqZOpd/E68j8YsW5PyJuFYQTVjXZrVD76D435mskl4gsKz1izEWz5jzU/oE4mGfuaobfaOuw5ixun7dd8FrXknIVHbne0zyEZ+FY0CgYEAxw0Jq3aJbrJHX8ahPsZAodbWKYH8Ojkt2GkXKdrB9HJ6EGKockjPj7R/+ForXw2XWoGdoL4QPalhKuJX+3bsSQIgt7mDRiDEPK7XkbKd5mS/HTXWXsTIkGaDhYlq6yOkbmRsR3QgCfnhLYaaYkZ6kIKDsGgUJF8oIqxKrlDWo6cCgYBUyulzbu3nLwklE3Er2GElbYRXrv4vviTg53U/1wjN2bEGLe7Ln7UfQIyi6uBOgsrKVpO8t7onimFYL6YrdMKplfuLHK2gdf+9HlAlQqbMIBilMheqNdFpUSkOCix8Wf38QauplBrS/BPlArQ5mhdoVo74LxCiJyfwa68DLCGLvQKBgEGg4tdNtfJxhWbmrrNr2lOB6gq1eNwZjiwUOjbqkZhvRh+w56kGqKjQ8oCH+lTUvlpw8e/VurUZ65egGTIn+6/2q6Ln34h3tTvsydaX9cfI39pZrdyBNT+nDSYyMLZmggiDw8+rUgT4Bm5kOvK8Gh0bax/2sO1tEmacN+NRc/NxAoGALzEnmKI9B46NVNOWi0VLtTdiloSI6bxgli0Rm8T+6wD6y9JNnWsibYydGx9pDn6w2qihuP1QcKruHUiZ7V8aahhD4o4e/a+IgRzNYcQh4CngUzL+XymlqQVbDSaiEiVi/qSNv+i9mgeF/mSYbYcZjhFPjzdOy3hvtO3GtjDSMQw=";
@NotBlank(message = "密文不能为空")
private String content;
@NotBlank(message = "签名不能为空")
private String signed;
/**
* 解密验签
*/
public String decrypt() {
try {
// 一、分解密文
log.info("接收到的密文:{}", content);
String[] split = content.split(ENCRYPT_DATA_SEPARATOR);
assert split.length == 2;
String encryptKey = split[0];
String encryptMsg = split[1];
// 获取对称密钥
RSA rsa = new RSA(privateKey_B, null);
byte[] key = rsa.decrypt(Base64.decodeBase64(encryptKey), KeyType.PrivateKey);
log.info("获取对称密钥成功, 解析到的对称密钥为:{}", key);
// 用对称密钥解密,提取消息明文
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
String msg = aes.decryptStr(Base64.decodeBase64(encryptMsg));
assert msg != null;
// 计算明文散列值
String msgHash = DigestUtil.sha256Hex(msg, CharsetUtil.UTF_8);
// 二、解签
log.info("接收到的签名:{}", signed);
assert StringUtils.isNotBlank(signed);
Sign sign = SecureUtil.sign(SignAlgorithm.SHA1withRSA, null, publicKey_A);
boolean isVerified = sign.verify(msgHash.getBytes(CharsetUtil.UTF_8), Base64.decodeBase64(signed));
if (!isVerified) {
log.info("验签失败");
} else {
log.info("验签成功, 收到的消息为:{}", msg);
return msg;
}
} catch (Exception e) {
log.error("解密或验签失败", e);
}
return null;
}
}
demo测试:
package com.jiangxb.rsa;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.jiangxb.model.Student;
import com.jiangxb.rsa.dp.CipherText;
import com.jiangxb.rsa.dp.PlainText;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author: jiangxiangbo
* @date: 2021/7/30
*/
@Validated
@RestController
@RequestMapping("/rsa")
public class RsaController {
/**
* 接收消息
*/
@PostMapping("/post")
public String testPost(@Validated CipherText cipherText) {
return cipherText.decrypt();
}
/**
* 发送消息
*/
public static void main(String[] args) {
String msg = JSONObject.toJSONString(Student.builder().name("张三").age(20).build());
Map<String, Object> encrypt = PlainText.builder().msg(msg).build().encrypt();
String resopnse = HttpUtil.post("127.0.0.1:8080/rsa/post", encrypt);
System.out.println(resopnse);
}
}
Student实体:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private String name;
private Integer age;
}
测试结果:
[INFO ] [http-nio-8080-exec-4] [com.jiangxb.rsa.dp.CipherText ]
接收到的密文:rVcet45asWcB2KZwQv6HmOktquLyPPqixOjPpIc3eIbULnUcuorl2KIxK4XDipvreT7tATlW4VFzbC7gSFW0DfiWLdqknLyPzmC2nXBeaP8o89bMk4pe4sQ/NVct5JTRpJqx+0+8MGA4QrMUlQZ+4REhZmdLg+JyJijRSccRvQMZhBh1uOZCRWeo8gmR5oWMuquo0r91Y91ewaMoVHjWfMdtu2Y1H8vXAeb7b1aDc26SEtstZb8q/TqZlpo+hh2pVoDGIhXJUQH5VlizF9sZnZBPuLUCnar7NU+wi2oEWlrNHscaP0dKCRKsikAcf0kplM3UhXCXghC6xkZsk1uptA==----SEPARATE----Wo89FnRcZ3e8PXL40tgieUYQssiUlYIzbzP8gMXB04w=
[INFO ] [http-nio-8080-exec-4] [com.jiangxb.rsa.dp.CipherText ] 获取对称密钥成功, 解析到的对称密钥为:[-28, 4, -90, 9, 120, 77, 28, 73, 119, 18, -112, 90, 65, -59, 89, -113]
[INFO ] [http-nio-8080-exec-4] [com.jiangxb.rsa.dp.CipherText ] 接收到的签名:kh7HtOnSPQi8t/vDrle31tRWUeSGc3cyc8S5heHKThq5HtH7dCeZEY8vq+UFtlMr/eqxF19y/VonXlaSaFsRxzKLM3fVJ8AGWivdH7MVpBI3/x3cHbqDfgnSRmhVQDBnTBjrgTgO00KwZx/hxJn6x5NzfXawxpoFyIXauJuqOpW+tts8ysNJMhAeR7/LdXuISEFLK7lZf/zWESwiH+f4davQv3XiVuXzTEH6U05NGwDq2LqWMiCBh/MgQFJfLaOBUO9kCNoMcuNiS3KOG0JBLr718rPX4lUKI37C0Mc3rAzgvnlZNMv68YtPxFiHe0VhpW/ssOHk245zUPWNjIsdPA==
[INFO ] [http-nio-8080-exec-4] [com.jiangxb.rsa.dp.CipherText ] 验签成功, 收到的消息为:{"age":20,"name":"张三"}
参考
《图解密码技术》