什么是RSA
全称:由美国麻 省理工 学院三 位学者 Riv est、Sh amir 及Adleman 研 究发 展出 一套 可实 际使用 的公 开金 钥密码系 统,那 就是RSA(Rivest-Shamir-Adleman)密码系统。
RS256
RS256(带有SHA-256的 RSA 签名)是一种非对称算法,它使用公钥/私钥对:身份提供者拥有用于生成签名的私钥(秘密)密钥,而 JWT 的消费者获得公钥验证签名。由于与私钥相反,公钥不需要保持安全,因此大多数身份提供者都可以让消费者轻松获取和使用(通常通过元数据 URL)。
HS256
另一方面, HS256(带有 SHA-256 的HMAC)涉及散列函数和一个(秘密)密钥的组合,该密钥在两方之间共享,用于生成用作签名的散列。由于生成签名和验证签名都使用相同的密钥,因此必须注意确保密钥不被泄露。
1 SHA-256:
SHA
(Secure Hash Algorithm,安全散列算法)是一个密码散列函数家族,由美国国家安全局(NSA)设计,并由美国国家标准与技术研究院(NIST)发布,是美国的政府标准。包括 SHA-0
系列、SHA-1
系列、SHA-2
系列和SHA-3
系列。SHA-256
是SHA-2
系列函数之一
对于SHA-256
- 无论输入多长,都输出
64
个字符,共32
字节(byte),256
位(bit) - 输出只包含数字
0
~9
和字母A
~F
,大小写不敏感
2 RS-256和HS-256的区别
3 JWT 使用HS-256的加解密过程
加密:
首先jwt分为header.payload.signature三个部分构成,
- 当我们对header和payload进行填充后,会进行用base64url对他们分别进行编码,记为A和B,假设未经过base64url编码的header和payload分别如下所示:
header: {
"alg": "HS256",
"typ": "JWT"
}
header编码后变成了:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9,记位A
payload: {
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
payload编码后变成了:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9,记位B
2. 随后使用HS256加密算法对header和payload以及认证服务器本身拥有的密钥进行加密,其加密公式如下:
加密公式:HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret code ),
解释一下HMACSHA256,也即是SHA256加密算法,是一个总是输出256bit长的字符串的散列函数,
加密后,最终得到的结果是64位的字符串: IgfIWP_XtusfBW3ltGuDKdGk4xJZkOjmyoqkjkAkWSI, 记为C,这个C其实就是HMAC(Hash Based Message Authentication Code即基于hash函数的信息验证码)
3. 最后再将A, B, C拼接在一起
即A + B + C = base64UrlEncode(header) + base64UrlEncode(payload) + C = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.IgfIWP_XtusfBW3ltGuDKdGk4xJZkOjmyoqkjkAkWSI"
解密:
- 拿到jwt后,会对使用SHA256对header和payload(未被解码)并结合密钥进行加密,加密公式以上加密的第二个步骤的HMACSHA256一样,
- 如果得到的结果和signature函数一摸一样,则认证成功。
4 JWT 使用RS256的加解密过程
加密:
- 当认证服务器收到用户的登录信息后,将用户唯一标识符装进payload信息中,随后对header和payload进行填充后,会进行用base64url对他们分别进行编码,记为A和B,假设未经过base64url编码的header和payload分别如下所示:
- 然后再使用SHA-256对(A + "." + B) 这一字符串进行哈希处理,得到一个只有256bit的字符串C,
- 再使用RSA256对哈希结果C进行加密,加密过程使用到了私钥,加密形式为RSA256(C, private secret key), 得到一个最终的数字签名D,
- 最终将A,B, D拼接成A + "." + B + "." + D的形式发送给客户端
解密过程:
- 应用服务器会使用公钥对收到jwt中的signature签名解密得到一个hash码,
- 再使用SHA-256对收到jwt的header,payload进行一起哈希,公式为SHA-256(header + "." + payload), 其中的"."不能掉
- 将签名中解密得到的hash值和刚使用Header和Payload参与计算的哈希值进行比较,如果两个哈希值相等,则证明JWT确实是由认证服务器创建的。
5 为什么jwt中推荐使用RSA-256而不是HS-256
原因:
- 最重要的原因是使用RSA-256能简化公钥的分发过程
- RSA中加密的私钥只有认证服务器拥有,即使公钥丢失,黑客除了解密数据之外无法伪造数据
- 根据盗来的JWT Header和Payload生成SHA-256哈希值,之后他还要暴力破解RSA才能继续生成签名。
(还记得之前我们说过,用来校验token的公钥可以随意分发,黑客无法使用它来做任何有意义的事情。然而黑客并不是想校验token,他们只是想伪造它们。这就使得我们将公钥放置到受我们自己控制的服务器上成为可能,应用服务器连接到公钥放置的服务器获取公钥,然后定期检查公钥是否有变化。因此,在更新密钥时,应用服务器和认证服务器不需要同时暂停服务。)
6 jwt在企业中的应用
JWT同样适用于企业内部,替代经典的存在已知安全隐患的预身份验证设置(Pre-Authentication setup)方式。
预身份验证设置方式中,我们的应用服务器在私有网络的一个代理后面运行,然后从HTTP请求头中获取当前用户信息。代表用户身份的HTTP请求头通常由中心化的登录页面填充,同时中心化的节点也对用户session进行管理。
当session过期后,服务器将阻止对应用的访问,并要求用户重新登录认证。之后,它将所有请求转发到应用服务器并在HTTP请求头添加代表用户身份的信息。
问题是这种设置方式,内网上的任何人都可以假扮成某个用户,只要设置同样的HTTP请求头。
对此也有一些解决方案,比如白名单列表,或者某种客户凭证。
- 更好的预身份验证设置方式
预身份验证设置方式是一个好主意,毕竟这种方式可以使得应用开发者不需要实现认证逻辑,减少开发时间和潜在的安全问题。
如果能有预身份验证设置方式的便捷,又没有安全方面的妥协,岂不美哉?
如果我们考虑到JWT,则可以轻松做到。我们不像以往那样将用户名放到HTTP请求头,而是将HTTP请求头封装成一个JWT。我们将用户名放到Payload里面,再由认证服务器加签。
应用服务器不再从HTTP请求头获取用户名,而是首先校验JWT:
- 如果签名是正确的,则用户认证通过,请求可以放行;
- 否则,应用服务器简单的拒绝请求就好了;
这样的结果就是,即使在私有网络内,我们的认证功能也可以正常工作。我们再也不需要通过HTTP请求头来识别用户了,我们保证了HTTP请求头的有效性并且是由代理生成的,而不是某黑客试图以某个用户身份登录。