JSON Web Encryption (JWE) の解説

ちょっと前に業務で `JWE` について調べ物をしたので、その際の資料を改稿して Qiita に放流します。

# JWE とは

`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)](https://tools.ietf.org/html/rfc7516)。

## シリアライゼーション形式

JWE には、`JWE Compact Serialization` と `JWE JSON Serialization` の二種類のシリアライズ形式が定義されています。

### JWE Compact 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)
```

それぞれのコンポーネントは、以下の役割を持ちます。

- **JWE Header**
    - JOSEヘッダ。JWEトークンを解釈するための情報が格納されているJSONオブジェクトの文字列。`alg`と`enc` の2キーが必須。
- **JWE Encrypted Key**
      - コンテンツの暗号化に使われた鍵(CEK)が、別の鍵で暗号化されたもの。
- **JWE Initialization Vector**
      - コンテンツの暗号化に使われた初期化ベクトル。
- **JWE Ciphertext**
    - 暗号化されたコンテンツ本体。
- **JWE Authentication Tag**
    - JWE トークンの完全性を保証する認証タグ。JWE トークンの改竄の有無を検証するために使われる。

### JWE JSON Serialization

一方の `JWE JSON Serialization` は、全体がひとつの JSON であるシリアライゼーション方式で、各コンポーネントが JSON のキー・バリューの形で表現されています。複数の `Recipient` (受信者)を指定でき、 Recipient ごとに異なるアルゴリズムや Encrypted Key を指定できます。
以下に `JWE Compact Serialization` の例を示します。改行は表示の都合です。

```json
{
  "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ヘッダ

`JOSEヘッダ` は、JWEトークンを適切に扱うためのメタ情報が格納された JavaScript オブジェクトです。 `JOSEヘッダ` は改竄に対して保護されます。`JOSEヘッダ` のうち、RFCで定義されたパラメータは `JWE Protected Header` と定義され、そのうち、`enc` と `alg` ヘッダは必須パラメータです。

以下に、代表的なヘッダパラメータを紹介します。

- 必須ヘッダ
    - **enc ヘッダ** (Encryption Algorithm)
        - **コンテンツを暗号するためのアルゴリズム** を指定します。[RFC 7518 JWAの5.1章で定義されたアルゴリズム識別子](https://tools.ietf.org/html/rfc7518#section-5.1) のほか、衝突防止プレフィックスをもつ未登録アルゴリズムを指定できます。アルゴリズムは、指定された長さのキーを使うAEAD(認証付き暗号化)アルゴリズムである必要がります。使用可能なアルゴリズムについては後述します。
    - **alg ヘッダ** (algorithm)
        - **コンテンツを暗号化した鍵(CEK)の管理方法** を指定します。使用可能なアルゴリズムは、[RFC 7518 - JSON Web Algorithms (JWA) section 4.1](https://tools.ietf.org/html/rfc7518#section-4.1) に定義されています。詳細は後述。
- 鍵を指定するヘッダ
    - **jku ヘッダ** (JWK Set URL)
        - JSONエンコードされた公開鍵のセット（そのうちひとつはJWEを暗号化した鍵に相当する）を参照するURI。JWK Set形式でエンコードされています。
    - **jwk ヘッダ** (JSON Web Key)
        - JWEを暗号化した鍵に対応する公開鍵。このキーはJSON Web Key[JWK]の形式で表現されます。
    - **kid ヘッダ** (Key ID)
        - JWEを暗号化するためにどの鍵が使用されたかを示すヒント情報。KIDの構造は定義されていない。JWKとともに使われた場合、JWK "kid" パラメータ値を照合するために用いられます。
    - **x5u ヘッダ** (X.509 URL)
        - JWEを暗号化した鍵に対応する、PEM形式の.509公開鍵証明書もしくは証明書チェーンリソースを参照するURI. JWEの複合に必要な秘密鍵を特定するために使うことができます。
    - **x5c ヘッダ** (X.509 Certificate Chain)
        - JWEを暗号化するために用いるX.509公開鍵証明書または証明書チェーン。証明書文字列の配列です。JWEの複合に必要な秘密鍵を特定するために使うことができます。
    - **x5t ヘッダ** (X.509 Certificate SHA-1 Thumbprint)
        - JWEを暗号化した鍵に相当するX.509のDERエンコードのSHA-1サムプリント(ダイジェスト)をbase64urlエンコードしたもの。JWEの復号に必要な秘密鍵を特定するために使うことができます。
    - **x5t#S256 ヘッダ**  (X.509 Certificate SHA-256 Thumbprint)
        - JWEを暗号化した鍵に相当するX.509のDERエンコードのSHA-256サムプリント(ダイジェスト)をbase64urlエンコードしたもの。JWEの復号に必要な秘密鍵を特定するために使うことができます。
- その他の Registered Header Parameters
    - **zip ヘッダ**  (Compression Algorithm)
        - コンテンツを暗号化する前に、zipで指定したアルゴリズムでPlaintextを圧縮できます。DEFLATE圧縮を示す `DEF` が使用できます。
    - **typ ヘッダ** (Type)
        - JWEオブジェクトの種類を示します。異なる種類のオブジェクトも扱う必要がある場合に、アプリケーションがJOSEオブジェクトを他のものと区別するために使うためのもの。
        - 使用可能な値
            - **JOSE**: JWS Compact Serialization を用いたJWSまたはJWE
            - **JOSE+JSON**: JWS JSON Serialization を用いたJWSまたはJWE
            - **その他の値**: アプリケーションはその他の値を使っても良い。（扱えない値は無視される）
    - **cty ヘッダ** (Content Type)
        - 保護されたコンテンツ（ペイロード）のタイプを示すために、アプリケーションにより使われるオプショナルヘッダ。IANA Media Typeの値をとります。JWS/JWEにペイロードが複数ある場合、種類を区別するために使うためのもの。
    - **crit ヘッダ**  (Critical)
        - JOSEヘッダのうち、受信者によって理解して処理されなければいけない拡張仕様および/またはJWAヘッダ名を列挙した配列。もしも列挙されたいずれかのヘッダが受信者によって理解され実行されなかった場合、そのJWS/JWEは不正です。

# JWE生成の手順

JWEの作成手順を図に示します。オレンジ色で示した **鍵**, **ヘッダパラメータ** と **Plaintext** が入力値で、下部の濃い灰色のボックスが出力されるJWEです。手順は大きく **①CEK生成** と **②コンテンツ暗号化** の２ステップで構成されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5235/6c59ad5f-c044-c826-cce4-c86d40811333.png)

## ① CEK生成

`alg` ヘッダパラメータによって指定されるアルゴリズムは、それぞれ対応する **鍵管理モード** を持ちますが、この鍵管理モードにより定義される方法で、コンテンツを暗号化する **コンテンツ暗号化キー(CEK)** が生成または用意されます。鍵管理モードとして、以下の5種類のモードが定義されています。

### **alg** パラメータ値と鍵管理モード

- **Direct Encryption**
    - 使用さているコンテンツ暗号化キーがパーティ間で共有されている秘密の対称的なキー値であるキー管理モード。共有されたCEKそのものが外部からパラメータとして与えられます。
    - 対応アルゴリズム
        - dir
- **Direct key Agreement**
    - 鍵共有(Key Agreement) アルゴリズムを使ってコンテンツ暗号化キーについて同意するキー管理モード。 
    - 対応アルゴリズム
        - ECDH-ES
            - Elliptic Curve Diffie-Hellman Ephemeral Static 鍵共有
- **Key Agreement with Key Wrapping**
    - 対称的な鍵ラップアルゴリズムを使ってコンテンツ暗号化キー値を暗号化するのに使われている対称的キーを共有するためにキーマネジメントアルゴリズムが使われているキー管理モード。
    - 対応アルゴリズム
        - ECDH-ES+A128KW, ECDS-ES+A192KW, ECDS-ES+A256KW
            - Concat KDFを使ったECDH-ES と 各キー長でのCEKラップ
- **Key Encryption**
    - コンテンツ暗号化キーの値が非対称鍵を使った鍵暗号化アルゴリズム（公開鍵暗号）を用いて暗号化されるキー管理モード。 
    - CEKを暗号化するための非対称鍵が外部からパラメータとして与えられます。
    - 対応アルゴリズム
        - RSA1_5
            - RSAES-PKCS1-v1_5
        - RSA-OAEP
            - デフォルトパラメータを使ったRSAES OAEP
        - RSA-OAEP-256
            - SHA-256 を使った RSAES OAEP と SHA256 による MGF1
- **Key Wrapping**
    - コンテンツ暗号化キーの値が対称鍵を使った鍵ラップアルゴリズム（共通鍵暗号）を用いて暗号化されるキー管理モード。 NOTE: 鍵ラップ：暗号鍵をカプセル化(暗号化)するためにデザインされた対称的暗号アルゴリズムの種類。Ref: [Key Wrap - Wikipedia](https://en.wikipedia.org/wiki/Key_Wrap)
    - CEKをラップするための対象鍵が外部からパラメータとして与えられる
    - 対応アルゴリズム
        - A128KW, A192KW, A256KW
            - 128, 192, 256ビットキーを使ったデフォルト初期値による AES 鍵ラップ
        - PBES2-HS256+A128KW, PBES2-HS284+A192KW, PBES2-HS512+A256KW
            - HMAC SHA-256, 284, 512 による PBES2 と A128KW, A192KW, A256KW 鍵ラップ
        - A128GCMKW, A192GCMKW, A256GCMKW
            - 128, 192, 256キーを使ったAES GCMによる鍵ラップ

各鍵管理モードでは、次の表な手順で鍵を生成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5235/5c3cf4da-48d7-d0b3-64cf-d8850b238e9d.png)


## ② コンテンツ暗号化

`enc` ヘッダパラメータにより指定されたアルゴリズム(下表)でコンテンツを暗号化します。

- **A128CBC-HS256**
    - AES_128_CBC_HMAC_SHA256 認証付き暗号化アルゴリズム.
- **A192CBC-HS384**
    - AES_192_CBC_HMAC_SHA384 認証付き暗号化アルゴリズム. 
- **A256CBC-HS512**
    - AES_256_CBC_HMAC_SHA512 認証付き暗号化アルゴリズム.
- **A128GCM**
    - 128ビットキーを使った AES GCM
- **A192GCM**
    - 192ビットキーを使った AES GCM
- **A256GCM**
    - 256ビットキーを使った AES GCM
 
[RFC 7518 - JSON Web Algorithms (JWA)](https://tools.ietf.org/html/rfc7518#section-5.1) より。

暗号処理の入力は **平文**, **コンテンツ暗号化キー**, **初期化ベクトル(Initial Vector)**, **追加暗号化データ(AAD)** で、出力は **Ciphertext** とAEADの**認証タグ(Authentication Tag)** です。

- 入力
    - **コンテンツ暗号化キー(CEK: Content Encryption Key):**
        - コンテンツを暗号化するためのキー
    - **初期化ベクトル(IV: Initial Vector):**
        - CBCモードで同じ鍵を使って同じ平文を暗号化すると、同じ暗号文が生成され、同じ内容であることがわかってしまう。これを防ぐため、暗号化ごとに用意されるユニークなバイト列のこと。暗号化した出力をランダム化するために用いられます。IV自体は秘匿すべき情報ではないため、JWEでは暗号化の際にランダムに生成され、最終的なJWEにそのまま表現されます。
    - **追加認証データ(AAD: Additional Authentication Data):**
        - JWEで指定可能な暗号化アルゴリズムはAEAD(認証付き暗号化アルゴリズム)なので、追加の認証データ(**AAD**)を使い、 **Authentication Tag** を生成することができます。このタグにより、JWEのAAD部とCiphertext部のどちらかが改竄されると復号時にエラーになり、改竄されたことがわかるため、JWEは完全性が得られます。`JWE Compact Serialization` では、AADとして`JWE Protected Header`がAADとして使われます。`JWE JSON Serialization` では、AAEとして任意のパラメータを指定できます。

## 3. JWE トークンを構築する
以下の5コンポーネントをピリオド'.'で連結し、JWEトークンの完成です。

1. JWE Protected HeaderをUTF-8エンコード+Base64Urlしたもの
2. ① で求めたJWE Encryption Key をBase64Urlしたもの
3. ② でランダムに生成したJWE Initial Vector を Base64Urlしたもの
4. ② で暗号化した出力であるJWE Ciphertext を Base64Urlしたもの
5. ② で暗号化した出力であるJWE Authentication Tag をBase64Urlしたもの

# 実装
GoでJWEを使用するライブラリには、[square/go-jose](https://github.com/square/go-jose/tree/v2) が挙げられます。go-jose.v2 を使ってJWEトークンを作成してみましょう。

```go
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への批判

JWE(またはJOSE)はセキュリティ上の批判に晒されていいます。特にParagonのエンジニアは、JWSでは多くの実装で攻撃が可能で、JWEは定義されている公開鍵暗号アルゴリズムは脆弱なものが多く、開発者が自分の足を撃つリスクが高いと主張しています。Fernetの方が優れており、さらにJOSEの問題を解決したPASETOを提案しています。

- [JOSEは、絶対に避けるべき悪い標準規格である | POSTD](https://postd.cc/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid/)
- [No Way, JOSE! Javascript Object Signing and Encryption is a Bad Standard That Everyone Should Avoid - Paragon Initiative Enterprises Blog](https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid)
- PKCS #1v1.5パティング付きRSA
    - "PKCS #1v1.5パディング付きRSAは、パディングオラクルと呼ばれる一種の選択暗号文攻撃に対して脆弱"
- OAEPパディング付きRSA
    - RSAを安全だとみなすなら安全だが、"セキュリティの専門家はRSAから移行するように勧めている"
- ECDH
    - JWT(JOSE?)では楕円曲線ディフィー・ヘルマンを使ったECDHのみ許されているが、この曲線は [Invalid-Curve攻撃 ](https://blogs.adobe.com/security/2017/03/critical-vulnerability-uncovered-in-json-encryption.html) の脆弱性があります。
AES-GCM
上記の公開鍵暗号モードは疑わしいので、事前に鍵を共有する共有鍵暗号モードをつかうべき（？）

# まとめ

いかがでしたか？コンテンツの暗号表現であるJavascript Web Encription の仕様、作成方法について解説しました。 PASETO や Branca などの代替規格を検討してもいいかもしれませんね。お疲れ様でした。