JSON Web Signature 规范解析

 

JWS 也就是 Json Web Signature,是构造 JWT 的基础结构(JWT 其实涵盖了 JWS 和 JWE 两类,其中 JWT 的载荷还可以是嵌套的 JWT),包括三部分 JOSE Header、JWS Payload、JWS Signature。

这里的 Signature 可以有两种生成方式,一种是标准的签名,使用非对称加密,因为私钥的保密性,能够确认签名的主体,同时能保护完整性;另一种是消息认证码 MAC(Message Authentication Code),使用对称秘钥,该秘钥需要在签发、验证的多个主体间共享,因此无法确认签发的主体,只能起到保护完整性的作用。

JWS 最终有两种序列化的表现形式,一种是 JWS Compact Serialization,为一串字符;另一种是 JWS JSON Serialization,是一个标准的 Json 对象,允许为同样的内容生成多个签名/消息认证码。
JSON Web Signature 规范解析

JWS Compact Serialization,各部分以 ‘.’ 分隔。

BASE64URL(UTF8(JWS Protected Header)) || ’.’ ||
BASE64URL(JWS Payload) || ’.’ ||
BASE64URL(JWS Signature)

JWS Json Serialization 还可以分为两种子格式:通用、扁平。

通用格式,最外层为 payload、signatures。signatures 中可以包含多个 json 对象,内层的 json 对象由 protected、header、signature 组成。不同的 protected header 生成不同的 Signature。

{
    "payload": "<payload contents>",
    "signatures": 
    [
        {
            "protected": "<integrity-protected header 1 contents>",
            "header": "<non-integrity-protected header 1 contents>",
            "signature": "<signature 1 contents>"
        },
        ...
        {
            "protected": "<integrity-protected header N contents>",
            "header": "<non-integrity-protected header 1 contents>",
            "signature": "<signature N contents>"
        }
    ]
}

扁平格式,就是为只有一个 signature/mac 准备的。

{
    "payload": "<payload contents>",
    "protected": "<integrity-protected header contents>",
    "header": "<non-integrity-protected header contents>",
    "signature": "<signature contents>"
}

JOSE Header:Json Object Signing and Encryption Header。描述加密行为及其他用到的参数,是 JWS Protected/Unprotected Header 的合集。

JWS Protected Header,有完整性保护的头参数。

JWS Unprotected Header,无完整性保护头参数,仅出现在 JWS Json Serialization 格式中。

以下列出了 JOSE Header 中的规定参数,各参数详细信息请参考 rfc7515

头参数 全称 解释 必选
alg algorithm 指定签名算法,为 none 时,表示不使用签名来保护完整性
jku JWK set URL 签名所用 key 对应公匙所在的 URI
jwk json web key 签名所用 key 对应的公匙
kid key id 签名所用 key 的 id
typ Type 指明整个 jws 的媒体类型,JOSE 意味着为compact,JOSE+JSON意味着为json
cty Content Type 载荷的媒体类型
crit Critical 此字段列出的扩展头参数必须被接收者理解并处理,否则该 jws 无效,该字段为数组格式
x5u X.509 URL
x5c X.509 Certificate Chain
x5t X.509 Certificate SHA-1 Thumbprint
x5t#S256 X.509 Certificate SHA-256 Thumbprint
# crit 参数例子
{
    "alg":"ES256",
    "crit":["exp","iss"],
    "exp":1363284000,
    "iss":"test"
}

以下为 rfc7515 中提供的一个案例,使用 HMAC_SHA256 生成 JWS Signature。

1. 头部就是一个紧凑的字符串,不换行,也无空格。

Header = {“typ”:“JWT”,“alg”:“HS256”}

base64url(Header) = eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9

import base64
header_encoded = base64.urlsafe_b64encode(b'{"typ":"JWT","alg":"HS256"}')
print(header_encoded)

2. 载荷是一个包括了换行和空格的 json 对象,换行取 win 系统的 CRLF,且除第一行外,每一行开头有一个空格,行尾无空格。

JSON Web Signature 规范解析

Payload = {“iss”:“joe”,\r\n “exp”:1300819380,\r\n “http://example.com/is_root”:true}

base64url(Payload) = eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ

import base64
Payload = {"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}
payload_encoded = (b'{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}')
print(payload_encoded)

3. 生成 Signature 时将头部和载荷视为一体

Message = ASCII(BASE64URL(UTF8(JWS Protected Header)) || ’.’ || BASE64URL(JWS Payload))

HMAC_SHA256 签名时需要用到对称密匙 Key,这里的 Key 是预先商量好的

Key = AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow

因为 Key 也是 base64url 编码后的内容,所以要获取 Key 的字节数组需要 base64url 解码一把,解码时由于 Key 的长度为 86,86%4=2,需要添加 ‘==’ 后再进行解码

# 也即解码时用的是这个
Key = AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow==

Signature = base64url( HMAC_SHA256(Message, Key) )

最后得到签名为 dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk=

import hashlib
import hmac
import base64

message = bytes('eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ','ascii')

secret = base64.urlsafe_b64decode('AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow==')

signature = base64.urlsafe_b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest())

print(signature)

去掉多余的 ‘=’ ,就是最终的 Signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

这样,最终的 JWS 就为(换行是为了方便看,实际就是一串)

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

当然,实际中可能会选用 RSA 之类的算法来进行签名,可参考 RFC 文档中的案例。

参考文档: rfc7515

 

上一篇:PHP开发APP接口简单签名全过程(二)实际测试


下一篇:python – 从函数签名中获取位置参数的名称