大家好,又见面了,我是你们的朋友全栈君。
文章目录
- 1、OpenSSL简介
- 2、 OpenSSL安装
- 3、加密技术介绍
- 4、openssl 命令
- 4.1摘要命令
- 4.2对称加密命令
- 4.3非对称加密命令
- 4.3.1生成私钥
- 4.3.2提取公钥
- 4.3.3利用公钥加密、私钥解密数据
- 4.3.4 数字签名
- 4.3.5数字证书
1、OpenSSL简介
OpenSSL是一个SSL协议的开源实现,采用C语言作为开发语言,具备了跨平台的能力,支持Unix/Linux、Windows、Mac OS等多种平台。 OpenSSL最早的版本在1995年发布,1998年后开始由OpenSSL项目组维护和开发。当前最新的版本是1.1.0 alpha版本,完全实现了对SSLv1、SSLv2、SSLv3和TLS的支持。。目前,OpenSSL已经得到了广泛的应用,许多类型的软件中的安全部分都使用了OpenSSL的库,如VOIP的OpenH323协议、Apache服务器、Linux安全模块等等。 OpenSSL整个软件包大概可以分成三个主要的功能部分: OpenSSL源码的目录结构也是围绕这三个功能部分进行规划的。密码算法库是一个强大完整的密码算法库,它是OpenSSL的基础部分,也是很值得一般密码安全技术人员研究的部分,它实现了目前大部分主流的密码算法和标准。主要包括对称算法、非对称算法、散列算法、数字签名和认证、X509数字证书标准、PKCS12、PKCS7等标准。其他两个功能部分SSL协议和应用程序都是基于这个库开发的。 在密码算法库的基础上实现的,SSL协议部分完全实现和封装了SSL协议的三个版本和TLS协议。使用协议库,你完全可以建立一个SSL服务器和SSL客户端。应用程序是基于密码算法库和SSL协议库实现的命令,熟悉OpenSSL可以从使用这些应用程序开始。 应用程序覆盖了密码技术的应用,主要包括了各种算法的加密程序和各种类型密钥的产生程序(如RSA、Md5、Enc等等)、证书签发和验证程序(如Ca、X509、Crl等)、SSL连接测试程序(如S_client和S_server等)以及其它的标准应用程序(如Pkcs12和Smime等)。
2、 OpenSSL安装
可以通过源码安装也可以apt-get install安装,安装openssl之前先看一下自己是否安装有openssl
代码语言:javascript复制wuyujun@wuyujun-virtual-machine:~$ openssl version
OpenSSL 1.0.1f 6 Jan 2014
wuyujun@wuyujun-virtual-machine:~$ whereis openssl
openssl: /usr/bin/openssl /usr/bin/X11/openssl /usr/include/openssl /usr/share/man/man1/openssl.1ssl.gz
我这就是已经安装有了,版本有点低,应该跟着系统一块安装的,如果想要更新可以删掉旧版本通过源码安装。 通过apt安装
代码语言:javascript复制sudo apt-get install openssl
sudo apt-get install libssl-dev
通过源码安装
代码语言:javascript复制wget https://www.openssl.org/source/openssl-1.1.1g.tar.gz
tar -xzvf openssl-1.1.1g.tar.gz
cd openssl-1.1.1g/
./config
make
sudo make install
3、加密技术介绍
加密技术包括两个元素:算法和密钥。算法是将普通的文本(或者可以理解的信息)与一串数字(密钥)的结合,产生不可理解的密文的步骤,密钥是用来对数据进行编码和解码的一种算法。在安全保密中,可通过适当的密钥加密技术和管理机制来保证网络的信息通讯安全。密钥加密技术的密码体制分为对称密钥体制和非对称密钥体制两种。
- 对称加密采用了对称密码编码技术,它的特点是文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥,这种方法在密码学中叫做对称加密算法,对称加密算法使用起来简单快捷,密钥较短。常见的对称加密算法:DES,AES,3DES等等。
- 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥 (privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。常见的非对称加密算法:RSA,ECC(移动设备用)、DSA(数字签名用)
4、openssl 命令
4.1摘要命令
消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,目前可以被解密逆向的只有CRC32算法,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。
openssl实现了5种信息摘要算法,分别是MD2、MD5、MDC2、SHA和RIPEMD。此外,OpenSSL还实现了DSS标准中规定的两种信息摘要算法DSS和DSS1。摘要一般有两个作用:1)做信息完整性校验;2)保存密码,有些密码是直接在数据库中采用MD5(真实密码值)保存的,有的还进行加盐处理,使其难以破解,这样密码只能重置,无法告诉你原始过程,因为摘要是不可逆的。
代码语言:javascript复制openssl dgst
常用选项有:
[-md5|-md4|-md2|-sha1|-sha|-mdc2|-ripemd160|-dss1] :指定一种摘要算法
-out filename:将摘要的内容保存到指定文件中
帮助:man dgst
示例如下:
单向加密除了 openssl dgst 工具还有: md5sum,sha1sum,sha224sum,sha256sum ,sha384sum,sha512sum 示例如下:
4.2对称加密命令
OpenSSL一共提供了8种对称加密算法,其中7种是分组加密算法,仅有的一种流加密算法是RC4。这7种分组加密算法分别是AES、DES、Blowfish、CAST、IDEA、RC2、RC5,都支持电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)和输出反馈模式(OFB)四种常用的分组密码加密模式。其中,AES使用的加密反馈模式(CFB)和输出反馈模式(OFB)分组长度是128位,其它算法使用的则是64位。事实上,DES算法里面不仅仅是常用的DES算法,还支持三个密钥和两个密钥3DES算法。
openssL作对称加密需要使用其子命令enc,其用法为:
代码语言:javascript复制openssl enc -ciphername [-in filename] [-out filename] [-pass arg] [-e] [-d] [-a/-base64] [-A] [-k password] [-kfile
filename] [-K key] [-iv IV] [-S salt] [-salt] [-nosalt] [-z] [-md] [-p] [-P] [-bufsize number] [-nopad] [-debug] [-none] [-engine id]
options are
-in <file> 输入文件
-out <file> 输出文件
-pass <arg> 密码
-e encrypt 加密操作
-d decrypt 解密操作
-a/-base64 base64 encode/decode, depending on encryption flag 是否将结果base64编码
-k passphrase is the next argument
-kfile passphrase is the first line of the file argument
-md 指定密钥生成的摘要算法 默认MD5
-S salt in hex is the next argument 用于加盐加密
-K/-iv key/iv in hex is the next argument 加密所需的key和iv向量
-[pP] print the iv/key (then exit if -P) 是否需要在控制台输出生成的 key和iv向量
-bufsize <n> buffer size 读写文件的I/O缓存,一般不需要指定
-nopad disable standard block padding 禁止标准填充
-engine e use engine e, possibly a hardware device 指定三方加密设备
Cipher Types 以下是部分算法,我们可以选择用哪种算法加密
-aes-128-cbc -aes-128-cbc-hmac-sha1 -aes-128-cfb
-aes-128-cfb1 -aes-128-cfb8 -aes-128-ctr
-aes-128-ecb -aes-128-gcm -aes-128-ofb
......
加密示例如下:
代码语言:javascript复制wuyujun@wuyujun-virtual-machine:~$ echo "1234567890abc" > plain.txt
wuyujun@wuyujun-virtual-machine:~$ openssl enc -aes-128-cbc -in plain.txt -out encrypt.txt
enter aes-128-cbc encryption password:
Verifying - enter aes-128-cbc encryption password:
解密示例如下:
代码语言:javascript复制wuyujun@wuyujun-virtual-machine:~$ openssl aes-128-cbc -d -in encrypt.txt -out encrypt_decrypt.txt
enter aes-128-cbc decryption password:
对称加密,加密和解密需要用相同的密码,用xxd或者hexdump查看加密后的内容以及解密后的内容
4.3非对称加密命令
OpenSSL一共实现了4种非对称加密算法,包括DH算法、RSA算法、DSA算法和椭圆曲线算法(EC)。DH算法一般用户密钥交换。RSA算法既可以用于密钥交换,也可以用于数字签名,当然,如果你能够忍受其缓慢的速度,那么也可以用于数据加密。DSA算法则一般只用于数字签名。
4.3.1生成私钥
利用openssl命令的子命令genrsa生成私钥,然后再使用子命令rsa私钥中提取公钥。genrsa的语法如下:
代码语言:javascript复制genrsa
功能:
用于生成RSA私钥,不会生成公钥,因为公钥提取自私钥
使用参数:
openssl genrsa [-out filename] [-passout arg] [-des] [-des3] [-idea] [numbits]
选项说明:
-out filename :将生成的私钥保存至filename文件,若未指定输出文件,则为标准输出。
-numbits :指定要生成的私钥的长度,默认为1024。该项必须为命令行的最后一项参数。
-des|-des3|-idea:指定加密私钥文件用的算法,这样每次使用私钥文件都将输入密码,太麻烦所以很少使用。
-passout args :加密私钥文件时,传递密码的格式,如果要加密私钥文件时单未指定该项,则提示输入密码。传递密码的args的格式见一下格式。
pass:password :password表示传递的明文密码
env:var :从环境变量var获取密码值
file:filename :filename文件中的第一行为要传递的密码。若filename同时传递给"-passin"和"-passout"选项,则filename的第一行为"-passin"的值,第二行为"-passout"的值
stdin :从标准输入中获取要传递的密码
4.3.2提取公钥
用rsa子命令从生成的私钥文件中提取公钥,rsa子命令的语法为:
代码语言:javascript复制 openssl rsa [-inform PEM|NET|DER] [-outform PEM|NET|DER] [-in filename] [-passin arg] [-out filename] [-passout arg] [-sgckey] [-des] [-des3] [-idea] [-text] [-noout] [-modulus] [-check] [-pubin] [-pubout] [-RSAPublicKey_in] [-RSAPublicKey_out] [-engine id]
rsa [options] <infile >outfile
where options are
-inform arg 输入文件编码格式,只有pem和der两种
-outform arg 输出文件编码格式,只有pem和der两种
-in arg input file 指明私钥文件的存放路径
-sgckey Use IIS SGC key format
-passin arg 如果输入文件被对称加密过,需要指定输入文件的密码
-out arg output file 指明将公钥的保存路径
-passout arg 如果输出文件也需要被对称加密,需要指定输出文件的密码
-des 对输出结果采用对称加密 des算法
-des3 对输出结果采用对称加密 des3算法
-seed encrypt PEM output with cbc seed
-aes128, -aes192, -aes256
encrypt PEM output with cbc aes
-camellia128, -camellia192, -camellia256
encrypt PEM output with cbc camellia
以上几个都是对称加密算法的指定,生成私钥的时候一般会用到
-text print the key in text 以明文形式输出各个参数值
-noout don't print key out 不输出密钥到任何文件
-modulus 输出模数值
-check 检查输入密钥的正确性和一致性
-pubin 指定输入文件是公钥
-pubout 指定输出文件是公钥
-engine e 指定三方加密库或者硬件
4.3.3利用公钥加密、私钥解密数据
使用rsautl进行加密和解密操作,语法如下:
代码语言:javascript复制 openssl rsautl [-in file] [-out file] [-inkey file] [-pubin] [-certin] [-sign] [-verify] [-encrypt] [-decrypt] [-pkcs] [-ssl] [-raw] [-hexdump] [-asn1parse]
openssl rsautl -h
Usage: rsautl [options]
-in file input file 输入文件
-out file output file 输出文件
-inkey file input key 指定私有密钥文件,格式是RSA私有密钥文件
-keyform arg private key format - default PEM 指定私钥格式
-pubin input is an RSA public 指定输入的是RSA公钥
-certin input is a certificate carrying an RSA public key 指定输入的是证书文件
-ssl 使用SSLv2的填充方式
-raw 不进行填充
-pkcs 使用V1.5的填充方式(默认)
-oaep 使用OAEP的填充方式
-sign 使用私钥做签名
-verify 使用公钥认证签名
-encrypt 使用公钥加密
-decrypt 使用私钥解密
-hexdump 以16进制打印
-engine e 指定三方库或者硬件设备
-passin arg pass phrase source 传递密码来源
操作示例如下:
代码语言:javascript复制/*创建需要加密的文件*/
wuyujun@wuyujun-virtual-machine:~$ echo "123456789 hello world!" > plain.txt
/*生成RSA密钥,采用des3对称加密私钥*/
wuyujun@wuyujun-virtual-machine:~$ openssl genrsa -des3 -passout pass:123456 -out RSA.pem
Generating RSA private key, 2048 bit long modulus
...........................
...........................................
e is 65537 (0x10001)
/*提取公钥*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsa -in RSA.pem -passin pass:123456 -pubout -out pub.pem
writing RSA key
/*使用RSA作为密钥进行加密,实际上使用其中的公钥进行加密*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -encrypt -in plain.txt -inkey RSA.pem -passin pass:123456 -out enc.txt
/*使用RSA作为密钥进行解密,实际上使用其中的私钥进行解密*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -decrypt -in enc.txt -inkey RSA.pem -passin pass:123456 -out replain.txt
/* 查看加密后的内容 */
wuyujun@wuyujun-virtual-machine:~$ xxd enc.txt
0000000: 5ba5 4cb5 d298 e923 b5d0 218f d287 cfad [.L....#..!.....
. . . . . .
00000f0: db5d fe03 73e6 71c2 6b2a 9122 05de 992e .]..s.q.k*.
/*查看解密后的内容*/
wuyujun@wuyujun-virtual-machine:~$ xxd replain.txt
0000000: 3132 3334 3536 3738 3920 6865 6c6c 6f20 123456789 hello
0000010: 776f 726c 6421 0a world!.
/*查看原文件内容*/
wuyujun@wuyujun-virtual-machine:~$ xxd plain.txt
0000000: 3132 3334 3536 3738 3920 6865 6c6c 6f20 123456789 hello
0000010: 776f 726c 6421 0a world!.
/*使用公钥进行加密*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -encrypt -in plain.txt -inkey pub.pem -pubin -out enc1.txt
/*私钥进行解密*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -decrypt -in enc1.txt -inkey RSA.pem -passin pass:123456 -out replain1.txt
/*查看解密后的内容*/
wuyujun@wuyujun-virtual-machine:~$ xxd replain1.txt
0000000: 3132 3334 3536 3738 3920 6865 6c6c 6f20 123456789 hello
0000010: 776f 726c 6421 0a world!.
/*查看原文件内容*/
wuyujun@wuyujun-virtual-machine:~$ xxd plain.txt
0000000: 3132 3334 3536 3738 3920 6865 6c6c 6f20 123456789 hello
0000010: 776f 726c 6421 0a world!.
4.3.4 数字签名
上面是RSA 公钥加密,私钥解密过程。如果是用私钥进行加密,公钥解密叫做数字签名,因为私钥只有一份,用公钥解密出来验证确认是你用这个私钥做的签名,这就是签名和验证。
先用pkcs8子命令提取出pkcs8格式的私钥,rsa默认生成pkcs1格式的私钥,当然也可以直接使用默认的来做签名和验证,在用java等一些开发中需要要求私钥是pkcs8格式,pkcs8子命令格式以及参数如下:
代码语言:javascript复制openssl pkcs8
[-inform PEM|DER] [-outform PEM|DER] [-in filename] [-passin arg] [-out filename]
[-passout arg] [-topk8] [-noiter] [-nocrypt] [-nooct] [-embed] [-nsdb] [-v2 alg]
[-v1 alg] [-engine id]
参数说明:
-inform PEM|DER :输入文件格式,DER或者PEM格式。DER格式采用ASN1的DER标准格式。一般用的多的都是PEM格式,就是base64编码格式。
-outform DER|PEM :输出文件格式,DER或者PEM格式。
-in filename :输入的密钥文件,默认为标准输入。如果密钥被加密,会提示输入一个密钥口令。
-passin arg :输入文件口令保护来源。
-out filename :输出文件,默认为标准输出。如果任何加密操作已经执行,会提示输入一个密钥值。输出的文件名字不能和输入的文件名一样。
-passout arg :输出文件口令保护来源。
-topk8 :通常的是输入一个pkcs8文件和传统的格式私钥文件将会被写出。设置了此选项后,位置转换过来:输入一个传统格式的私钥文件,输出一个PKCS#8格式的文件。
-noiter :MAC保护计算次数为1。
-nocrypt :PKCS#8密钥产生或输入一般用一个适当地密钥来加密PKCS#8 EncryptedPrivateKeyInfo结构。设置了此选项后,一个不加密的PrivateKeyInfo结构将会被输出。这个选项一直不加密私钥文件,在绝对必要的时候才能够使用。某些软件例如一些JAVA代码签名软件使用不加密的私钥文件。
-nooct :这个选项产生的RSA私钥文件是一个坏的格式,一些软件将会使用。特别的是,私钥文件必须附上一个八位组字符串,但是一些软件仅仅包含本身的结构体没有使八位组字符串所环绕。不采用八位组表示私钥。
-embed :这个选项产生的RSA私钥文件是一个坏的格式。在私钥结构体中采用嵌入式DSA参数格式。在这个表单中,八位组字符串包含了ASN1 SEQUENCE中的两种结构:一个SEQUENCE包含了密钥参数,一个ASN1 INTEGER包含私钥值。
-nsdb :这个选项产生的RSA私钥文件是一个坏的格式并兼容了Netscape私钥文件数据库。采用NetscapeDB的DSA格式。
-v2 alg :采用PKCS#5 v2.0,并指定加密算法,默认的是PKCS#8私钥文件被叫做B<pbeWithMD5AndDES-CBC>(该算法用56字节的DES加密但是在PKCS#5 v1.5中有更加强壮的加密算法)的加密算法用口令进行加密。用B<-v2>选项,PKCS#5 v2.0相关的算法将会被使用,可以是des3(168字节)和rc2(128字节),推荐des3。
-v1 alg :采用PKCS#5 v1.5或pkcs12,并指定加密算法。
-engine id :指定硬件引擎。
然后用rsautl子命令-sign生成签名,-verify验证,示例如下:
代码语言:javascript复制/*提取PCKS8格式的私钥*/
wuyujun@wuyujun-virtual-machine:~$ openssl pkcs8 -topk8 -in RSA.pem -passin pass:123456 -out pri.pem -nocrypt
/*使用私钥生成签名*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -sign -in plain.txt -inkey pri.pem -out sign.txt
/*使用公钥对签名进行验证*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -verify -in sign.txt -inkey pub.pem -pubin -out replain.txt
/*查看公钥验证签名解密的内容*/
wuyujun@wuyujun-virtual-machine:~$ xxd replain.txt
0000000: 3132 3334 3536 3738 3920 6865 6c6c 6f20 123456789 hello
0000010: 776f 726c 6421 0a world!.
/*查看原内容*/
wuyujun@wuyujun-virtual-machine:~$ xxd plain.txt
0000000: 3132 3334 3536 3738 3920 6865 6c6c 6f20 123456789 hello
0000010: 776f 726c 6421 0a world!.
/*用默认的rsa生成的pkcs1格式私钥生成签名*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -sign -in plain.txt -inkey RSA.pem -passin pass:123456 -out sign1.txt
/*用公钥验证签名*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -verify -in sign1.txt -inkey pub.pem -pubin -out replain1.txt
/*查看解密内容*/
wuyujun@wuyujun-virtual-machine:~$ xxd replain1.txt
0000000: 3132 3334 3536 3738 3920 6865 6c6c 6f20 123456789 hello
0000010: 776f 726c 6421 0a world!.
4.3.5数字证书
为了确保拿到的服务器公钥确实是正确的服务器的公钥,即有人将其他的服务器的公钥给了客户端,使客户端误以为自己在跟正确的服务器进行交互。(攻击者可以在代理服务器层拦截客户端的请求,再重定向到自己的服务器) 这时候需要一个权威的第三方机构(CA)确认这一个公钥确实是真实的服务器的公钥,服务器将自己的公钥和一些私人信息发给 CA,CA 用自己的私钥将这些数据加密之后也就是数字签名,这样的签名结果称为数字证书(SSL证书), 数字证书遵循X509标准。 当服务器向客户端发送数据的时候,还附带上从 CA 下载到本地的证书,客户端拿到证书以后使用CA的公钥进行解密,确认服务器的公钥无误,最后用服务器的公钥解密。更详细的可以看这个博客加密与安全:图解非对称加密算法 RSA 数字签名与数字证书
代码语言:javascript复制/*生成私钥*/
wuyujun@wuyujun-virtual-machine:~$ openssl genrsa -des3 -passout pass:123456 -out private.key
Generating RSA private key, 2048 bit long modulus
......
..............................................................
e is 65537 (0x10001)
/*查看私钥信息*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsa -noout -passin pass:123456 -text -in private.key
证书请求:
代码语言:javascript复制openssl req -new -key private.key -out cert.csr (-config openssl.cnf
)openssl req -new -nodes -key private.key -out cert.csr (-config openssl.cnf)
这个命令将会生成一个证书请求,当然,用到了前面生成的密钥private.key文件 这里将生成一个新的文件cert.csr,即一个证书请求文件,你可以拿着这个文件去数字证书颁发机构(即CA)申请一个数字证书。CA会给你一个新的文件cacert.pem,那才是包含公钥给对方用的数字证书。 查看证书请求:
代码语言:javascript复制openssl req -noout -text -in cert.csr
如果是自己测试使用,加上-x509生成自签名证书
代码语言:javascript复制/*自签名证书,用于自己测试,不需要CA签发*/
wuyujun@wuyujun-virtual-machine:~$ openssl req -new -x509 -key private.key -passin pass:123456 -out cacert.pem -days 1095
/*查看证书信息*/
wuyujun@wuyujun-virtual-machine:~$ openssl x509 -noout -text -in cacert.pem
自签是用自己的私钥给证书签名,有了private.key和cacert.pem文件后就可以在自己的程序中使用了,比如做一个加密通讯的服务器。 测试:
代码语言:javascript复制/*从证书中提取公钥*/
wuyujun@wuyujun-virtual-machine:~$ openssl x509 -in cacert.pem -pubkey >> public.key
/*创建文件*/
wuyujun@wuyujun-virtual-machine:~$ echo "Hello 2020">plain.txt
/*采用私钥加密*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -sign -in plain.txt -inkey private.key -passin pass:123456 -out sign.txt
/*用提取出来的公钥解密*/
wuyujun@wuyujun-virtual-machine:~$ openssl rsautl -verify -in sign.txt -inkey public.key -pubin -out replain.txt
/*查看原文件内容*/
wuyujun@wuyujun-virtual-machine:~$ xxd plain.txt
0000000: 4865 6c6c 6f20 3230 3230 0a Hello 2020.
/*查看解密后文件内容*/
wuyujun@wuyujun-virtual-machine:~$ xxd replain.txt
0000000: 4865 6c6c 6f20 3230 3230 0a Hello 2020.
当然这只是简单的测试,一般情况下,消息一般和证书一块发送,接收端就通过证书和CA公钥验证发送端公钥,接着用公钥解密获取消息。 上面做的只是自签证书,CA签发则是用CA的私钥给自己的证书签名来保证证书的可靠性,利用OpenSSL可以自己作为CA进行证书签发,当然这并不权威,用openssl建立CA这里就不在说了,感兴趣可以看看这个OpenSSL – 利用OpenSSL自签证书和CA颁发证书。
参考:https://www.jianshu.com/p/15b1d935a44b https://www.cnblogs.com/aixiaoxiaoyu/p/8650180.html https://blog.csdn.net/liwei16611/article/details/83686674 https://cloud.tencent.com/developer/article/1129327
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/125761.html原文链接:https://javaforall.cn