HTTP通道安全学习笔记
-
为什么HTTP通道不安全?
客户端(浏览器)向服务端发起请求,到服务端响应回客户端的过程中,请求数据和响应数据都可能被窃听、被篡改。
-
要做什么?
避免通信过程数据被窃听、篡改、冒充。
这是一个通过窃听、篡改或冒充的手段,达到非法盈利的案例,请搜索关键词:90后 薅羊毛 话费
方案的演化
以下演化过程为个人整理相关后的理解,过程中碰到了博客说不清的,查了很久资料才总结出来,但是仅供参考。
1 防止窃听
为了防止数据被窃听(被看到内容),可以对请求参数和响应结果进行加密。
由于客户端(浏览器)上运行的代码,是很容易可以看到源码的,所以如果使用对称加密,实际上用于加解密的密钥(yue,不是yao)是可以被人获得的,知道了密钥,就可以任意对数据进行加解密,从而伪造请求和响应结果,所以安全性很低。
那么我们可以使用非对称加密。非对称加密算法需要两个密钥:公开密钥
(publickey:简称公钥)和私有密钥
(privatekey:简称私钥)。公钥
与私钥
是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
总结非对称加密就是:公钥加密私钥解密、私钥加密公钥解密
由于使用非对称加密,私钥只要保存在服务端并不被泄露出去,那么黑客就很难解密客户端请求参数,即使读到了响应结果,也没办法伪造响应结果,从而提高了系统安全性。
2 防止数据被篡改
防止篡改的基本方式是通过“签名”来实现。即所有参数通过一种摘要算法(比如哈希)计算出一个摘要值,再用加密算法对摘要进行加密得到签名,只要黑客不知道加密的密钥,就不能伪造出这个签名,从而达到了防篡改的目的。
例如:
- 假设某一次HTTP请求过程中,请求参数集U有{a,b,c} 3个参数
- 客户端有一个黑客不知道的密钥 X
- 客户端通过摘要算法(比如对 a=1&b=2&c=3 计算hash code,得到 Y)
- 用黑客不知道的密钥 X 加密 Y 得到一个签名,这个签名代表着这次请求的业务参数,签名会一起作为请求参数传给服务端,供服务端验证
- 由于黑客不知道密钥,无法伪造签名,所以最多只能改a、b、c这3个参数,但是服务端通过相同算法,对a、b、c计算签名,得出的结果与原签名不匹配,就说明本次请求被第三方伪造了,请求就可以判定为无效。
3 验证服务端下发的公钥
由于请求是可以被劫持的,所以如果第三方劫持了客户端到服务端的通道,那么黑客就可以伪造公钥,从而劫持客户端所有请求。要解决这个问题,我们就要能够验证服务端的身份(形象的比喻就是把你的身份证给我看看)。
在HTTP通信中,我们使用证书机制来验证服务端的身份。
证书就可以看做是服务器的身份证,那么身份证的颁发机构就是CA机构(看作是公安局),CA机构的证书是收费的,当然也可以做一个自签的证书,比作商户的会员卡性质,只要客户端认你的会员卡就行。
有了证书后,我们的请求流程就成了这样:
CA证书机制是如何保证服务端身份确认的准确性?
- 首先生成证书之前,会先生成一对证书专用的公私钥对,公钥pub_key会放到证书里,私钥pri_key配在服务端,不对外公布
- 证书中保存了服务端的域名、证书颁发机构、证书有效期、公钥、签名(签名是通过 [第2点 防止数据被篡改](#2 防止数据被篡改)中描述的方法生成的。当然这些证书内的信息、签名应该会经过私钥pri_key加密,这里不作详细研究)等信息
- 客户端拿到证书后,会用公钥解密出证书内容,匹配当前要访问在站点域名是否一致、签名是否有效、正式是否过期等,这样就确认好了证书
- 此时还有一个问题存在,虽然证书是真的,但是我怎么知道证书就是你的(身份证上的人是你)?
- 有这样的问题是因为证书是公开的,任何人访问站点都可以获得这个证书,所以自然也会被恶意利用
- 第三方劫持通道后,可以那着真的证书来伪造你的请求内容,所以接下来就是如何确认证书和服务端的关系
- 客户端验证证书没问题后,会生成一个随机的码 A,A 通过摘要算法(如哈希)计算并用公钥pub_key加密后得到 B,客户端将A 和 B 同时发给服务端,服务端用私钥pri_key解密对比,再用私钥pri_key用类似的算法加密 A ,得到加密结果 C,将 C 响应回客户端,由客户端再作一遍验证这样就可以确认服务端的身份了(基于私钥没有泄露)
4 加解密方案的优化
达成一个共识:使用对称加密的效率要远高于非对称加密。
此时,我们验证了服务端的身份了,那么本次连接就已经是安全的了,我们此时的目的就是单纯的不让人看到明文内容就好了(我们已经面对面了,解决了被篡改和冒充的风险,现在只需要不让其他人听懂我们之间的谈话就行了),所以如果还用非对称加密(基于公私钥)那么效率就会比较低,我们可以选择用对称加密来提高局部的效率。
5 SSL的基本原理
SSL就是基于上述我自己推导的过程产生的,当前其中还有很多内容并没有提及,但是我认为初步掌握SSL所需的要点,在上文中都已经提及了,下面简单总结一下SSL的原理。
- 在确认随机码的时候数据用公钥加密,所以没有私钥是无法解密去伪造响应内容的;
- 所以随机码只有客户端和服务端之间才知道,以这样的机制,就可以防止第三方窃听、伪造、冒充了。
SpringBoot内置HTTPS配置
本方法适用于小型的SpringBoot单体项目,可以直接将SSL证书配置到SpringBoot内置的Tomcat上,从而开启SSL通信。但是不适用于带有负载均衡器或微服务集群乃至分布式的大型应用场景。
使用keystore工具,它是jdk自带的一项工具,在jdk/bin
下,keytool.exe
。
命令:
-certreq 生成证书请求
-changealias 更改条目的别名
-delete 删除条目
-exportcert 导出证书
-genkeypair 生成密钥对
-genseckey 生成密钥
-gencert 根据证书请求生成证书
-importcert 导入证书或证书链
-importpass 导入口令
-importkeystore 从其他密钥库导入一个或所有条目
-keypasswd 更改条目的密钥口令
-list 列出密钥库中的条目
-printcert 打印证书内容
-printcertreq 打印证书请求的内容
-printcrl 打印 CRL 文件的内容
-storepasswd 更改密钥库的存储口令
使用 "keytool -command_name -help" 获取 command_name 的用法
1 生成密钥对
-genkeypair命令选项:
-alias <alias> 要处理的条目的别名
-keyalg <keyalg> 密钥算法名称
-keysize <keysize> 密钥位大小
-sigalg <sigalg> 签名算法名称
-destalias <destalias> 目标别名
-dname <dname> 唯一判别名
-startdate <startdate> 证书有效期开始日期/时间
-ext <value> X.509 扩展
-validity <valDays> 有效天数
-keypass <arg> 密钥口令
-keystore <keystore> 密钥库名称
-storepass <arg> 密钥库口令
-storetype <storetype> 密钥库类型
-providername <providername> 提供方名称
-providerclass <providerclass> 提供方类名
-providerarg <arg> 提供方参数
-providerpath <pathlist> 提供方类路径
-v 详细输出
-protected 通过受保护的机制的口令
执行生成命令
keytool.exe -genkeypair -alias aliias -keypass 123456 -keyalg RSA -storetype PKCS12 -validity 365 -keystore D:\alias.keystore -storepass 123456 -ext san=dns:localhost
查看密钥对内容
keytool.exe -list -v -keystore D:\alias.keystore
keytool.exe -list -rfc -keystore D:\alias.keystore
2 导出证书
-exportcert选项:
-rfc 以 RFC 样式输出
-alias <alias> 要处理的条目的别名
-file <filename> 输出文件名
-keystore <keystore> 密钥库名称
-storepass <arg> 密钥库口令
-storetype <storetype> 密钥库类型
-providername <providername> 提供方名称
-providerclass <providerclass> 提供方类名
-providerarg <arg> 提供方参数
-providerpath <pathlist> 提供方类路径
-v 详细输出
-protected 通过受保护的机制的口令
执行导出证书命令
keytool.exe -exportcert -alias alias -keystore D:\alias.keystore -file D:\alias.cer
导出的这个证书在客户端(PC)上安装到“受信任的根证书颁发机构”下,关闭浏览器重新打开访问系统即可。
3 配置SpringBoot
以下为application.yml的相关配置
# 服务相关配置
server:
# 服务启动端口号
port: 443
ssl:
key-alias: alias
key-store: classpath:aliias.keystore
key-store-type: JKS
key-store-password: 123456
key-password: 123456
将aliias.keystore
拷贝到src/main/resources下,与application.yml同级目录中(或指定位置,需要修改server.ssl.key-store配置值)。
clean maven依赖后,可以运行测试,此时可以访问https://localhost,但https这部分还是处于红色警告标识(无法验证该证书的有效性),需要把alias.cer文件安装在客户端(PC)本地的“受信任的根证书颁发机构”下。安装后清除缓存重启浏览器,重新范围系统即可。
需要了解的是,cer证书我们需要通过前端触发,让用户进行下载安装,以此来信任站点,IE有ActiveX可以做,其他浏览器的话,目前还是通过js+用户手动安装实现(不然就花钱买CA机构的证书吧,一劳永逸)。
如果要允许用户使用http访问,那么我们需要配置http转https,这里不作详细记录。
基于Nginx的HTTPS配置
1 安装Openssl
通过该网站,下载可在Windows下运行安装的安装包 http://slproweb.com/products/Win32OpenSSL.html
安装后即可开始创建证书
注意,安装过程中,可以选择将可执行文件存放在安装目录内的bin目录下,这样方便找
打开命令窗口,进入安装目录下的bin目录中
2 创建证书
-
生成服务端私钥文件
openssl genrsa -des3 -out D:\server.key 1024
-
建立证书的申请文件root.csr
openssl req -new -key D:\server.key -out D:\server.csr
输入国家,省份,城市,公司信息,证书发送邮箱地址和证书密码(服务器端)
-
免密,放在Nginx中后,每次使用不用输密码
复制server.key,生成新的文件server.key.src,然后执行下面命令
openssl rsa -in D:\server.key.src -out D:\server.key
-
生成一个有效期1年的证书
openssl x509 -req -days 365 -signkey D:\server.key -in D:\server.csr -out D:\server.crt
3 配置Nginx
Nginx 的配置文件nginx.conf中,默认是带有ssl配置模板的,我们只要稍微改改就可以用了,具体Nginx需要如何配置,这里不作详细说明。
upstream app {
server 127.0.0.1:8094;
}
# HTTPS server
#
server {
listen 443 ssl;
server_name localhost;
ssl_certificate D://server.crt;
ssl_certificate_key D://server.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://app;
# root html;
# index index.html index.htm;
}
}
接口防篡改
主要用于开放API的时候(服务端对服务端,或者客户端对服务端),通常会对请求参数进行加密和签名。
签名过程通常可以有这样几个步骤:
- 请求参数打乱顺序;
- 请求参数组装成一个串,然后通过摘要算法加密,其中会用一个只有双方约定好的密钥加密,来保证双方都可以相互加解密,其次还会有一个调用方的身份标识,典型的例子就是appId和secret;
- 这样就保证了数据不容易被篡改,同时为了不被窃听,还可以把所有请求参数都加密。