加密解密

算法名称

  • AES、DES 对称加密算法(密文可通过秘钥还原成原始数据)
  • RSA、DSA、ECC 非对称加密
  • CRC32、MD5、SHA1 摘要算法(加签)
    • CRC32 Cyclic Redundancy Check,又称循环冗余校验,类似还有CRC64(出现碰撞的概率小),常用于校验网络上传输的文件
    • MD5 Message-Digest Algorithm 5,又叫摘要算法和哈希算法
    • SHA1 Secure Hash Algorithm,又叫安全散列算法
    • 区别
      • CRC的计算效率很高;MD5和SHA1比较慢
      • CRC一般用作通信数据的校验(毕竟效率高适用于通信数据校验)或数据库索引;MD5和SHA1用于安全(Security)领域,比如文件校验、数字签名等

加密相关的概念

  • 对称加密
    • 这是加密文件常用的方式,加密的时候输入一个密码,解密的时候也用这个密码,加密和解密都用同一个密码,所以叫对称加密。常见的算法有AES3DES
  • 非对称加密
    • 非对称加密有两个不一样的密码,一个叫私钥,另一个叫公钥,用其中一个加密的数据只能用另一个密码解开,用自己的都解不了,也就是说用公钥加密的数据只能由私钥解开,反之亦然
    • 私钥一般自己保存,而公钥是公开的,同等加密强度下,非对称加密算法的速度比不上对称加密算法的速度,所以非对称加密一般用于数字签名和密码(对称加密算法的密码)的交换。常见的算法有RSADSAECC
  • 摘要算法
    • 摘要算法不是用来加密的,其输出长度固定,相当于计算数据的指纹主要用来做数据校验,验证数据的完整性和正确性。常见的算法有MD5SHA1SHA256CRC
  • 数字签名
    • 数字签名就是 “非对称加密+摘要算法”,其目的不是为了加密,而是用来防止他人篡改数据
    • 其核心思想是
      • 比如A要给B发送数据,A先用摘要算法得到数据的指纹,然后用A的私钥加密指纹,加密后的指纹就是A的签名,B收到数据和A的签名后,也用同样的摘要算法计算指纹,然后用A公开的公钥解密签名,比较两个指纹,如果相同,说明数据没有被篡改,确实是A发过来的数据
      • 假设C想改A发给B的数据来欺骗B,因为篡改数据后指纹会变,要想跟A的签名里面的指纹一致,就得改签名,但由于没有A的私钥,所以改不了,如果C用自己的私钥生成一个新的签名,B收到数据后用A的公钥根本就解不开

SSL/TLS

介绍

  • SSL(Secure Sockets Layer)和TLS(Transport Layer Security)的关系就像windows XP和windows 7的关系,升级后改了个名字而已
  • TLSv1是建立在SSLv3.0之上的,可以理解成SSLv3.1,中间还有TLSv1.1,目前一般推荐使用TLSv1.2,但是最新版本已近到了TLSv1.3
    • 具体使用得TLS协议版本有客户端优先选择,但是服务器可设置支持的协议版本。像XP系统下的谷歌(v49)就不支持TLSv1.2,但是XP系统下的火狐浏览器是支持的
  • 最初的SSL只支持TCP,现在已经可以支持UDP
  • HTTPS=HTTP+TLSFTPS=FTP+TLS。SSH和SSL/TLS是两个不同的协议,SSH并不依赖于SSL/TLS
  • 测试https访问 https://www.ssllabs.com/ssltest/analyze.html?d=test.aezo.cn

证书概念

  • 私钥:私钥就是一个算法名称加上密码串,自己保存,从不给任何人看
  • 公钥:公钥也是一个算法名称加上密码串,一般不会单独给别人,而是嵌在证书里面一起给别人
  • CA:专门用自己的私钥给别人进行签名的单位或者机构
  • 申请签名文件:在公钥的基础上加上一些申请人的属性信息,比如我是谁,来自哪里,名字叫什么,证书适用于什么场景等的信息。然后带上进行的签名,发给CA(私下安全的方式发送),带上自己签名的目的是为了防止别人篡改文件
  • 证书文件证书由公钥加上描述信息,然后经过私钥签名之后得到。一般都是一个人(一般是CA)的私钥给另一个人的公钥签名;如果是自己的私钥给自己的公钥签名,就叫自签名
  • 签名过程
    • CA收到申请文件后,会走核实流程,确保申请人确实是证书中描述的申请人,防止别人冒充申请者申请证书,核实通过后,会用CA的私钥对申请文件进行签名
    • 签名后的证书包含:申请者的基本信息,CA的基本信息,证书的使用年限,申请人的公钥,签名用到的摘要算法,CA的签名
    • 签完名之后,证书就可以用了
  • 证书找谁签名合适
    • 别人认不认你的证书要看上面签的是谁的名,所以签名一定要找权威的人来签,否则别人不认,哪谁是权威的人呢?那就是CA,哪些CA是受人相信的呢?那就要看软件的配置,配置相信谁就相信谁,比如浏览器、操作系统等,安装好了之后里面就内置了很多信任的CA的证书,只要是那些CA签名的证书,操作系统/浏览器都会相信。而自己写的程序,可以由你自己指定信任的CA(即使用自签名证书);浏览器使用自签名证书时必须将CA证书添加为信任的证书,否则会有警告
  • 那么CA的证书又是谁签的名呢?一般CA都是分级的,CA的证书都是由上一级的CA来签名,而最上一级CA的证书是自签名证书
  • 以浏览器为例,说明证书的验证过程
    • 在TLS握手的过程中,浏览器得到了网站的证书
    • 打开证书,查看是哪个CA签名的这个证书
    • 在自己信任的CA库中,找相应CA的证书
    • 用CA证书里面的公钥解密网站证书上的签名,取出网站证书的校验码(指纹),然后用同样的算法(比如sha256)算出出网站证书的校验码,如果校验码和签名中的校验码对的上,说明这个证书是合法的,且没被人篡改过
    • 读出里面的CN,对于网站的证书,里面一般包含的是域名
    • 检查里面的域名和自己访问网站的域名对不对的上,对的上的话,就说明这个证书确实是颁发给这个网站的
    • 到此为止检查通过
    • 如果浏览器发现证书有问题,一般是证书里面的签名者不是浏览器认为值得信任的CA,浏览器就会给出警告页面,这时候需要谨慎,有可能证书被掉包了。如访问12306网站,由于12306的证书是自己签的名,并且浏览器不认为12306是受信的CA,所以就会给警告,但是一旦把12306的根证书安装到了你的浏览器中,那么下次就不会警告了,因为配置了浏览器让它相信12306是一个受信的CA

TLS握手过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+--------+                                                                                                +--------+
| | 1. ClientHello(发送TLS版本及密码套件/算法) | |
| |----------------------------------------------------------------------------------------------->| |
| | | |
| | 2. ServerHello(确认TLS版本及密码套件) | |
| | 3. Certificate(发送服务器证书) | |
| | 4. ServerKeyExchange (optional. 如DHE_RSA非对称加密算法需要发送一个消息给客户端生成premaster) | |
| | 5. CertificateRequest (optional. 如使用U盾访问银行网站需要) | |
| | 6. ServerHelloDone | |
| |<-----------------------------------------------------------------------------------------------| |
| Client | | Server |
| | 7. Certificate (optional. 发送客户端证书给服务器验证) | |
| | 8. ClientKeyExchange(生成premaster, RSA可直接生成, DHE_RSA需4.ServerKeyExchange中的消息) | |
| | 9. CertificateVerify (optional. 配合7.Certificate, 验证客户端证书对应的私钥确实是在客户端手里) | |
| | 10. Finished | |
| |----------------------------------------------------------------------------------------------->| |
| | | |
| | 11. Finished | |
| |<-----------------------------------------------------------------------------------------------| |
+--------+ +--------+

证书生成示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mkdir cert && cd cert
## CA机构(或者公司内部项目间进行互认可自己创建根证书):生成CA的私钥和证书(openssl参数说明见下文)
# 生成的过程中会要求填一些信息,除了`Common Name`(CN)要取一个容易区分的名字之外,如网站域名(如ca.com),其它都可以随便填写(可参考下文)
# -x509:专用于CA生成自签证书,如果不是自签证书则不需要此项
# -days:证书的有效期限,单位是day(天),默认是365天
openssl req -newkey rsa:2048 -nodes -sha256 -keyout ca.key -x509 -days 365 -out ca.crt

## 普通程序商:生成私钥(aezo.key)和证书签名申请文件(aezo.csr)
# 这里和上面的区别就是这里是-new生成一个证书签名申请文件,而上面用-x509生成一个自签名文件,其它的参数意义都一样,如网站域名(如aezo.cn)
# 可知CA的私钥和普通人的私钥没什么区别,唯一的区别就是CA用私钥自签名的证书受别人相信,而普通人的自签名证书别人不信,所以需要CA来给证书签名
openssl req -newkey rsa:2048 -nodes -sha256 -keyout aezo.key -new -out aezo.csr

## 使用CA的私钥对申请文件进行签名(从而得到证书文件 aezo.crt)
# 由于需要往生成的证书里写入签名者的信息,所以这里需要ca.crt,ca.key里面只有私钥的信息
openssl x509 -CA ca.crt -CAkey ca.key -in aezo.csr -req -days 365 -out aezo.crt -CAcreateserial -sha256
# 将 aezo.key(私钥) 和 aezo.crt (证书) 提交给用户; 部分常用可能还需要提供 ca.crt

## 查看证书内容
# 上面生成的证书文件格式都是pem格式,需通过下列命令查看
openssl x509 -text -noout -in ca.crt
# 可以看到Issuer对应的是ca.com(CA签名信息)
openssl x509 -text -noout -in aezo.crt
  • openssl参数说明
    • -newkey rsa:2048:生成一个长度为2048的采用RSA算法的私钥
    • -nodes:这个私钥在本地存储的时候不加密(可以通过其它参数来加密私钥,这样存储比较安全)
    • -sha256:生成的证书里面使用sha256作为摘要算法
    • -keyout ca.key:输出私钥到ca.key(或者取名key.pem)
    • -x509:证书文件格式为x509,目前TLS默认只支持这种格式的证书
    • -days 365:证书有效期1年
    • -out ca.crt:生成的证书文件保存到ca.crt(或者取名cert.pem)
  • 证书文件
    • 证书的CRT内容:”—–BEGIN CERTIFICATE—–”开头,”—–END CERTIFICATE—–”结尾
    • 证书的私钥内容:”—–BEGIN PRIVATE KEY—–”开头,”—–END PRIVATE KEY—–”结尾
  • 证书生成填写
1
2
3
4
5
6
7
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:Shanghai
Locality Name (eg, city) [Default City]:Huangpu
Organization Name (eg, company) [Default Company Ltd]:AEZO
Organizational Unit Name (eg, section) []:JAVA
Common Name (eg, your name or your server's hostname) []:smalle
Email Address []:test@qq.com

HTTPS

  • 证书检测在线工具 可以查看包括二级证书
  • 使用 HTTPS 时,所有的 HTTP 请求和响应数据在发送到网络之前,都要进行加密。网络分层如下

https-net

  • 不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,会有以下风险
    • 窃听风险(eavesdropping):第三方可以获知通信内容
    • 篡改风险(tampering):第三方可以修改通信内容
    • 冒充风险(pretending):第三方可以冒充他人身份参与通信
  • SSL 是个二进制协议,与 HTTP 完全不同,其流量是承载在另一个端口上的(SSL 通常是由端口 443 承载的)
    • 如果URL的方案为http,客户端会打开服务器80端口的连接
    • 如果URL的方案为https,客户端就会打开一条到服务器端口443(默认情况下) 的连接,然后与服务器“握手”,以二进制格式与服务器交换一些 SSL 安全参数, 附上加密的 HTTP 命令
  • 服务器公钥放在服务器的数字证书之中
  • 清除谷歌证书缓存:访问chrome://net-internals/#hsts,在Delete domain security policies中输入域名删除证书,然后重新打开浏览器

Let’s Encrypt免费证书使用

Linux服务器证书申请(基于certbot)

  • Linux基于certbot安装和自动更新证书 ^3
  • 简单测试(建议参考下文: 结合自动验证DNS脚本进行配置)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
## 安装
## ***建议参考下文: 结合自动验证DNS脚本进行配置***
sudo yum install certbot python3-certbot-nginx # 第二个包为基于nginx自动安装证书的插件, 其他版本如python2-certbot-nginx
certbot -h # 查看帮助
certbot --help all # 查看所有帮助

## 参数说明
run # 获取并安装证书到当前的Web服务器,默认选项,`./certbot run` 和 `./certbot` 效果一致
certonly # 获取或续期证书,但是不安装
renew # 在证书快过期时,续期之前获取的所有证书
-d DOMAINS # 一个证书支持多个域名,用逗号分隔,也可 -d xxx -d yyy

--apache # 使用 Apache 插件来认证和安装证书
--standalone # 运行独立的 web server 来验证,临时在服务器启动一个服务监听80端口
--nginx # 使用 Nginx 插件来认证和安装证书
--webroot # 如果目标服务器已经有 web server 运行且不能关闭,可以通过往服务器的网站根目录放置文件的方式来验证
--manual # 通过交互式方式,或 Shell 脚本手动获取证书。之后需要将证书手动设置到nginx

certificates # 显示从 Certbot 获取的证书的信息
revoke # 撤销证书,certbot revoke --cert-path /path/to/fullchain.pem [options]
delete # 删除证书
register # 创建一个 Let's Encrypt ACME 账户

## (可选)注册Let's Encrypt账号,需要同意协议并设置邮箱。如果此处不注册,可在申请证书的时候设置邮箱
certbot register

## (忽略)手动获取证书(建议参考下文去掉--manual使用自动)
certbot certonly --manual -d test.aezo.cn
# 1.执行后交互命令行会提示需要验证域名,确认生成证书的机器的IP是域名解析到的IP地址(y)
# 2.再确认给定连接可以正常返回再执行下一步。如需要访问 http://test.aezo.cn/.well-known/acme-challenge/0ye4Z1RWQSJmLZxHMGunNknbozOFW2rMNZpz8-ODabQ 返回 abDhqPN_fTSdSsThZoDa1ez6B64LoGXdG5tXAtRLeAc.UTd9IUMKNv88HZklaYypIp4B0Pjpvhv-taL12btuDSs (因此需要在服务器增加此url)
# 3.提示 Congratulations 表示获取证书成功。证书和key均保存在 /etc/letsencrypt/live/test.aezo.cn (共四个文件)
# 4.手动配置nginx证书见下文

## 自动获取并安装证书到nginx.conf,配置时会提示是否自动将http重定向https(输入1/2选择)。如果nginx.conf中有中文可能设置失败
certbot --nginx -d test.aezo.cn -d *.aezo.cn
# 也可基于配置文件自动安装证书(之后命令行输入通过逗号分割需要安装的域名序号)
# 如果nginx配置文件不为/etc/nginx/nginx.conf,则使用--nginx-server-root指定nginx配置文件路径
certbot --nginx --nginx-server-root=/www/server/nginx/conf

## 自动更新脚本
crontab -e
# 在每天凌晨3点运行,该命令将检查服务器上的证书是否将在未来30天内过期,如果是,则进行更新
0 3 * * * /usr/bin/certbot renew --quiet # --quiet 指令告诉 certbot 不要生成输出
# 将在每两个月的凌晨 1:10 执行
# 10 1 * */2 * /usr/bin/certbot renew --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"
  • 结合自动验证DNS脚本进行配置通配符证书(阿里云需要AccessKey账号)(2403)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
## 参考:https://www.cnblogs.com/trblog/p/14690908.html
## 将EPEL添加到CentOS 7,并安装snapd(install速度比较慢)
yum install epel-release
yum install snapd
systemctl enable --now snapd.socket
ln -s /var/lib/snapd/snap /snap

## 安装certbot
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot

## 自动验证DNS脚本
# 不管是申请还是续期,只要是通配符证书,只能采用 dns-01 的方式校验申请者的域名,也就是说 certbot 操作者必须手动添加 DNS TXT 记录
# certbot 提供了一个 hook,可以编写一个 Shell 脚本,让脚本调用 DNS 服务商的 API 接口,动态添加 TXT 记录,这样就无需人工干预了
# git clone https://gitee.com/mirrors_ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au.git
cd /opt
git clone https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au # 速度慢可使用上面的仓库
cd certbot-letencrypt-wildcardcertificates-alydns-au
chmod 0777 au.sh
# 修改配置
# 场景的com/cn根域名已经预置在domain.ini中了,如果是其他后缀可添加进去
# 如果是阿里云的域名,则修改 ALY_KEY/ALY_TOKEN 的Token(可使用主账号的AccessKey;或者创建一个子账号,然后增加 AliyunDNSFullAccess 云解析权限,然后使用子账号的AccessKey)
vi au.sh

## 申请证书测试(dry-run)
# 回车命令后,输入邮箱,回车后输入Y,回车后再输入N(不share email)
# manual-auth-hook 和 manual-cleanup-hook 用于添加和删除DNS TXT记录(申请的时候需要添加,获取到证书后,此TXT记录就没啥用了,可删除)
# 更多参数参考:https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au
certbot certonly \
-d example.com \
-d *.example.com \
--manual \
--preferred-challenges dns \
--dry-run \
--manual-auth-hook "/opt/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly add" \
--manual-cleanup-hook "/opt/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly clean" \
--pre-hook "systemctl stop nginx.service" \
--post-hook "systemctl start nginx.service"

## 正式申请证书
# 去掉 --dry-run 参数即可

## 部署证书
# (七牛上传)证书路径在 /etc/letsencrypt/live,复制 fullchain.pem 和 privkey.pem 到普通用户目录,并设置文件普通用户权限,然后下载上传到如七牛即可
# nginx中配置参考下文:手动配置nginx证书

## 增加自动续期参考上文
crontab -e
0 3 * * * /usr/bin/certbot renew --quiet
  • 手动配置nginx证书
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
# listen 80;
server_name test.shop.cn;
root /home/www;
index index.html index.htm;

listen 443 ssl; # 一定需要对外开放443端口

ssl_certificate /etc/letsencrypt/live/test.aezo.cn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test.aezo.cn/privkey.pem;
}
server {
server_name test.shop.cn;
listen 80;

return 301 https://$host$request_uri;
}
  • 测试https访问 https://www.ssllabs.com/ssltest/analyze.html?d=test.aezo.cn
  • 常见错误

Linux服务器证书申请(基于acme.sh)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 参考:https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E

## 安装 acme.sh
# 方式一:安装目录:~/.acme.sh/;会自动设置定时任务续签证书
curl https://get.acme.sh | sh -s email=my@example.com
# 方式二:大陆建议手动下载仓库安装(服务器访问github慢)
git clone https://gitee.com/neilpang/acme.sh.git
cd acme.sh
./acme.sh --install -m my@example.com
cd ~/.acme.sh

## 生成证书
# acme.sh 实现了 acme 协议支持的所有验证协议. 一般有两种方式验证: http 和 dns 验证
# http 方式(推荐): 需要在你的网站根目录下放置一个文件, 来验证你的域名所有权, 完成验证
# 手动 dns 方式: 手动在域名上添加一条 txt 解析记录, 验证域名所有权
# 使用http 方式,此命令会自动解析nginx配置,在域名根目录创建验证文件,完成验证后删除验证文件
acme.sh --issue -d example.com --nginx

## copy/安装证书
# 默认生成的证书都放在安装目录下: ~/.acme.sh/
# 可复制到nginx证书目录,或者通过命令进行复制并重启nginx
acme.sh --install-cert -d example.com \
--key-file /path/to/keyfile/in/nginx/key.pem \
--fullchain-file /path/to/fullchain/nginx/cert.pem \
--reloadcmd "service nginx force-reload"

## 查看已安装证书信息
acme.sh --info -d example.com

## 更新证书
# 安装acme.sh后,便会自动创建cronjob,可使用`crontab -l`查看

Windows证书申请(基于win-acme)

  • 基于win-acme,下载win-acme.v2.1.7.807.x64.trimmed.zip
  • 生成证书操作流程
    • 执行wacs.exe文件
    • Create new cerificate (full options)
    • Manual input
    • 输入host name
    • 选择默认认证方式
      • serve verification files from memory 通过内存验证服务器(需要在实际服务器上运行上述exe。会先访问Lets自动提交工单,然后Lets会访问配置的域名,会在内存生成一个随机码进行验证)
      • [dns-01] Create verification records manually (auto-renew not possible) 基于DNS进行验证(可在工作机上运行上述exe。选择后继续往下操作,最后会生成一个DNS解析值;如生成test.aezo.cn的证书,则他需要解析出一个 _acme-challenge.test,类型为TXT,值为随机生成的一串字符;解析好后,稍等片刻等域名解析生效后再继续执行后续步骤进行验证)
    • rsa key
    • pem encoded files(Apache, nginx, etc.)
    • 输入文件存放路径
    • No store steps
  • 将生成的文件设置到ngixn(参考上文手动配置nginx证书)
  • 查看托管的证书
    • 再次执行wacs.exe文件
    • A: Manage renewals (1 total) 可查看托管的自动更新证书

证书过期导致RestTemplate(SpringBoot)访问接口失败

  • 报错:unable to find valid certification path to requested target
  • 自定义RestTemplate同时支持访问http与https
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Bean
public RestTemplate restTemplate() throws KeyStoreException, NoSuchAlgorithmException {
final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
// 设置过期时间
factory.setConnectionRequestTimeout(5000);
factory.setReadTimeout(5000);
factory.setReadTimeout(5000);

final SSLContextBuilder builder = new SSLContextBuilder();
// 全部信任 不做身份鉴定
builder.loadTrustMaterial(null, (X509Certificate[] x509Certificate, String s) -> true);

// 客户端支持SSLv2Hello,SSLv3,TLSv1,TLSv1
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(builder.build(), new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);

//为自定义连接器注册http与https
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", new PlainConnectionSocketFactory()).register("https", socketFactory).build();

PoolingHttpClientConnectionManager phccm = new PoolingHttpClientConnectionManager(registry);
phccm.setMaxTotal(500);
final CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).setConnectionManager(phccm).setConnectionManagerShared(true).build();
factory.setHttpClient(httpClient);
final RestTemplate restTemplate = new RestTemplate(factory);
return restTemplate;
}

AES/DES

简介

  • 密码学中的高级加密标准 (Advanced Encryption Standard,AES),又称高级加密标准Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。高级加密标准已然成为对称密钥加密中最流行的算法之一。该算法为比利时密码学家Joan Daemen和VincentRijmen所设计,结合两位作者的名字,以Rijndael命名之 [1]
  • 加密方式如
    • AES/CBC/NOPadding
    • AES/CBC/PKCS5Padding 128位(16字节),jdk默认支持,建议使用
    • AES/CBC/PKCS7Padding 256位(32字节),jdk默认不支持
  • 说明
    • 类似有 DES/CBC/PKCS5Padding
    • 上述命名意义分别为:AES为算法名称,CBC为加密模式,PKCS5Padding为填充方式(PKCS5Padding是PKCS7Padding在填充块大小为8个字节时的特殊情况,本质上是一样的)
    • 使用CBC模式,需要一个向量iv,可增加加密算法的强度
    • 一般在对内容加密时,需要先将内容进行编码,如Base64。因为,不是所有的字节数组都可以new String(),然后在通过String.getBytes()还原

JAVA 实现

  • java默认不支持PKCS7,如果非要指定PKCS7需要借助BouncyCastle类和安装扩展包

    • BouncyCastle

      1
      2
      3
      4
      5
      6
      7
      <!-- AES/CBC/PKCS7Padding 加解密 -->
      <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
      <dependency>
      <groupId>org.bouncycastle</groupId>
      <artifactId>bcprov-jdk15on</artifactId>
      <version>1.55</version>
      </dependency>
    • 安装扩展包

      • oracle官方下载(JDK1.8)
      • 下载之后得到local_policy.jarUS_export_policy.jar两个jar包,把这两个jar包放到jre/lib/security目录下替换原来的两个jar包即可
      • 如果是128位(16字节)则无需安装扩展包
  • 示例(基于jdk1.8测试)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import org.apache.tomcat.util.codec.binary.Base64;
import sun.misc.BASE64Decoder;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.AlgorithmParameterSpec;

public class AesU {
public static void main(String args[]) throws Exception {
System.out.println(encrypt("aezo.cn")); // U7fKj3r+hCydAkG20p0ZOw==
System.out.println(decrypt("U7fKj3r+hCydAkG20p0ZOw==")); // aezo.cn
System.out.println(decrypt("7gerc9kKbi7d7/rskLzq/H/+9Zb9lqa/XhiWkgeaThw=")); // aezo.cn
System.out.println(encrypt2("aezo.cn")); // 7gerc9kKbi7d7/rskLzq/H/+9Zb9lqa/XhiWkgeaThw=
System.out.println(decrypt2("U7fKj3r+hCydAkG20p0ZOw==")); // aezo.cn
}

private static final String CHARSET_NAME = "UTF-8";
private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; // "AES/CBC/PKCS7Padding"
private static final String KEY = "aabcw334^#^&#*^$W1233qwreqwr12 "; // 秘钥 32字节
private static final String IV = "abc8j*Ghg7!rNI84"; // 偏移 16字节

// static {
// // 使用"AES/CBC/PKCS7Padding"时需要开启
// Security.addProvider(new BouncyCastleProvider());
// }

/**
* 加密
* @param content
* @return
* @throws Exception
*/
public static String encrypt(String content) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(CHARSET_NAME), "AES");
AlgorithmParameterSpec paramSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, paramSpec);
byte[] result = cipher.doFinal(content.getBytes(CHARSET_NAME));
return Base64.encodeBase64String(result);
}

/**
* 解密
* @param content
* @return
* @throws Exception
*/
public static String decrypt(String content) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(CHARSET_NAME), "AES");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
return new String(cipher.doFinal(Base64.decodeBase64(content)), CHARSET_NAME);
}

/**
* AES加密+BASE64
* @param content
* @return
* @throws Exception
*/
public static String encrypt2(String content) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
int blockSize = cipher.getBlockSize();

byte[] dataBytes = content.getBytes();
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}

byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);

SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes());

cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plaintext);

return new sun.misc.BASE64Encoder().encode(encrypted);
}

/**
* 解密
* @param content
* @return
* @throws Exception
*/
public static String decrypt2(String content) throws Exception {
byte[] encrypted = new BASE64Decoder().decodeBuffer(content);

Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes());

cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

byte[] original = cipher.doFinal(encrypted);
return new String(original);
}
}

JS 实现

SHA1

1
2
3
4
5
6
7
8
9
10
11
public static String sha1(String str)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
if (null == str || str.length() == 0) {
return null;
}
MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
mdTemp.update(str.getBytes("UTF-8"));
byte[] md = mdTemp.digest();

return "{SHA}" + Utf8.decode(java.util.Base64.getEncoder().encode(md));
}

参考文章

ChatGPT开源小程序