# 機能設計書 45-暗号化機能

## 概要

本ドキュメントは、AES-256-GCMによる機密データの暗号化・復号化機能の設計について記載する。この機能は、OAuthトークンなどの機密情報をセキュアに保存・取得するための暗号化メカニズムを提供する。

### 本機能の処理概要

**業務上の目的・背景**：Liamアプリケーションは、GitHubとの連携のためにOAuthトークン（アクセストークン、リフレッシュトークン）を保存する必要がある。これらのトークンは機密性の高い情報であり、漏洩した場合にユーザーのGitHubリポジトリへの不正アクセスにつながる可能性がある。AES-256-GCM暗号化を使用することで、トークンの機密性と完全性を保護する。

**機能の利用シーン**：
- OAuthトークンをHttpOnly Cookieに保存する際の暗号化
- Cookieからトークンを読み取る際の復号化
- サーバーサイドでのみトークンを使用する際の安全な取り扱い

**主要な処理内容**：
1. AES-256-GCM暗号化による平文の暗号化
2. ランダムな初期化ベクトル（IV）の生成
3. 認証タグ（Authentication Tag）の生成
4. キーIDによる暗号化キーの識別
5. AES-256-GCM復号化による暗号文の復号

**関連システム・外部連携**：
- Node.js crypto モジュール
- @liam-hq/neverthrow（Result型エラーハンドリング）
- GitHub OAuth Cookie管理（No.42 OAuthフロー）

**権限による制御**：サーバーサイドでのみ実行可能。クライアントサイドでは復号化キーにアクセスできない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能は画面に直接関連しないセキュリティユーティリティ |

## 機能種別

セキュリティ処理 / 暗号化・復号化

## 入力仕様

### 入力パラメータ（encryptAesGcm）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| plaintext | string | Yes | 暗号化対象の平文 | 空文字可 |

### 入力パラメータ（decryptAesGcm）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| keyId | string | Yes | 復号化に使用するキーのID | キーリングに存在するID |
| ciphertext | Buffer | Yes | 暗号化されたデータ | - |
| initializationVector | Buffer | Yes | 初期化ベクトル（12バイト） | 12バイト |
| authenticationTag | Buffer | Yes | 認証タグ（16バイト） | 16バイト |

### 入力データソース

- 暗号化: アプリケーション内部からの平文データ（OAuthトークン等）
- 復号化: HttpOnly Cookieから取得した暗号化済みデータ

## 出力仕様

### 出力データ（encryptAesGcm）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| keyId | string | 暗号化に使用したキーのID |
| ciphertext | Buffer | 暗号化されたデータ |
| initializationVector | Buffer | 初期化ベクトル（12バイト） |
| authenticationTag | Buffer | 認証タグ（16バイト） |

### 出力データ（decryptAesGcm）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| plaintext | string | 復号化された平文（UTF-8） |

### 出力先

呼び出し元への戻り値として返却（Result型）

## 処理フロー

### 処理シーケンス

```
[暗号化フロー]
1. currentKey()でキーリングから現在のキーを取得
   └─ キーが存在しない場合はエラーを返却
2. crypto.randomBytes(12)で12バイトのIVを生成
3. crypto.createCipheriv('aes-256-gcm', key, iv)で暗号器を作成
4. cipher.update() + cipher.final()で暗号化を実行
5. cipher.getAuthTag()で認証タグを取得
6. CipherBundleオブジェクトを返却

[復号化フロー]
1. キーリングからkeyIdに対応するキーを検索
   └─ キーが存在しない場合はエラーを返却
2. crypto.createDecipheriv('aes-256-gcm', key, iv)で復号器を作成
3. decipher.setAuthTag(authenticationTag)で認証タグを設定
4. decipher.update() + decipher.final()で復号化を実行
5. 平文をUTF-8文字列として返却
```

### フローチャート

```mermaid
flowchart TD
    subgraph Encrypt["暗号化フロー"]
        E1[encryptAesGcm呼び出し] --> E2[currentKey取得]
        E2 --> E3{キー存在?}
        E3 -->|No| E4[エラー返却]
        E3 -->|Yes| E5[IV生成 12バイト]
        E5 --> E6[createCipheriv]
        E6 --> E7[暗号化実行]
        E7 --> E8[AuthTag取得]
        E8 --> E9[CipherBundle返却]
    end

    subgraph Decrypt["復号化フロー"]
        D1[decryptAesGcm呼び出し] --> D2[キーID検索]
        D2 --> D3{キー存在?}
        D3 -->|No| D4[エラー返却]
        D3 -->|Yes| D5[createDecipheriv]
        D5 --> D6[setAuthTag]
        D6 --> D7[復号化実行]
        D7 --> D8[平文返却]
    end
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-45-01 | 暗号化アルゴリズム | AES-256-GCMを使用 | 全ての暗号化・復号化 |
| BR-45-02 | IV長 | 初期化ベクトルは12バイト | 暗号化時 |
| BR-45-03 | 認証タグ長 | 認証タグは16バイト | 暗号化・復号化時 |
| BR-45-04 | キー長 | 暗号化キーは32バイト（256ビット） | キーリング登録時 |
| BR-45-05 | キーID必須 | 復号化にはキーIDが必要 | 復号化時 |
| BR-45-06 | ランダムIV | 暗号化ごとに新しいIVを生成 | セキュリティ要件 |

### 計算ロジック

**AES-256-GCM暗号化**：
```typescript
const iv = crypto.randomBytes(12)
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)
const ciphertext = Buffer.concat([
  cipher.update(plaintext, 'utf8'),
  cipher.final(),
])
const authenticationTag = cipher.getAuthTag()
```

**AES-256-GCM復号化**：
```typescript
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)
decipher.setAuthTag(authenticationTag)
const plaintext = Buffer.concat([
  decipher.update(ciphertext),
  decipher.final(),
])
return plaintext.toString('utf8')
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | 本機能はデータベースを直接操作しない |

### テーブル別操作詳細

本機能はメモリ内での暗号化・復号化処理であり、データベースを直接操作しない。
暗号化されたデータはHttpOnly Cookieに保存される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | No keys configured | キーリングが空 | 環境変数LIAM_GITHUB_OAUTH_KEYRINGを設定 |
| - | Unknown key id | 指定キーIDがキーリングに存在しない | 古いキーが削除された可能性。再暗号化が必要 |
| - | 復号化エラー | 認証タグ不一致・データ改ざん | データの整合性が破損している |

### リトライ仕様

暗号化・復号化処理は決定論的であり、リトライは不要。エラー発生時は即座にResult型でエラーを返却。

## トランザクション仕様

本機能はメモリ内処理のみであり、トランザクション管理は不要。

## パフォーマンス要件

- 暗号化・復号化: 即時（ミリ秒単位）
- Node.js cryptoモジュールはネイティブ実装であり高速

## セキュリティ考慮事項

- **AES-256-GCM**: 認証付き暗号化により機密性と完全性を保証
- **ランダムIV**: 暗号化ごとに12バイトのランダムIVを生成
- **認証タグ**: データ改ざん検知
- **キーローテーション**: キーリングによる複数キー管理（No.46参照）
- **環境変数管理**: 暗号化キーは環境変数LIAM_GITHUB_OAUTH_KEYRINGで管理
- **サーバーサイド専用**: クライアントサイドでは復号化不可

## 備考

- neverthrowのResult型を使用した関数型エラーハンドリング
- GCM（Galois/Counter Mode）は認証付き暗号化モード
- 初期化ベクトルは暗号化ごとに一意である必要がある

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、暗号化機能で使用される型定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | cryptoBox.ts | `frontend/internal-packages/security/src/cryptoBox.ts` | Key型とCipherBundle型の定義 |

**読解のコツ**:
- Key型（6行目）: `{ id: string; key: Buffer }` - キーIDと32バイトのキー本体
- CipherBundle型（40-45行目）: 暗号化結果を格納する構造体

#### Step 2: 暗号化関数を理解する

encryptAesGcm関数の実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | cryptoBox.ts | `frontend/internal-packages/security/src/cryptoBox.ts` | encryptAesGcm関数 |

**主要処理フロー**:
1. **50行目**: 関数シグネチャ `encryptAesGcm(plaintext: string): Result<CipherBundle, Error>`
2. **51行目**: currentKey()で現在のキーを取得
3. **53行目**: `crypto.randomBytes(12)` - 12バイトのランダムIV生成
4. **54行目**: `crypto.createCipheriv('aes-256-gcm', key, iv)` - 暗号器作成
5. **55-58行目**: Buffer.concatで暗号化実行
6. **59行目**: `cipher.getAuthTag()` - 認証タグ取得
7. **60-65行目**: CipherBundleオブジェクト構築と返却

#### Step 3: 復号化関数を理解する

decryptAesGcm関数の実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | cryptoBox.ts | `frontend/internal-packages/security/src/cryptoBox.ts` | decryptAesGcm関数 |

**主要処理フロー**:
- **73-78行目**: 関数シグネチャ（4つのパラメータ）
- **79-80行目**: キーリングからkeyIdでキーを検索
- **82行目**: fromThrowableでエラーをResult型に変換
- **83-86行目**: createDecipherivで復号器作成
- **88行目**: `setAuthTag` - 認証タグ設定
- **89行目**: 復号化実行とBuffer結合
- **90行目**: UTF-8文字列として返却

#### Step 4: キー管理を理解する

キーリングとcurrentKey関数を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cryptoBox.ts | `frontend/internal-packages/security/src/cryptoBox.ts` | キーリング管理 |

**主要処理フロー**:
- **12-26行目**: parseKeyring関数（環境変数からキーをパース）
- **29行目**: RING変数（キーリング配列）
- **31-33行目**: setKeyring関数（テスト用）
- **35-38行目**: currentKey関数（現在のキーを返却）

### プログラム呼び出し階層図

```
呼び出し元（cookie.ts等）
    │
    ├─ encryptAesGcm(plaintext)
    │      │
    │      ├─ currentKey()
    │      │      └─ RING[0]  ← キーリングの先頭キー
    │      │
    │      ├─ crypto.randomBytes(12)  ← IV生成
    │      │
    │      ├─ crypto.createCipheriv('aes-256-gcm', key, iv)
    │      │
    │      └─ cipher.update() + cipher.final() + getAuthTag()
    │
    └─ decryptAesGcm(keyId, ciphertext, iv, tag)
           │
           ├─ RING.find(k => k.id === keyId)  ← キーIDで検索
           │
           ├─ crypto.createDecipheriv('aes-256-gcm', key, iv)
           │
           └─ decipher.setAuthTag() + update() + final()
```

### データフロー図

```
[暗号化フロー]
[入力]                      [処理]                          [出力]

                     ┌─────────────────────────┐
plaintext ──────────▶│    currentKey()        │
                     │    (キーリング参照)     │
                     └──────────┬──────────────┘
                                │
                     ┌──────────▼──────────────┐
                     │  crypto.randomBytes(12) │
                     │       (IV生成)          │
                     └──────────┬──────────────┘
                                │
                     ┌──────────▼──────────────┐
                     │   createCipheriv        │──────────▶ CipherBundle
                     │   (AES-256-GCM暗号化)   │            { keyId, ciphertext,
                     └─────────────────────────┘              iv, authTag }

[復号化フロー]

                     ┌─────────────────────────┐
keyId ──────────────▶│   RING.find(keyId)     │
                     │   (キー検索)            │
                     └──────────┬──────────────┘
                                │
                     ┌──────────▼──────────────┐
ciphertext, iv, ────▶│   createDecipheriv     │──────────▶ plaintext
authTag              │   + setAuthTag          │            (UTF-8文字列)
                     │   (AES-256-GCM復号化)   │
                     └─────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| cryptoBox.ts | `frontend/internal-packages/security/src/cryptoBox.ts` | ソース | 暗号化・復号化のメイン実装 |
| cookie.ts | `frontend/apps/app/libs/github/cookie.ts` | ソース | 暗号化機能の利用箇所（Cookie管理） |
