开发常见密码技术概念&RSA使用示例

本文详细介绍了密码技术的基础概念,包括单向散列函数(如MD5、SHA系列)及其应用,对称密码(如DES、3DES和AES)和非对称密码(RSA加密与数字签名)。阐述了混合密码系统在提高效率和保护会话密钥中的作用,以及数字签名的实现和用途。文章还探讨了RSA的安全性、潜在攻击和证书在验证公钥真实性中的作用。
摘要由CSDN通过智能技术生成

一、单向散列函数

1.1 概念及术语

单向散列函数(one-way hash function)有一个输入和一个输出,其中输入称为消息(message),输出称为散列值(hash value)。单向散列函数可以根据消息的内容计算出散列值,而散列值就可以被用来检查消息的完整性。这里的消息可以是文档图像视频音频等,散列函数会将它们都当成单纯的bit序列来处理。这样,要确认完整性就不需要对比消息本身,对比小的散列值即可。

注意:单向散列函数并不是一种加密,因此无法通过解密将散列值还原为原来的消息。
术语:单向散列函数也称为消息摘要函数(message digest function)、哈希函数或者杂凑函数。输入的消息也称为原像(pre-image),输出的散列值也成为消息摘要(message digest)或者指纹(fingerprint),完整性也称为一致性

1.2 使用场景及常见函数

单向散列函数的使用场景

  1. 检测软件是否被篡改:有些软件会将计算出的散列值公布在自己的官方网站上,用户下载软件后,可以自行计算散列值,然后与官网上的散列值对比来判断是否被篡改。当软件作者通过多个站点(镜像站点)来发布软件时,单向散列函数就会在检测是否被篡改方面发挥重要作用。
  2. 被用于基于口令的加密(Password Based Encryption,PBE):PBE原理是将口令和盐(salt,通过伪随机数生成器产生的随机值)混合后计算其散列值,然后将这个散列值用作加密的密钥。通过这样的方法能够防御针对口令的字典攻击。
  3. 构造消息认证码:消息认证码是将“发送者和接收者之间的共享密钥”和“消息”进行混合后计算出的散列值。使用消息认证码可以检测并防止通信过程中的错误、篡改以及伪装。消息认证码在SSL/TLS中也得到了运用。
  4. 数字签名:数字签名的处理过程非常耗时,因此一般不会对整个消息内容直接施加数字签名,而是先通过单向散列函数计算出消息的散列值,然后再对这个散列值加数字签名。
  5. 伪随机数生成器:密码技术中所使用的随机数需要具备“事实上不可能根据过去的随机数列预测未来的随机数列”这样的性质。为了保证不可预测性,可以利用单向散列函数的单向性。
  6. 一次性口令(one-time password):一次性口令经常被用于服务器对客户端的合法性认证。在这种方式中,通过单向散列函数可以保证口令只在通信链路上传送一次,因此即使窃听者窃取了口令也无法使用。
  7. 存储密码:加盐操作配合单向散列函数可以存储用户密码,满足普通项目需求。

常见的单向散列函数

**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.png

明文经过三次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方式、椭圆曲线密码。
不难发现,公钥和私钥有如下特点:

  • 发送者只需要公钥
  • 接收者只需要私钥
  • 私钥由接收者持有,不能被窃听者获取
  • 公钥被窃听者获取也没问题

得出结论:公钥密码解决了使用对称密码时的密钥配送问题。

公钥加解密流程图示如下:
公钥加解密流程.PNG

3.1 公钥密码无法解决的问题

公钥密码解决了密钥配送问题,但这并不意味着它能够解决所有问题,因为我们需要判断所得到的公钥是否正确合法,这个问题被称为公钥认证问题;
还有个问题是,它的处理速度很慢,只有对称密码的几百分之一。

四、混合密码——用对称密码提高速度,用公钥密码保护会话密钥

通过使用对称密码,能够在通信中确保机密性。然而在实际中运用时,就必须解决密钥配送问题。
而使用公钥密码就可以避免解密密钥(私钥)配送,从而也就解决了对称密码所存在的密钥配送问题。但是,公钥密码还有两个很大的问题:

  1. 公钥密码处理速度远远低于对称密码
  2. 公钥密码难以抵御中间人攻击

混合密码系统可以解决问题1,而要解决问题2,则需要对公钥进行认证。

混合密码系统(hybrid cryptosystem)是将对称密码和公钥密码的优势相结合的方法。具体加解密流程如下:

4.1 加密

混合密码加密.PNG

会话密钥(session key)是指为本次通信而生成的的临时密钥,它一般是通过伪随机数生成器产生的。会话密钥也会作为对称密码的密钥使用。
简言之,会话密钥是对称密码的密钥,同时也是公钥密码的明文

4.2 解密

混合密码解密.PNG

分离:双方要事先约定好密文结构,这样对密文的分离操作就很容易完成。

著名的密码软件PGP、以及网络上的密码通信所使用的SSL/TLS都运用了混合密码系统。

五、数字签名——消息到底是谁写的

数字签名是根据消息内容生成的一串“只有自己才能计算出来的数值”,因此数字签名的内容是随消息的改变而改变的。

数字签名跟公钥密码有着非常紧密的联系。简言之,数字签名就是通过将公钥密码“反过来用”而实现的。如下图所示:
公钥密码与数字签名的密钥使用方式.PNG

下面再来对比一下公钥密码和私钥密码:
公钥密码包括一个由公钥和私钥组成的密钥对,其中公钥加密,私钥解密。如图:
公钥加密.PNG

数字签名中也同样会使用公钥密码和私钥密码组成的密钥对,不过这两个密钥的用法和公钥密码是相反的,即用私钥加密相当于生成签名
用公钥解密则相当于验证签名(严格来说,RSA算法中公钥加密和数字签名正好是完全相反的关系,但在其他算法中有可能不是这样完全相反的关系)。
私钥加密.PNG

公钥密码中,任何人都能够进行加密;
数字签名中,任何人都能够验证签名。

5.1 数字签名的方法

一般有两种生成和验证数字签名的方法:

  1. 直接对消息签名的方法
  2. 对消息的散列值签名的方法

直接对消息签名的方法比较容易理解,但实际上并不会使用;对消息的散列值签名的方法稍微复杂一点,但实际中一般都使用这种方法。

直接对消息签名很简单,就是用私钥对消息签名就行,接收者接收到消息和签名后,用对应公钥解签,再对比消息,一致则验签成功,否则失败。

5.1.1 对消息的散列值签名

上面说过,公钥密码算法是很慢的,那么对长消息加密也很慢。那么,能不能生成一条很短的数据来代替消息本身呢?
回想一下,上面说过的单向散列函数就非常契合。长消息的散列值永远都很短,对其加签是很轻松的。
对散列值签名的流程图如下:
签名验签流程.PNG

5.2 数字签名无法解决的问题

数字签名既可以识别出篡改和伪装,还可以防止否认。也就是说,同时实现了确认消息的完整性、进行认证以及否认防止。

然而要正确使用数字签名,有一个大前提,那就是用于验证签名的**公钥必须属于真正的发送者。**即便数字签名算法再强大,如果你得到的公钥是伪造的,那么数字签名也会完全失效。

为了能够确认自己得到的公钥是否合法,我们需要使用**证书。**所谓证书,就是将公钥当作一条消息,由一个可信的第三方对其签名后所得到的公钥。

六、证书——为公钥加上数字签名

公钥证书(Public-Key Certificate,PKC)其实和驾照很相似,里面记有姓名、组织、邮箱地址等个人信息,以及属于此人的公钥,并由认证机构(Certification Authority、Certifying Authority,CA)施加数字签名。只要看到公钥证书,我们就可以知道认证机构认定该公钥的确属于此人。公钥证书也简称为证书(certificate)。

认证机构就是能够认定“公钥确实属于此人”并能够生成数字签名的个人或者组织。世界上最有名的认证机构当属VeriSign(verisign.com)公司。

6.1 证书应用场景

认证机构必须是可信的,对于“可信的第三方”,这里用 Trent 这个名字代表,那么场景示例如下图所示
证书使用场景示例.PNG

七、RSA

7.1 RSA加密

RSA是现在使用最广泛的公钥密码算法 。它的名字是由它的三位开发者,即Ron Rivest、Adi Shamir 和 Leonard Adleman 的姓氏的首字母组成。
RSA可以被用于公钥密码和数字签名。1983年,RSA公司为RSA算法在美国取得了专利,但现在专利已过期。

RSA加密过程:

在RSA中,明文、密钥和密文都是数字。

RSA的加密解密公式如下:
RSA的加密解密公式.PNG

7.2 RSA数字签名

RSA的签名生成和验证公式如下:
RSA的签名生成和验证公式.PNG

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,但却是一种针对机密性的有效攻击。中间人攻击方式如下图:
中间人攻击.png

上图的过程可以被重复多次,Bob 向 Alice 发送加密邮件时也可能受到同样的攻击,因此 Bob 即便要发邮件给 Alice 以询问她真正的想法,也会被 Mallory 随意篡改。

这种攻击不仅针对 RSA,而是可以针对任何公钥密码。在这个过程中,公钥密码并没有被破译,保证了信息的机密性。然而,所谓的机密性并非在 Alice 和 Bob 之间,而是在 Alice 和 Mallory 之间,以及 Mallory 和 Bob 之间成立的。仅靠公钥密码本身,是无法防御中间人攻击的。

要防御中间人攻击,还需要一种手段来确认所收到的公钥是否真的属于 Bob,这种手段称为认证,这里就用到了证书。

7.4 整个加密签名,解密验签过程

根据个人理解,整合上述的所有流程,在不考虑中间人攻击的情况下,在开发中的使用流程如下:
加密与签名流程.png

接收者Bob收到消息后的处理流程如下:
解密与验签流程.png

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":"张三"}

参考

《图解密码技术》

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值