JSON Web Encryption (JWE) の解説 #JWT - Qiita
我之前在工作中研究了一下JWE
,现在将整理的资料修改后发布到 Qiita 上。
JSON Web Encryption(JWE)
是一种将加密数据与解密所需的元数据一起打包成 JSON 格式的数据表示形式。此外,它还具有通过 AEAD 提供的完整性(防篡改)保证功能。该规范已被 IETF 制定为 RFC 标准。
JWE 是 JSON Web Signature(JWS)用于签名和打包消息以确保防篡改性,以及用于在各方之间交换认证令牌的 JSON Web Token(JWT)等规范中的一部分。JWT
可以利用JWS
或JWE
。
RFC 7516 - JSON Web Encryption(JWE)。
JWE 定义了两种序列化形式,分别是JWE Compact Serialization
和JWE JSON Serialization
。
JWE Compact Serialization
是一种通过.(点)连接5个经过 BASE64URL 编码的组件的序列化方式。由于是 Web 安全的,因此可以在 URL 或 HTTP 头值中使用。
以下是JWE Compact Serialization
的示例。换行仅为了方便显示。
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ
.
AAG-Mxcoy3qHgYNZTGC8IzLhTkQOv7Iku5JI_gGm1Ev1GaqlbwdWT5x0rtvNSIxbvGOA
KFoTnPygByBcvXVkD0SnkPotkjXqKXocozG9zT9vaHdDUtth4ySKMs9huVXWqglZTkSA
QvZSpxSU6Gu0ZUB5yuUO2XZEeIwuRiG9F0hlAuQDokNvqWS69NfDxoiRN6Qp7Ud67LF8
FH75JA_eSuJsrW1JWWcB8bHQgrbB0WU-z5IVbyN7VPGNm1GGdENSh963iRTb0DlvADF-
KbU5yIHRomvJxEjOQDS2LYyswy6zAv7rrJeTO4dzgWYo7lcf07F19U8mGFfiimFXD5Lw
.
HtGmVwOnLfsVZmp1
.
cHUwotN3V05o4sHRhA
.
NGmNkNr5QyuVYGh93zknlA
上面的 JWE 令牌由以下组件组成。
base64url(JWE Header)
.
base64url(JWE Encrypted)
.
base64url(JWE Initialization Vector)
.
base64url(JWE Ciphertext)
.
base64url(JWE Authentication Tag)
每个组件的作用如下
alg
和enc
。JWE JSON序列化
是一种将整个数据表示为单个JSON对象的序列化方式,其中每个组件都以JSON键值对的形式表示。可以指定多个接收者(Recipient),并且可以为每个接收者指定不同的算法和加密密钥。
以下是JWE紧凑序列化
的示例。换行仅为了显示方便。
{
"protected": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ",
"encrypted_key": "KJpOWsV4gN-3tPaEoEd_Ae0UE6KI4gvWdt6Vuc_mOUmeWX2L9QYvi_CeCNZaiWoQoAT-pE4oiewGufT-7xgkILILvSoomrFD0xJlmWkJcGUoSZSUZr7nmOJa6V0XHbA--g3F1B35Jk-qUd7tgAxb9uDJKkfr96LTe1_Zt6ADUkaPwnB_0mqSqWXVl14W1LuwPYHrE9K4tPXtn6O3uoOAACLR8X_oGr5x8uVicgOBYpitDsB30k-0o-T6a8kgpD3MHF1iYYIKrZb-QEbeTYyT9wDbMsS4FsmRqKHu6kQ94ha4wFBEJ_fUvWvXnbX5WZ67zrCg8MtqEKAibUfRKv5Zeg",
"iv": "7ObH5owmUWQdldiQ",
"ciphertext": "DW2iCLUFevSLloS4mg",
"tag": "UoZ1Ljz_a7R4QhFoWwNRlQ"
}
由于这种格式并不常用,因此本文将基于JWE紧凑序列化
进行解释。
JOSE头
是一个JavaScript对象,其中包含了处理JWE令牌所需的元数据。JOSE头
受到篡改的保护。在RFC中定义的参数称为JWE保护头
,其中enc
和alg
头是必需的参数。
以下是一些代表性的头参数:
DEF
。下图展示了JWE的生成步骤。输入值为用橙色表示的密钥、头部参数和明文,输出为底部的深灰色方框中的JWE。该过程由两个步骤组成,即大致为①CEK生成和②内容加密。
image.png
由alg
头部参数指定的算法各自具有相应的密钥管理模式,但是通过该密钥管理模式定义的方式,将生成或准备好用于加密内容的**内容加密密钥(CEK)**。共定义了以下5种密钥管理模式。
在每种密钥管理模式下,按照下表的步骤生成密钥。
通过enc
标头参数指定的算法(见下表)对内容进行加密。
来源:RFC 7518 - JSON Web Algorithms (JWA)。
加密处理的输入包括明文、内容加密密钥、初始化向量(IV)、附加认证数据(AAD),输出为密文和AEAD的**认证标签(Authentication Tag)**。
JWE Compact Serialization
中,JWE Protected Header
被用作AAD。在JWE JSON Serialization
中,可以指定任意参数作为AAD。以下五个部分用句号". 来完成 JWE 令牌。
在Go语言中,可以使用square/go-jose库来处理JWE。让我们使用go-jose.v2来创建一个JWE令牌。
package main
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"log"
jose "gopkg.in/square/go-jose.v2"
)
func main() {
var plaintext = []byte("Lorem ipsum dolor sit amet")
fmt.Println("Plaintext:", string(plaintext))
//
// RSA-OAEP で使うためのRSA鍵を生成
//
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}
//
// enc: AES128-GCM, alg: RSA-OAEP で暗号化し、JWE Compact Serializationで出力
//
encrypter, err := jose.NewEncrypter(jose.A128GCM, jose.Recipient{
Algorithm: jose.RSA_OAEP,
Key: privateKey.PublicKey,
}, nil)
if err != nil {
log.Fatal(err)
}
object, err := encrypter.Encrypt(plaintext)
if err != nil {
log.Fatal(err)
}
token, err := object.CompactSerialize()
if err != nil {
log.Fatal(err)
}
fmt.Println("JWE Token:", token)
//
// 復号化
//
object, err = jose.ParseEncrypted(token)
if err != nil {
log.Fatal(err)
}
decrypted, err := object.Decrypt(privateKey)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Decrypted: %s\n", decrypted)
}
执行结果
Plaintext: Lorem ipsum dolor sit amet
JWE Token: eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.YOnRGwtYnd4C4w_OEtxlHhFzJT3qUH6APt7jDXTn3ln5yt-kS1RxM29uM3NEBnop5ZcQhtqNmZ90VeyN3AqsMGDp1KkEvHOb1O405Iwk1taaI5XZfGI6dxAhnH6YonbUBpQkqgHcNQhOYxgXuOkhecCUCyKKK-RcsukQSDNWB4rjf0QRwvcjtC4uAsnplsP2gwKNof4QgV_yMoVLBYFg6YVyDeYdIVSLIeZxO0SKIDbugF698Mufrj5gjsd_ydm-kvFlLO29Bti5eaDnOetlw1QzCzHa8fjUHoRyS6L5kcKjPqs6HzIxZv53OxrAsWq2IWdmnkMfBSXIMHCCIPeBVw.RZ9qri36pOoV_5C-.bQCU4nVQ9lrEH4EgvExzTjziJ8iWTzW7BKI.iNIOEhEt9eglEX6rGXxXpw
Decrypted: Lorem ipsum dolor sit amet
JWE(或JOSE)受到了安全性方面的批评。特别是Paragon的工程师指出,在许多实现中,JWS存在攻击可能性,而JWE中定义的公钥加密算法很多都是脆弱的,开发人员可能会自掘坟墓。他们认为Fernet更优秀,提出了解决JOSE问题的PASETO。
您觉得如何?我们讨论了Javascript Web Encryption的规范和创建方法。或许可以考虑使用PASETO或Branca等替代标准。辛苦了。
ちょっと前に業務で JWE
について調べ物をしたので、その際の資料を改稿して Qiita に放流します。
JSON Web Encryption (JWE)
とは、暗号化したデータを、復号に必要なメタデータとあわせて JSON 形式でパッケージするデータ表現形式です。また、 AEAD による完全性(改竄耐性)保証の機能も持ちます。仕様は IETF により RFC 化されています。
JWE は、メッセージを署名とパッケージして改竄耐性を保証する JSON Web Signature (JWS)
や、パーティ間で認証トークンを交換するための JSON Web Token (JWT)
などを含む Javascript Object Signing and Encryption (JOSE)
と呼ばれる規格群のひとつです。 JWT
は、 JWS
または JWE
を利用することができます。
RFC 7516 - JSON Web Encryption (JWE)。
JWE には、JWE Compact Serialization
と JWE JSON Serialization
の二種類のシリアライズ形式が定義されています。
JWE Compact Serialization
は、 BASE64URL エンコードされた5つのコンポーネントを、.(ピリオド)で結合するシリアライゼーション方式です。Web セーフなため、URL や HTTP ヘッダ値のなかで使用できます。
以下に JWE Compact Serialization
の例を示します。改行は表示の都合です。
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ
.
AAG-Mxcoy3qHgYNZTGC8IzLhTkQOv7Iku5JI_gGm1Ev1GaqlbwdWT5x0rtvNSIxbvGOA
KFoTnPygByBcvXVkD0SnkPotkjXqKXocozG9zT9vaHdDUtth4ySKMs9huVXWqglZTkSA
QvZSpxSU6Gu0ZUB5yuUO2XZEeIwuRiG9F0hlAuQDokNvqWS69NfDxoiRN6Qp7Ud67LF8
FH75JA_eSuJsrW1JWWcB8bHQgrbB0WU-z5IVbyN7VPGNm1GGdENSh963iRTb0DlvADF-
KbU5yIHRomvJxEjOQDS2LYyswy6zAv7rrJeTO4dzgWYo7lcf07F19U8mGFfiimFXD5Lw
.
HtGmVwOnLfsVZmp1
.
cHUwotN3V05o4sHRhA
.
NGmNkNr5QyuVYGh93zknlA
上の JWE トークンは、以下のようなコンポーネントで構成されています。
base64url(JWE Header)
.
base64url(JWE Encrypted)
.
base64url(JWE Initialization Vector)
.
base64url(JWE Ciphertext)
.
base64url(JWE Authentication Tag)
それぞれのコンポーネントは、以下の役割を持ちます。
alg
とenc
の2キーが必須。一方の JWE JSON Serialization
は、全体がひとつの JSON であるシリアライゼーション方式で、各コンポーネントが JSON のキー・バリューの形で表現されています。複数の Recipient
(受信者)を指定でき、 Recipient ごとに異なるアルゴリズムや Encrypted Key を指定できます。
以下に JWE Compact Serialization
の例を示します。改行は表示の都合です。
{
"protected": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ",
"encrypted_key": "KJpOWsV4gN-3tPaEoEd_Ae0UE6KI4gvWdt6Vuc_mOUmeWX2L9QYvi_CeCNZaiWoQoAT-pE4oiewGufT-7xgkILILvSoomrFD0xJlmWkJcGUoSZSUZr7nmOJa6V0XHbA--g3F1B35Jk-qUd7tgAxb9uDJKkfr96LTe1_Zt6ADUkaPwnB_0mqSqWXVl14W1LuwPYHrE9K4tPXtn6O3uoOAACLR8X_oGr5x8uVicgOBYpitDsB30k-0o-T6a8kgpD3MHF1iYYIKrZb-QEbeTYyT9wDbMsS4FsmRqKHu6kQ94ha4wFBEJ_fUvWvXnbX5WZ67zrCg8MtqEKAibUfRKv5Zeg",
"iv": "7ObH5owmUWQdldiQ",
"ciphertext": "DW2iCLUFevSLloS4mg",
"tag": "UoZ1Ljz_a7R4QhFoWwNRlQ"
}
この形式はあまり使われないため、本稿では JWE Compact Serialization
を前提に解説を進めます。
JOSEヘッダ
は、JWEトークンを適切に扱うためのメタ情報が格納された JavaScript オブジェクトです。 JOSEヘッダ
は改竄に対して保護されます。JOSEヘッダ
のうち、RFCで定義されたパラメータは JWE Protected Header
と定義され、そのうち、enc
と alg
ヘッダは必須パラメータです。
以下に、代表的なヘッダパラメータを紹介します。
DEF
が使用できます。JWEの作成手順を図に示します。オレンジ色で示した 鍵, ヘッダパラメータ と Plaintext が入力値で、下部の濃い灰色のボックスが出力されるJWEです。手順は大きく ①CEK生成 と ②コンテンツ暗号化 の2ステップで構成されます。
image.png
alg
ヘッダパラメータによって指定されるアルゴリズムは、それぞれ対応する 鍵管理モード を持ちますが、この鍵管理モードにより定義される方法で、コンテンツを暗号化する コンテンツ暗号化キー(CEK) が生成または用意されます。鍵管理モードとして、以下の5種類のモードが定義されています。
各鍵管理モードでは、次の表な手順で鍵を生成します。
enc
ヘッダパラメータにより指定されたアルゴリズム(下表)でコンテンツを暗号化します。
RFC 7518 - JSON Web Algorithms (JWA) より。
暗号処理の入力は 平文, コンテンツ暗号化キー, 初期化ベクトル(Initial Vector), 追加暗号化データ(AAD) で、出力は Ciphertext とAEADの認証タグ(Authentication Tag) です。
JWE Compact Serialization
では、AADとしてJWE Protected Header
がAADとして使われます。JWE JSON Serialization
では、AAEとして任意のパラメータを指定できます。以下の5コンポーネントをピリオド'.'で連結し、JWEトークンの完成です。
GoでJWEを使用するライブラリには、square/go-jose が挙げられます。go-jose.v2 を使ってJWEトークンを作成してみましょう。
package main
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"log"
jose "gopkg.in/square/go-jose.v2"
)
func main() {
var plaintext = []byte("Lorem ipsum dolor sit amet")
fmt.Println("Plaintext:", string(plaintext))
//
// RSA-OAEP で使うためのRSA鍵を生成
//
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}
//
// enc: AES128-GCM, alg: RSA-OAEP で暗号化し、JWE Compact Serializationで出力
//
encrypter, err := jose.NewEncrypter(jose.A128GCM, jose.Recipient{
Algorithm: jose.RSA_OAEP,
Key: privateKey.PublicKey,
}, nil)
if err != nil {
log.Fatal(err)
}
object, err := encrypter.Encrypt(plaintext)
if err != nil {
log.Fatal(err)
}
token, err := object.CompactSerialize()
if err != nil {
log.Fatal(err)
}
fmt.Println("JWE Token:", token)
//
// 復号化
//
object, err = jose.ParseEncrypted(token)
if err != nil {
log.Fatal(err)
}
decrypted, err := object.Decrypt(privateKey)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Decrypted: %s\n", decrypted)
}
実行結果
Plaintext: Lorem ipsum dolor sit amet
JWE Token: eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.YOnRGwtYnd4C4w_OEtxlHhFzJT3qUH6APt7jDXTn3ln5yt-kS1RxM29uM3NEBnop5ZcQhtqNmZ90VeyN3AqsMGDp1KkEvHOb1O405Iwk1taaI5XZfGI6dxAhnH6YonbUBpQkqgHcNQhOYxgXuOkhecCUCyKKK-RcsukQSDNWB4rjf0QRwvcjtC4uAsnplsP2gwKNof4QgV_yMoVLBYFg6YVyDeYdIVSLIeZxO0SKIDbugF698Mufrj5gjsd_ydm-kvFlLO29Bti5eaDnOetlw1QzCzHa8fjUHoRyS6L5kcKjPqs6HzIxZv53OxrAsWq2IWdmnkMfBSXIMHCCIPeBVw.RZ9qri36pOoV_5C-.bQCU4nVQ9lrEH4EgvExzTjziJ8iWTzW7BKI.iNIOEhEt9eglEX6rGXxXpw
Decrypted: Lorem ipsum dolor sit amet
JWE(またはJOSE)はセキュリティ上の批判に晒されていいます。特にParagonのエンジニアは、JWSでは多くの実装で攻撃が可能で、JWEは定義されている公開鍵暗号アルゴリズムは脆弱なものが多く、開発者が自分の足を撃つリスクが高いと主張しています。Fernetの方が優れており、さらにJOSEの問題を解決したPASETOを提案しています。
いかがでしたか?コンテンツの暗号表現であるJavascript Web Encription の仕様、作成方法について解説しました。PASETO や Branca などの代替規格を検討してもいいかもしれませんね。お疲れ様でした。
欢迎关注公众号