暗号における鍵管理について

# 鍵管理とは

例えば、玄関の鍵に高度なセキュリティを採用したとします。
しかしながら、その「鍵」をずさんな管理していたら、セキュリティーの意味が無いわけです。
そこで「暗号メッセージ構文」[RFC.2630]（旧PKCS#7）では、「鍵管理手法」(Key Management Algorithms)が定められています。

本記事では、暗号メッセージ構文(CMS)における、封書データ(Enveloped-data Content)で用いられる鍵管理手法について記載します。。

<備考>
- データタイプ(Data Content Type)
- 電子署名データタイプ(Signed-data Content Type)
- 封書データタイプ(Enveloped-data Content Type) ← これに関する記述です。 
- ダイジェストデータタイプ(Digested-data Content Type)
- 暗号化データタイプ(Encrypted-data Content Type)
- 資格認証データタイプ(Authenticated-data Content Type)


## ■鍵管理手法を用いた暗号／複合の図解

### 暗号のプロセス
![enc.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663244/ac4eb891-7946-7cc4-c511-17ff6dd60ded.jpeg)


### 復号のプロセス

![dec.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663244/2350910a-1a51-faaf-7118-faf0e256605f.jpeg)

## ■鍵管理手法の種類

### 鍵合意 (Key Agreement Algorithms)

暗号化したメッセージを送りたい時は、
1) 送信者と受信者が互いに秘密鍵と公開鍵を用意し、互いに公開鍵のみを交換する。
2) 送信者は、自分の秘密鍵と相手の公開鍵で暗号化し、暗号文を送る。
3) 受信者は、自分の秘密鍵で複合する。
秘密鍵は互いに秘密とする。
秘密鍵を知っている者でないと複合できないので、秘密鍵が漏洩しない限りは、第３者に盗聴されるリスクは無いという思想。

#### [アルゴリズム]：
 - X9.42 Ephemeral-Static Diffie-Hellman
 - X9.42 Static-Static Diffie-Hellman

### 鍵配送 (Key Transport Algorithms)

送信者が受信者に暗号化したメッセージを送りたい時は、
1) 受信者は秘密鍵と公開鍵のペアを作り、送信者に公開鍵を送る。
2) 送信者は、受信者の公開鍵でメッセージを暗号化し、暗号文を送る。
3) 受信者は、自身の秘密鍵で暗号文を複合する。
秘密鍵が漏洩しない限りは、第３者に盗聴されるリスクは無いという思想。

#### [アルゴリズム]：
 - RSA (PKCS #1 v1.5)

### 共通鍵暗号 (Symmetric Key-Encryption Key Algorithms)

事前に暗号文の通信とは別の方法で互いに共有した共通鍵で暗号文をやり取りする方法。
暗号文と同じ経路で、暗号鍵をやり取りしてはいけない。

#### [アルゴリズム]：
 - Triple-DES Key Wrap
 - RC2 Key Wrap

### 鍵導出 (Key Derivation Algorithms)

事前に暗号文の通信とは別の方法で互いに共有したパスワードを用いて、暗号文をやりとりする方法。
暗号文と同じ経路で、パスワードをやり取りしてはいけない。

#### [アルゴリズム]：
- PBKDF2

#### [パスワードを用いた暗号のプロセス]
![password_enc.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663244/bc92b51a-0f41-2121-3b66-95f3dbf9e0f1.jpeg)

##### [鍵の生成例]

以下表の条件で、計算した場合を示す。

| 項目 | 値 |
|----|----|
| コンテンツ暗号鍵(CEK) | 8C 62 7C 89 73 23 A2 F8 (乱数から生成された) |
| パスワード | "password" |
| 鍵導出関数 | PBKDF2 with HMAC-SHA-1 |
| ソルト | 12 34 56 78 78 56 34 12 (乱数から生成された)  |
| 繰り返し回数(count) | 5 |
| 鍵暗号化アルゴリズム | id-alg-PWRI-KEK |
| 暗号関数 | DES暗号, 暗号利用モード=CBC |
| 初期化ベクタ | EF E5 98 EF 21 B3 3D 6D (乱数から生成された)  |

==== 鍵導出 ====

まず、鍵暗号鍵(KEK)を計算する。

``` C
password = "password";
salt = {0x12, 0x34, 0x56, 0x78, 0x78, 0x56, 0x34, 0x12};  // 通常は乱数
count = 5;  // 推奨される回数は1000回だが、参考例として5回。
dklen = 8;  // 使う暗号関数の鍵長(今回はDES-CBCなので、8)
KEK = PBKDF2_with_HMAC-SHA-1(password, salt, count, dklen);
    = {0xD1, 0xDA, 0xA7, 0x86, 0x15, 0xF2, 0x87, 0xE6};
```

==== コンテンツ暗号鍵(CEK)の暗号化 ====

コンテンツ暗号鍵(CEK) を成型する。
暗号関数にDES-CBCを使うので、ブロック長は8[Byte]となる。
従い、整形後のデータは8の倍数のサイズにならなければならない。
「長さ[1Byte] + チェック[3Bytes] + 暗号鍵 + パディング」の並びとするので、

|項目|バイト列|
|----|----|
| CEKの長さ | 08 |
| チェック(CEK先頭3Bytesの補数) | 73 9D 83 |
| コンテンツ暗号鍵(CEK) | 8C 62 7C 89 73 23 A2 F8 |
| パディング (ランダムに) | C4 36 F5 41 |

つまり、整形後のデータは、`08 73 9D 83 8C 62 7C 89 73 23 A2 F8 C4 36 F5 41`の16Byteのデータとなる。
この16Byteのバイト列を、暗号関数で指定した暗号関数で、2回暗号化する。

``` C
DATA = {0x08, 0x73, 0x9D, 0x83, 0x8C, 0x62, 0x7C, 0x89, 0x73, 0x23, 0xA2, 0xF8, 0xC4, 0x36, 0xF5, 0x41}
KEK  = {0xD1, 0xDA, 0xA7, 0x86, 0x15, 0xF2, 0x87, 0xE6};  // PBKDF2()関数で求めた値
IV   = {0xEF, 0xE5, 0x98, 0xEF, 0x21, 0xB3, 0x3D, 0x6D};  // 通常は乱数

DES-CBC.SetKey(KEK);   //暗号鍵の設定
DES-CBC.SetIV(IV);     //初期化ヴェクタの設定
E1 = DES-CBC(DATA);  // 1回目
E1 = {0x06, 0xA0, 0x43, 0x86, 0x1E, 0x82, 0x88, 0xE4, 0x8B, 0x59, 0x9E, 0xB9, 0x76, 0x10, 0x00, 0xD4};
Encrypted_Key = DES-CBC(E1);  // 2回目 (※初期化ヴェクタIVはリセットしない)
Encrypted_Key = {0xB8, 0x1B, 0x25, 0x65, 0xEE, 0x37, 0x3C, 0xA6, 0xDE, 0xDC, 0xA2, 0x6A, 0x17, 0x8B, 0x0C, 0x10};
```

受取人情報 （RecipientInfo）は以下の構造となる。
※PBKDF2は、使うハッシュ関数の指定を省略した場合、`HMAC-SHA-1`を用いる。

``` 
    0 A3   68: [3] {
    2 02    1:   INTEGER 0
    5 A0   26:   [0] {
    7 06    9:     OBJECT IDENTIFIER id-PBKDF2 (1 2 840 113549 1 5 12)
   18 30   13:     SEQUENCE {
   20 04    8:       OCTET STRING
             :         12 34 56 78 78 56 34 12　　… salt
   30 02    1:       INTEGER 5　　… 繰り返し回数
             :       }
             :     }
   34 30   32:   SEQUENCE {
   36 06   11:     OBJECT IDENTIFIER id-alg-PWRI-KEK
             :         (1 2 840 113549 1 9 16 3 9)
   33 30   17:     SEQUENCE {
   35 06    5:       OBJECT IDENTIFIER des-CBC (1 3 14 3 2 7)
   42 04    8:       OCTET STRING
             :         EF E5 98 EF 21 B3 3D 6D　　… 初期化ベクタIV
             :       }
             :     }
   68 04   16:   OCTET STRING
             :     B8 1B 25 65 EE 37 3C A6 DE DC A2 6A 17 8B 0C 10　　… Encrypted_Key
             :   }
```

#### [パスワードを用いた復号のプロセス]
![password_dec.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663244/7416b711-aff2-efd0-81c8-a02146186851.jpeg)

##### [コンテンツ暗号鍵の復元方法]

鍵導出による鍵暗号鍵(KEK)の計算までは、暗号化の手順と同じ。
コンテンツ暗号鍵(CEK)の復号の手順は、以下の通り。
暗号化されたコンテンツ暗号鍵(Encrypted Key)のデータブロック数をnとする。
1.) 先ず、暗号モジュールの初期化ベクタIVを、(n-1)番目のブロックの値に設定し、(n)番目(最終)のブロックを復号する。
2.) 次に、復号した(n)番目(最終)のブロックを初期化ベクタIVにし、1～(n-1)番目のブロックを復号する。
3.) 暗号モジュールのパラメータどおりの初期化ベクタIVに設定しなおし、1～(n)番目までの全ブロックを復号する。
4.) 1バイト目が鍵長。2～4バイト目がチェック地、5バイト目～がコンテンツ用暗号鍵(CEK)の平文となる。



## ■暗号メッセージ構文(CMS)のパスワードによる暗号化を実装してみたもの。
https://github.com/Shaw02/CMS
※Microsoft Visual C++と、Microsoft Macro Assemblerにて作っています。