実務でJWSによって保護されたJWTを扱う機会があったのですが、あれから時間が経過して忘れてしまっていることが多いため、JWSについてまとめることにしました。
記事の内容は RFC7515 JSON Web Signature (JWS) を元にJWSの概要をまとめました。
この記事では以下の点についてまとめています。
- JWSは何か
- JWSの構成要素
- JWSの3つの形式
- JWSの作成/検証
- JWSとJWTの関係
JWSの概要
JWS(JSON Web Signature)は、デジタル署名やメッセージ認証コード(MAC)によってデータを保護し、改ざん検知を可能にする仕組みです。保護対象のデータはJSONオブジェクトである必要はなく、任意のデータとすることができます。JWSは文字列またはJSONオブジェクトとして表現されます。このうち、JWT(JSON Web Token)で使われる表現は文字列です。
用語説明
本記事を読む上で参考にしてください。
これらはRFC7515の 2. Terminologyで定義されています。
JWS Protected Header
デジタル署名やMACによって保護されるJSONオブジェクト。
データ保護に使用された暗号操作などに関する情報を含む。
JWSでは name/value のペアを Header Parameter という。
含むことのできる Header Parameter の詳細はRFC7515 4. JOSE Headerを参照。
JWS Unprotected Header
デジタル署名やMACによって保護されないJSONオブジェクト。
含むことのできる Header Parameter は JWS Protected Headerとほとんど変わらないが、
"alg" などセキュリティ上重要な Header Parameter を含めることは推奨されない。
JWS JSON Serialization形式のJWSでのみ使われる。
JWS Payload
データをエンコードして得られるオクテットシーケンス(8ビットのバイト列)。
デジタル署名やMACによって保護される。
データはJSONである必要はなく、任意のデータとすることが可能
JWS Signing Input
デジタル署名やMACを求めるために必要な値で、
以下を処理して得られるオクテットシーケンス。
ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload))
JWS Signature
JWS Protected HeaderとJWS Payloadに対するデジタル署名またはMACのこと。
JWSの構成要素
JWSは以下の要素の組み合わせで構成されます。
- JWS Protected Header
- JWS Unprotected Header
- JWS Payload
- JWS Signature
どれをどう組み合わせるかはJWSの形式によって異なります。
JWSの形式
形式は3つあります。
1. JWS Compact Serialization
JWSをASCII文字列として表現する方法。
コンパクトでURLに含めても安全なのが特徴
BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload) || '.' ||
BASE64URL(JWS Signature)
2. JWS JSON Serialization
JWSをJSONオブジェクトとして表現する方法。
複数のデジタル署名やMACでデータを保護できるが、コンパクトさやURL安全性が最適化されていない。JSONを構成する “protected” と “header” は必ずどちらか一つは存在しなければならない。
2つの構文がある
2.1. General JWS JSON Serialization Syntax
複数のデジタル署名やMACでデータを保護できる。より一般的な構文。
{
"payload":"<BASE64URL(JWS Payload)>",
"signatures":[
{"protected":"<BASE64URL(UTF8(JWS Protected Header))>",
"header":<JWS Unprotected Header>,
"signature":"<BASE64URL(JWS Signature)>"},
...
{"protected":"<BASE64URL(UTF8(JWS Protected Header))>",
"header":<JWS Unprotected Header>,
"signature":"<BASE64URL(JWS Signature)>"}]
}
2.2 Flattened JWS JSON Serialization Syntax
1つのデジタル署名またはMACでデータを保護する構文。
よりコンパクトな表現が可能
{
"payload":"<BASE64URL(JWS Payload)>",
"protected":"<BASE64URL(UTF8(JWS Protected Header))>",
"header":<JWS Unprotected Header>,
"signature":"<BASE64URL(JWS Signature)>"
}
次のセクションでそれぞれのJWSを作成します。
JWSの作成
ここではデータ保護方法として、署名スキーム RSASSA-PKCS1-v1_5 とハッシュ関数 SHA-256 を使った方法を取り上げますが、他の方法においても JWS Signature の作成/検証を除いて処理が共通しています。他の方法でJWSを作成するフローを確認したい方は Appendix A. JWS Examples を参照ください。
データは以下のJSONオブジェクトとし、署名スキーム RSASSA-PKCS1-v1_5 とハッシュ関数SHA-256を使ってデータを保護します。
{"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true}
必要なデータを準備する
3つの形式に共通で必要なデータは次の3つです。(JWS JSON Serialization 形式のJWSでは、 “kid” をJWS Unprotected Header に含めることが可能ですが、説明を簡単にするために JWS Protected Header に含めることにし、 JWS Unprotected Header は省略します)
- BASE64URL(UTF8(JWS Protected Header))
- BASE64URL(JWS Payload)
- BASE64URL(JWS Signature)
BASE64URL(UTF8(JWS Protected Header)) を作成する
JWT Protected Header は以下のJSONオブジェクトです。
”alg” は必須要素で、どのような方法で保護するかを表します。
”kid” はデジタル署名の検証に必要な公開鍵を特定するための値です。
{"alg":"RS256",
"kid":"1dbe06b5d7c2a7c044563061ff0fea37740b86bc"}
このJSONの文字列表現をUTF-8エンコードしてオクテットシーケンスに変換します。
(見やすさのために途中で改行を入れています)
[123, 34, 116, 121, 112, 34, 58, 34, 74, 87, 84, 34, 44, 13, 10, 32,
34, 97, 108, 103, 34, 58, 34, 82, 83, 50, 53, 54, 34, 44, 13, 10, 32,
34, 107, 105, 100, 34, 58, 34, 49, 100, 98, 101, 48, 54, 98, 53, 100,
55, 99, 50, 97, 55, 99, 48, 52, 52, 53, 54, 51, 48, 54, 49, 102, 102,
48, 102, 101, 97, 51, 55, 55, 52, 48, 98, 56, 54, 98, 99, 34, 125]
このオクテットシーケンスを base64url エンコードして文字列に変換します。
以降、これを header として参照します。
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJSUzI1NiIsDQogImtpZCI6IjFkYmUwNmI1ZDdjMmE3YzA0NDU2MzA2MWZmMGZlYTM3NzQwYjg2YmMifQ
BASE64URL(JWS Payload) を作成する
JWS Payload はデータをエンコードして得られるオクテットシーケンスです。今回はデータがJSONなのでUTF-8でエンコードします。(途中で改行を入れています)
[123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32,
34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44,
13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112,
108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58,
116, 114, 117, 101, 125]
このJWS Payloadを base64url エンコードします。
以降、これを payload として参照します。
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
BASE64URL(JWS Signature) を作成する
JWS Signature を作成するために、まず JWS Signing Input を求めます。
JWS Signing Input は header と payload をドット(”.”)で結合した文字列をASCIIエンコードして得られるオクテットシーケンスです。(途中で改行を入れています)
[101, 121, 74, 48, 101, 88, 65, 105, 79, 105, 74, 75, 86, 49, 81, 105, 76,
65, 48, 75, 73, 67, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 122, 73,
49, 78, 105, 73, 115, 68, 81, 111, 103, 73, 109, 116, 112, 90, 67, 73, 54,
73, 106, 70, 107, 89, 109, 85, 119, 78, 109, 73, 49, 90, 68, 100, 106, 77,
109, 69, 51, 89, 122, 65, 48, 78, 68, 85, 50, 77, 122, 65, 50, 77, 87, 90,
109, 77, 71, 90, 108, 89, 84, 77, 51, 78, 122, 81, 119, 89, 106, 103, 50,
89, 109, 77, 105, 102, 81, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105,
74, 113, 98, 50, 85, 105, 76, 65, 48, 75, 73, 67, 74, 108, 101, 72, 65, 105,
79, 106, 69, 122, 77, 68, 65, 52, 77, 84, 107, 122, 79, 68, 65, 115, 68, 81,
111, 103, 73, 109, 104, 48, 100, 72, 65, 54, 76, 121, 57, 108, 101, 71, 70,
116, 99, 71, 120, 108, 76, 109, 78, 118, 98, 83, 57, 112, 99, 49, 57, 121,
98, 50, 57, 48, 73, 106, 112, 48, 99, 110, 86, 108, 102, 81]
署名で使う秘密鍵は、RFC7515 A.2. Example JWS Using RSASSA-PKCS1-v1_5 SHA-256 のJWK(JSON Web Key)形式で表現されたRSA鍵を使用します。
鍵の内容(長いので折りたたみます)
{"kty":"RSA",
"n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx
HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs
D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH
SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV
MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8
NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ",
"e":"AQAB",
"d":"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I
jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0
BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn
439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT
CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh
BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ",
"p":"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi
YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG
BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc",
"q":"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa
ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA
-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc",
"dp":"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q
CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb
34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0",
"dq":"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa
7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky
NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU",
"qi":"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o
y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU
W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U"
}
このJWKから秘密鍵を作成し、RSA署名関数に秘密鍵、SHA-256、JWS Signing Inputを与えると、以下の JWS Signature が得られます。(途中で改行を入れています)
[119, 64, 125, 226, 172, 102, 132, 43, 208, 154, 241, 32, 52, 150, 161, 85,
14, 183, 119, 57, 18, 101, 9, 68, 227, 163, 109, 166, 186, 224, 105, 164,
191, 170, 251, 192, 123, 155, 238, 185, 215, 177, 120, 118, 23, 41, 224,
100, 145, 49, 238, 60, 53, 73, 109, 174, 73, 119, 0, 148, 176, 164, 55, 33,
181, 187, 0, 52, 194, 213, 102, 220, 132, 186, 99, 72, 231, 103, 62, 80,
191, 77, 64, 100, 91, 242, 229, 205, 111, 8, 62, 3, 92, 1, 175, 67, 122,
126, 150, 247, 35, 138, 211, 37, 216, 144, 71, 24, 225, 245, 7, 38, 249,
138, 199, 63, 40, 246, 137, 196, 1, 181, 129, 126, 41, 179, 234, 76, 102,
217, 14, 28, 118, 241, 218, 193, 29, 49, 156, 234, 242, 167, 17, 8, 176,
166, 173, 55, 211, 250, 212, 168, 9, 202, 145, 52, 245, 60, 118, 9, 184,
246, 55, 40, 74, 149, 76, 138, 36, 196, 144, 249, 21, 54, 18, 64, 221, 195,
246, 140, 239, 42, 235, 105, 255, 196, 7, 189, 146, 194, 183, 75, 91, 50, 4,
241, 100, 206, 239, 221, 19, 64, 240, 153, 143, 153, 13, 109, 159, 85, 30,
230, 61, 67, 127, 45, 180, 58, 182, 34, 178, 109, 179, 129, 105, 173, 106,
203, 69, 215, 108, 215, 8, 90, 135, 197, 12, 111, 70, 175, 57, 27, 187, 206,
4, 185, 162, 235, 193, 84, 177, 243, 25, 42, 184, 86]
このJWS Signatureを base64url エンコードします。
以降、これを signature として参照します。
d0B94qxmhCvQmvEgNJahVQ63dzkSZQlE46NtprrgaaS_qvvAe5vuudexeHYXKeBkkTHuPDVJba5JdwCUsKQ3IbW7ADTC1WbchLpjSOdnPlC_TUBkW_LlzW8IPgNcAa9Den6W9yOK0yXYkEcY4fUHJvmKxz8o9onEAbWBfimz6kxm2Q4cdvHawR0xnOrypxEIsKatN9P61KgJypE09Tx2Cbj2NyhKlUyKJMSQ-RU2EkDdw_aM7yrraf_EB72SwrdLWzIE8WTO790TQPCZj5kNbZ9VHuY9Q38ttDq2IrJts4FprWrLRdds1whah8UMb0avORu7zgS5ouvBVLHzGSq4Vg
準備した header, payload, signature から、3つの形式のJWSを作成します。
JWS Compact Serialization
準備した header, payload, signature を先頭から順番にドット(’.’)で結合すると
JWS Compact Serialization 形式のJWSの完成です。(途中で改行を入れています)
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJSUzI1NiIsDQogImtpZCI6IjFkYmUwNmI1ZDdjMmE3YzA0NDU2MzA2MWZmMGZlYTM3NzQwYjg2YmMifQ
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
d0B94qxmhCvQmvEgNJahVQ63dzkSZQlE46NtprrgaaS_qvvAe5vuudexeHYXKeBkkTHuPDVJba5JdwCUsKQ3IbW7ADTC1WbchLpjSOdnPlC_TUBkW_LlzW8IPgNcAa9Den6W9yOK0yXYkEcY4fUHJvmKxz8o9onEAbWBfimz6kxm2Q4cdvHawR0xnOrypxEIsKatN9P61KgJypE09Tx2Cbj2NyhKlUyKJMSQ-RU2EkDdw_aM7yrraf_EB72SwrdLWzIE8WTO790TQPCZj5kNbZ9VHuY9Q38ttDq2IrJts4FprWrLRdds1whah8UMb0avORu7zgS5ouvBVLHzGSq4Vg
Flattened JWS JSON Serialization Syntax
JSONオブジェクトの protected, payload, signature 要素の値として、それぞれ header, payload, signature を使うことで完成です。
{
"payload":"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
"protected":"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJSUzI1NiIsDQogImtpZCI6IjFkYmUwNmI1ZDdjMmE3YzA0NDU2MzA2MWZmMGZlYTM3NzQwYjg2YmMifQ",
"signature":"d0B94qxmhCvQmvEgNJahVQ63dzkSZQlE46NtprrgaaS_qvvAe5vuudexeHYXKeBkkTHuPDVJba5JdwCUsKQ3IbW7ADTC1WbchLpjSOdnPlC_TUBkW_LlzW8IPgNcAa9Den6W9yOK0yXYkEcY4fUHJvmKxz8o9onEAbWBfimz6kxm2Q4cdvHawR0xnOrypxEIsKatN9P61KgJypE09Tx2Cbj2NyhKlUyKJMSQ-RU2EkDdw_aM7yrraf_EB72SwrdLWzIE8WTO790TQPCZj5kNbZ9VHuY9Q38ttDq2IrJts4FprWrLRdds1whah8UMb0avORu7zgS5ouvBVLHzGSq4Vg"
}
General JWS JSON Serialization Syntax
今回は一つのデジタル署名でデータを保護しているので以下のようにして完成です。デジタル署名やMACが複数存在する場合は、それぞれに対応するJSONオブジェクトを signatures 要素に追加します。
{
"payload":"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
"signatures":[
{
"protected":"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJSUzI1NiIsDQogImtpZCI6IjFkYmUwNmI1ZDdjMmE3YzA0NDU2MzA2MWZmMGZlYTM3NzQwYjg2YmMifQ",
"signature":"d0B94qxmhCvQmvEgNJahVQ63dzkSZQlE46NtprrgaaS_qvvAe5vuudexeHYXKeBkkTHuPDVJba5JdwCUsKQ3IbW7ADTC1WbchLpjSOdnPlC_TUBkW_LlzW8IPgNcAa9Den6W9yOK0yXYkEcY4fUHJvmKxz8o9onEAbWBfimz6kxm2Q4cdvHawR0xnOrypxEIsKatN9P61KgJypE09Tx2Cbj2NyhKlUyKJMSQ-RU2EkDdw_aM7yrraf_EB72SwrdLWzIE8WTO790TQPCZj5kNbZ9VHuY9Q38ttDq2IrJts4FprWrLRdds1whah8UMb0avORu7zgS5ouvBVLHzGSq4Vg"
}
]
}
JWSの検証
signature の検証
RS256で保護された場合、デジタル署名の検証は、SHA-256ハッシュ関数を使用するように設定された署名検証機RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)
に以下のデータを渡して行います。
- M:JWS Signing Input (前の例と同じため省略)
- S:JWS Signature(前の例と同じため省略)
- (n, e):公開鍵
公開鍵はweb上のエンドポイントから取得できます。公開鍵は証明書やJWKの形式で提供されることがあります。例として、firebase は証明書、facebookはJWKを提供します。
先ほどのJWKの n と e から公開鍵を作成すると以下の値が得られます。
(途中で改行を入れています)
"-----BEGIN PUBLIC KEY-----\n
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAofgWCuLjybRlzo0tZWJj\n
NiuSfb4p4fAkd/wWJcyQoTbji9k0l8W26mPddxHmfHQp+Vaw+4qPCJrcS2mJPMEz\n
P1Pt0Bm4d4QlL+yRT+SFd2lZS+pCgNMsD1W/YpRPEwOWvG6b32690r2jZ47soMZo\n
9wGzjb/7OMg0LOL+bSf63kpaSHSXndS5z5rexMdbBYUsLA9e+KXBdQOS+UTo7WTB\n
EMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6/I5IhlJH7aGhyxX\n
FvUK+DWNmoudF8NAco9/h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXp\n
oQIDAQAB\n-----END PUBLIC KEY-----\n"
署名の検証にはライブラリを使うことが多いと思います。署名・検証に利用できるライブラリはJWT公式ページにまとまっています。
具体的な署名検証フローが知りたい人は RFC3447 8.2.2 Signature verification operation を確認ください。(時間がある時にまとめるかも)
signature が複数ある場合について
RFC7515 5.2. Message Signature or MAC Validationによれば、すべての signature の検証に失敗した場合はJWSを無効として扱うが、その全ての検証に成功するべきなのか、そのうちのどの検証に成功するべきなのかの判断はアプリケーションに委ねるとしています。
他の検証項目について
RFC7515 5.2. Message Signature or MAC Validation では signature の検証以外にも検証項目が列挙されていますが、ライブラリを使う場合は気にしなくて良さそうなため割愛します。
JWT と JWS の関係
RFC7519 1. Introduction では、JWTは以下のように説明されています。
JWTs encode claims to be transmitted as a JSON [RFC7159] object that is used
as the payload of a JSON Web Signature (JWS) [JWS] structure or
as the plaintext of a JSON Web Encryption (JWE) [JWE] structure,
enabling the claims to be digitally signed or integrity protected with a
Message Authentication Code (MAC) and/or encrypted. JWTs are always represented
using the JWS Compact Serialization or the JWE Compact Serialization.
このことから、以下の条件をすべて満たすJWSはJWTであると言えます。
- JWSの形式が JWS Compact Serialization
- payload に含まれるデータがJSONオブジェクト(このJSONオブジェクトをJWTではJWT Claims Set といいます)