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 對象,允許爲同樣的內容生成多個簽名/消息認證碼。
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,且除第一行外,每一行開頭有一個空格,行尾無空格。
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