JCA （Java 暗号化アーキテクチャ）使い方メモ

# JCA とは
Java Cryptography Architecture（Java 暗号化アーキテクチャ）の略。
Java で暗号技術を使うための API、フレームワーク。

# 環境
## Java
`openjdk 11.0.2`

## OS
`Windows 10 (64bit)`

# 前提知識
JCA を使うには、暗号技術についての基礎知識（どういう技術があるのか、どういう仕組なのかとか）が必要になる。
これを知っておかないと、クラス構成の意味や正しい使い方が理解できない恐れがある。
最悪、実際の利用シチュエーションでは問題のある使い方をしてしまい、セキュリティホールを埋め込んでしまうかもしれない。

なので、まずはそもそもの暗号技術について勉強しておく必要がある。

暗号技術についての説明は [こちら](https://qiita.com/opengl-8080/items/85df520e2d8c4e19be8e) を参照。

# 事前定義している関数
検証には jshell を利用している。
よく利用する処理はあらかじめ関数として定義しておいて、特に説明することなく利用している。

```java:事前定義している関数
import java.nio.*
import java.nio.file.*
import java.io.*

// バイト配列を 16 進数表記の文字列に変換する
String toHexString(byte[] bytes) {
    ByteBuffer buffer = ByteBuffer.wrap(bytes);
    StringBuilder sb = new StringBuilder();
    for (int i=0; i<bytes.length; i+=4) {
        sb.append(String.format("%08x", buffer.getInt()));
    }
    return sb.toString();
}

// バイト配列をファイルに出力する
void writeFile(String path, byte[] bytes) throws IOException {
    Files.write(Paths.get(path), bytes);
}

// ファイルをバイト配列として読み取る
byte[] readFile(String path) throws IOException {
    return Files.readAllBytes(Paths.get(path));
}
```

# 注意事項
動作検証は JShell で行っているため、記述を簡略化するために入出力ストリームの `close()` は省略しています。
実際に利用する場合は、 `close()` するのをお忘れなきよう。


# JCA の基本的な仕組み
## 設計思想
JCA は次のことを実現することを基本方針として設計されている。

1. 実装の独立性と相互操作性
2. アルゴリズムの独立性と拡張性

### 実装の独立性
JCA が提供する様々な暗号技術（暗号化・ハッシュ関数・デジタル署名・etc...）には、様々な事情（米国の法律や新しい暗号技術の登場などなど）により複数の実装から構成されている。
この、暗号技術の実装を提供するコンポーネントのことを**暗号化サービス・プロバイダ**（もしくは単にプロバイダ）と呼ぶ。

実装の独立性とは、これらの実装を提供するプロバイダの存在を意識しなくて済むようにすることを指している。

つまり、「～～暗号を使いたいから、それをサポートしている・・・プロバイダを指定する」みたいなことを**することなく**、「～～暗号を使いたい」とだけ宣言すれば、あとは JCA がよしなにサポートしているプロバイダを見つけて実装クラスを解決してくれるようになっている。

### 実装の相互操作性
実装の相互操作性とは、複数のプロバイダを組み合わせられることを指している。

つまり、ハッシュ関数の実装には～～プロバイダを使い、乱数生成には・・・プロバイダを使い、鍵生成には＊＊＊プロバイダ、デジタル署名と暗号化には＠＠＠プロバイダを使う、みたいなことができるようになっている。

### アルゴリズムの独立性
普通、１つの暗号技術には複数のアルゴリズムが存在している。
例えば、ハッシュ関数（暗号技術）であれば SHA-1, SHA-2, SHA-3, MD5 など複数のアルゴリズムが存在する。

アルゴリズムの独立性とは、具体的なアルゴリズムに依存しないように暗号技術を使えることを指している。

例えばハッシュ関数については `MessageDigest` というクラスで抽象化されている。
このクラスには特定のアルゴリズムに依存しない API が定義されている。
`MessageDigest` のインスタンスを生成するときにアルゴリズムを指定する必要があるが、その後のハッシュ化の処理はアルゴリズムに関係なく同じ実装で実現できるようになっている。

### アルゴリズムの拡張性
アルゴリズムの拡張性とは、新しい暗号技術のアルゴリズムが発明され実装されたときに、それを簡単に追加して使えるようにすることを指している。

## 暗号化サービス・プロバイダ
１つ以上の暗号技術の実装を提供するコンポーネントを**暗号化サービス・プロバイダ**と呼ぶ。
JCA の文脈で単に「プロバイダ」と言った場合、それは暗号化サービス・プロバイダのことを指している。

JDK には最低１つのプロバイダがインストールされている。
プロバイダは静的にも動的に追加できるようになっている。

実際に JDK にデフォルトでインストールされているプロバイダについては、以下に説明がある（Oracle の Java の資料だから、 OpenJDK だと違う可能性がある？）。
[JDKプロバイダ・ドキュメント](https://docs.oracle.com/javase/jp/11/security/oracle-providers.html#GUID-FE2D2E28-C991-4EF9-9DBE-2A4982726313)

### プロバイダの検索
特定の暗号技術に関するクラスのインスタンスを生成する場合、アルゴリズムを指定してインスタンスを生成する。

例えば `MessageDigest` のインスタンスを SHA-256 のアルゴリズムを指定して生成する場合は次のように実装する。

```java:jshell
jshell> import java.security.*

jshell> var md = MessageDigest.getInstance("SHA-256")
md ==> SHA-256 Message Digest from SUN, <initialized>
```

すると、 JCA は裏でインストール済みのプロバイダに対して１つずつ「SHA-256 の `MessageDigest` をサポートしているか？」と問い合わせる。
そして、最初に見つかったサポートしているプロバイダから具体的な実装を取得するようになっている。

これにより、アプリケーションは具体的なプロバイダを意識することなく暗号技術を利用できるようになる。
一応プロバイダの名前を指定してインスタンスを取得することも可能だが、推奨はされていない。

### 実行環境にインストールされているプロバイダを確認する
プロバイダを表すクラスとして [java.security.Provider](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Provider.html) というクラスが用意されている。

現在の実行環境にインストールされている全ての `Provider` は、 `java.security.Security` の [getProviders()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Security.html#getProviders()) で取得できる。

```java:jshell
jshell> import java.util.stream.*

jshell> Stream.of(Security.getProviders()).forEach(System.out::println)
SUN version 11
SunRsaSign version 11
SunEC version 11
SunJSSE version 11
SunJCE version 11
SunJGSS version 11
SunSASL version 11
XMLDSig version 11
SunPCSC version 11
JdkLDAP version 11
JdkSASL version 11
SunMSCAPI version 11
SunPKCS11 version 11
```

ドキュメントとして参照したい場合は [4 JDKプロバイダ・ドキュメント](https://docs.oracle.com/javase/jp/11/security/oracle-providers.html#GUID-FE2D2E28-C991-4EF9-9DBE-2A4982726313) あたりを見ればいいと思う。

## エンジン・クラス
特定の暗号技術を提供するクラスのことを、**エンジン・クラス**と呼ぶ。

具体的なエンジン・クラスには、次のようなものがある（一部のみ）。

|クラス|提供する機能|
|-----|-----------|
|[SecureRandom](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/SecureRandom.html)|暗号用に予測不可能性を備えた乱数生成|
|[MessageDigest](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/MessageDigest.html)|暗号用のハッシュ関数|
|[Signature](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Signature.html)|デジタル署名の作成と検証|
|[Cipher](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/Cipher.html)|暗号化と復号|
|[Mac](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/Mac.html)|メッセージ認証コード|
|[KeyGenerator](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/KeyGenerator.html)|秘密鍵の生成|
|[KeyPairGenerator](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyPairGenerator.html)|鍵ペアの生成|
|[KeyStore](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html)|鍵を管理するキーストア|

### インスタンスの取得
```java:jshell
jshell> var md = MessageDigest.getInstance("SHA-256")
md ==> SHA-256 Message Digest from SUN, <initialized>
```

エンジン・クラスには `getInstance()` という `static` なファクトリメソッドが用意されている。
このファクトリメソッドの引数にアルゴリズムの名前を指定することで、そのアルゴリズムを実装したインスタンスを取得できる。

なお、アルゴリズムの名前は大文字小文字を**区別しない**。

### 仕様上サポートされているアルゴリズム
各エンジン・クラスには、仕様上サポートしなければならないアルゴリズムが存在している。

どのアルゴリズムがサポート必須となっているかは、各エンジン・クラスの Javadoc に記載されている。

たとえば `MessageDigest` クラスであれば Java 11 の時点で次の３つが必須となっている。

- MD5
- SHA-1
- SHA-256

[MessageDigest (Java SE 11 & JDK 11 )](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/MessageDigest.html)

### 実行環境のプロバイダがサポートしているアルゴリズムを確認する
```java:jshell
jshell> var provider = Security.getProviders()[0]
provider ==> SUN version 11

jshell> var services = provider.getServices()
services ==> [SUN: SecureRandom.DRBG -> sun.security.provider. ... ImplementedIn=Software}
]

jshell> services.stream().map(s -> s.getType() + ": " + s.getAlgorithm()).forEach(System.out::println)
SecureRandom: DRBG
SecureRandom: SHA1PRNG
Signature: SHA1withDSA
Signature: NONEwithDSA
Signature: SHA224withDSA
Signature: SHA256withDSA
Signature: SHA1withDSAinP1363Format
Signature: NONEwithDSAinP1363Format
Signature: SHA224withDSAinP1363Format
Signature: SHA256withDSAinP1363Format
KeyPairGenerator: DSA
MessageDigest: MD2
MessageDigest: MD5
MessageDigest: SHA
MessageDigest: SHA-224
MessageDigest: SHA-256
MessageDigest: SHA-384
MessageDigest: SHA-512
MessageDigest: SHA-512/224
MessageDigest: SHA-512/256
MessageDigest: SHA3-224
MessageDigest: SHA3-256
MessageDigest: SHA3-384
MessageDigest: SHA3-512
AlgorithmParameterGenerator: DSA
AlgorithmParameters: DSA
KeyFactory: DSA
CertificateFactory: X.509
KeyStore: PKCS12
KeyStore: JKS
KeyStore: CaseExactJKS
KeyStore: DKS
Policy: JavaPolicy
Configuration: JavaLoginConfig
CertPathBuilder: PKIX
CertPathValidator: PKIX
CertStore: Collection
CertStore: com.sun.security.IndexedCollection
```

プロバイダが提供する個々の暗号技術機能を表すクラスとして、 [java.security.Provider.Service](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Provider.Service.html) というクラスが用意されている。
この `Service` は、 `Provider` に用意されている各種 Getter メソッドから取得できる。

`Service` にはサポートする暗号技術に関する情報を取得するメソッドが用意されている。
例えば、[getType()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Provider.Service.html#getType()) からは `"MessageDigest"` のようにそのサービスがサポートする暗号技術の種類を取得でき、
[getAlgorithm()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Provider.Service.html#getAlgorithm()) メソッドからは、 `"SHA-256"` のように具体的なアルゴリズムの名前を取得できる。

ドキュメントとして参照したい場合は、 [Javaセキュリティ標準アルゴリズム名](https://docs.oracle.com/javase/jp/11/docs/specs/security/standard-names.html#cipher-algorithm-names) を見ればいいと思う。

具体的なプロバイダごとにサポートしているアルゴリズムを確認したい場合は、 [JDKプロバイダ・ドキュメント](https://docs.oracle.com/javase/jp/11/security/oracle-providers.html#GUID-FE2D2E28-C991-4EF9-9DBE-2A4982726313) を見ればいいと思う。


### アルゴリズムに依存しない API
エンジン・クラスは、そのクラスが提供する暗号技術に関する API を、アルゴリズムに依存しない形で提供している。

つまり、 SHA-256 でも MD5 でも、 `MessageDigest` を利用すれば同じ実装でハッシュ値を生成できるようになっている。

```java:jshell
// MD5 でハッシュ値を計算
jshell> var md5 = MessageDigest.getInstance("MD5")
md5 ==> MD5 Message Digest from SUN, <initialized>

jshell> md5.update("hoge".getBytes())

jshell> md5.digest()
$7 ==> byte[16] { -22, 112, 62, 122, -95, -17, -38, 0, 100, -22, -91, 7, -39, -24, -85, 126 }

// SHA-256 でハッシュ値を計算
jshell> var sha256 = MessageDigest.getInstance("SHA-256")
sha256 ==> SHA-256 Message Digest from SUN, <initialized>

jshell> sha256.update("hoge".getBytes())

jshell> sha256.digest()
$10 ==> byte[32] { -20, -74, 102, -41, 120, 114, 94, -55, 115, 7, 4, 77, 100, 43, -12, -47, 96, -86, -69, 118, -11, 108, 0, 105, -57, 30, -94, 91, 30, -110, 104, 37 }
```

↑は、 jshell で MD5 と SHA-256 でハッシュ値を生成している例になる。
`MessageDigest` のインスタンスを生成するときに指定しているアルゴリズムが異なるだけで、ハッシュ値を計算している部分の実装はどちらのアルゴリズムも同じ形になっている。

# パッケージが２つに別れている理由
JCA が提供するクラスは、大きく [javax.crypto](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/package-summary.html) パッケージと [java.security](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/package-summary.html) パッケージに分かれて提供されている。

これには歴史的な理由がある。
アメリカはかつて、暗号の輸出を厳しく制限していた時期があった。

[アメリカ合衆国からの暗号の輸出規制 - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%A1%E3%83%AA%E3%82%AB%E5%90%88%E8%A1%86%E5%9B%BD%E3%81%8B%E3%82%89%E3%81%AE%E6%9A%97%E5%8F%B7%E3%81%AE%E8%BC%B8%E5%87%BA%E8%A6%8F%E5%88%B6)

JCA のパッケージ構成はこの規制に対応したものとなっている。
`java.security` パッケージには輸出可能な技術に関するクラス（`MessageDigest` や `Signature`）が入れられ、 `javax.crypto` パッケージには輸出できない技術に関するクラス（`Cipher` や `KeyAgreement`）が入れられている。

プロバイダもこれらに合わせて分けられていたようで、 [SUN プロバイダ](https://docs.oracle.com/javase/jp/11/security/oracle-providers.html#GUID-3A80CC46-91E1-4E47-AC51-CB7B782CEA7D) が `java.security` で提供している機能を、 [SunJCE プロバイダ](https://docs.oracle.com/javase/jp/11/security/oracle-providers.html#GUID-A47B1249-593C-4C38-A0D0-68FA7681E0A7) が `javax.crypto` で提供している機能をそれぞれ実装しているっぽい。

規制が厳しかった頃は、 SunJCE は拡張機能として提供されていたらしいが、現在は規制が緩和され JDK にバンドルされるようになっている。

参考：[Javaの暗号化 | 1. 一般的なセキュリティ | セキュリティ開発者ガイド](https://docs.oracle.com/javase/jp/11/security/java-security-overview1.html#GUID-C6D250FC-F147-4284-A6BF-8384DFD39DA6)


# メッセージダイジェスト（ハッシュ）
ハッシュ関数を使うためには [java.security.MessageDigest](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/MessageDigest.html) クラスを使用する。

```java:jshell
jshell> var md = MessageDigest.getInstance("SHA-256")
md ==> SHA-256 Message Digest from SUN, <initialized>

jshell> byte[] hash = md.digest("hello world".getBytes())
hash ==> byte[32] { -71, 77, 39, -71, -109, 77, 62, 8, -91 ...  -84, -30, -17, -51, -23 }
```

[digest()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/MessageDigest.html#digest()) メソッドにハッシュ化したい値の `byte` 配列を渡すと、ハッシュ値が `byte` 配列で返る。

## 入力を分割する
```java:jshell
jshell> md.update("hello".getBytes())

jshell> md.update(" world".getBytes())

jshell> var hash = md.digest()
hash ==> byte[32] { -71, 77, 39, -71, -109, 77, 62, 8, -91 ...  -84, -30, -17, -51, -23 }
```

[update()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/MessageDigest.html#update(byte)) メソッドを使うと、入力を複数に分割できる。

もし一度に全ての `byte` を入力しなければならないとすると、一旦すべてのデータを `byte` 配列にしなければならないということになる。
ハッシュ対象のデータサイズが小さい場合は問題ないが、大容量のファイルなどをハッシュ化したい場合は全てのデータをメモリ上に展開することになるため厳しくなる。
そういう場合は、入力データをちょっとずつ `update()` に渡すことで全てのデータを一度にメモリ上に読み込まなくても済むようになる。

`digest()` を実行すると `MessageDigest` の状態は初期化されるので、インスタンスは再利用できる。


## 16進数の文字列に変換する
`digest()` の結果は `byte` 配列なので、そのままだと分かりづらい。
よくハッシュ値の文字列表現として 16進数の文字列が利用されるので、その変換をしてみる。

```java:jshell
// ※toHashString() は、事前定義している関数（ページトップを参照）
jshell> toHexString(hash)
$6 ==> "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
```


## ファイルのハッシュ値を計算する
ファイルからハッシュ値を計算する場合、自力で `FileInputStream` からデータを取り出して `MessageDigest` に入力する方法もあるが、より簡単に実装できるようにするためのクラスが用意されている。

- [DigestInputStream](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/DigestInputStream.html)
- [DigestOutputStream](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/DigestOutputStream.html)

OpenJDK 12 の Windows 版 zip ファイルの、 SHA-256 のハッシュ値を実際に計算してみる。

```java:jshell
jshell> var md = MessageDigest.getInstance("SHA-256")
md ==> SHA-256 Message Digest from SUN, <initialized>

// ファイルの InputStream を生成
jshell> var in = new BufferedInputStream(new FileInputStream("openjdk-12_windows-x64_bin.zip"))
in ==> java.io.BufferedInputStream@10bbd20a

// DigestInputStream を生成
jshell> var dis = new DigestInputStream(in, md)
dis ==> [Digest Input Stream] SHA-256 Message Digest from SUN, <initialized>

// InputStream から全ての情報を読み取る（読み取り結果は必要ないので nullOutputStream() に捨てる）
jshell> dis.transferTo(OutputStream.nullOutputStream())
$31 ==> 196405895

// ハッシュ値を計算
jshell> var hash = md.digest()
hash ==> byte[32] { 53, -88, -48, 24, -12, 32, -5, 5, -2,  ... -13, -119, 124, 78, -110 }
```

`DigestInputStream` は、ストリームからデータを読み取るたびに、読み取ったデータが `MessageDigest` の `update()` に渡される。

```java:jshell
jshell> var md = MessageDigest.getInstance("SHA-256")
md ==> SHA-256 Message Digest from SUN, <initialized>

// ファイルの InputStream を生成
jshell> var in = new BufferedInputStream(new FileInputStream("openjdk-12_windows-x64_bin.zip"))
in ==> java.io.BufferedInputStream@5a1c0542

// DigestOutputStream を生成（書き込まれた情報は必要ないので nullOutputStream() に捨てる）
jshell> var dos = new DigestOutputStream(OutputStream.nullOutputStream(), md)
dos ==> [Digest Output Stream] SHA-256 Message Digest from SUN, <initialized>

// InputStream から全ての情報を読み取り、 DigestOutputStream に書き出す
jshell> in.transferTo(dos)
$24 ==> 196405895

// ハッシュ値を計算
jshell> var hash = md.digest()
hash ==> byte[32] { 53, -88, -48, 24, -12, 32, -5, 5, -2,  ... -13, -119, 124, 78, -110 }
```

一方 `DigestOutputStream` は、ストリームにデータを書き出すたびに、書き出したデータが `MessageDigest` の `update()` に渡される。

ちなみに、計算したハッシュ値を 16 進数文字列に変換すると、

```java:jshell
jshell> toHexString(hash)
$12 ==> "35a8d018f420fb05fe7c2aa9933122896ca50bd23dbd373e90d8e2f3897c4e92"
```

[OpenJDK のサイトで公開されているハッシュ値](https://download.java.net/java/GA/jdk12/GPL/openjdk-12_windows-x64_bin.zip.sha256)は `35a8d018f420fb05fe7c2aa9933122896ca50bd23dbd373e90d8e2f3897c4e92` なので、ちゃんと同じ値が計算できている。

# 鍵
暗号技術には様々な「鍵」が存在する（秘密鍵、公開鍵、プライベート鍵、etc...）。
JCA も、それに合わせて様々な鍵を表す型を定義している（`SecretKey`, `PublicKey`, `PrivateKey`, etc...）。

鍵を表す型は様々だが、これらは全て１つの共通のインターフェースを親に持っている。
それが  [Key](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Key.html) インターフェースで、「鍵」を表す最上位の型となる。

`Key` には、鍵の情報にアクセスするためのメソッドが３つ定義されている。

1. [getAlgorithm()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Key.html#getAlgorithm())
    - AES や RSA といった、鍵に関連するアルゴリズム名を取得する
2. [getEncoded()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Key.html#getEncoded())
    - 鍵を特定の形式（X.509 や PKCS8 など）にエンコードした値を取得する
3. [getFormat()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Key.html#getFormat())
    - エンコードの名前を取得する

## 鍵仕様
`Key` からはアルゴリズムなどの情報は取得できるが、鍵を構成する具体的なデータは取得できない。
このように具体的な鍵データにアクセスできないことを、 JCA では**不透明な表現**と呼んでいる。

一方で、鍵を構成する具体的なデータにアクセスできる型も用意されている。
それが [KeySpec](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/spec/KeySpec.html) （鍵仕様）となる。

`KeySpec` 自体は「この型は鍵仕様である」ということを表現することが目的なので、実際に鍵データにアクセスするための API は定義されていない。
実際の API は、 `KeySpec` の実装クラスで定義されている。

例えば、 [DSAPrivateKeySpec](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/spec/DSAPrivateKeySpec.html) には、鍵生成で使用された素数 $p$ や非公開鍵 $x$ の情報にアクセスできるようになっている。

このように、鍵を構成する具体的なデータにアクセスできることを JCA では **透明な表現** と呼んでいる。

実際の `Key` と `KeySpec` および一部のクラス階層は次のような感じになっている。

![jca.png](https://qiita-image-store.s3.amazonaws.com/0/28302/5c73fb17-5ecf-777d-f43c-400cc66e5019.png)

## Generator と Factory
鍵を作るための手段として、 JCA には `Generator` と `Factory` の２つが用意されている。

`Generator` は、鍵を新規に作成する機能を提供する。
例えば、鍵長のようなパラメータを指定して新しい鍵を生成できる。

一方 `Factory` は、主に鍵と鍵仕様を相互変換する機能を提供する。
鍵によっては異なる２つの鍵仕様から同じ鍵を生成できるものもある（らしい）。

具体的には次のようなクラスが存在する。

- `Generator`
    - [javax.crypto.KeyGenerator](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/KeyGenerator.html)
    - [java.security.KeyPairGenerator](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyPairGenerator.html)
- `Factory`
    - [java.security.KeyFactory](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyFactory.html)
    - [javax.crypto.SecretKeyFactory](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/SecretKeyFactory.html)

# 暗号化/復号
共通鍵暗号および公開鍵暗号による暗号化/復号処理は、どちらも [javax.crypto.Cipher](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/Cipher.html) クラスを使用する。

## Cipher のアルゴリズム指定
```java:jshell
jshell> var cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher ==> Cipher.AES/CBC/PKCS5Padding, mode: not initialize ... orithm from: (no provider)
```

`Cipher` の `getInstance()` に渡す文字列は単純なアルゴリズム名ではなく、ブロック暗号のモードなども指定できるようになっている。

具体的な書式は `algorithm/mode/padding` となる。

`algorithm` には AES や DES, RSA などの暗号化アルゴリズムの名前を指定する。
`mode` は、ブロック暗号のモードで、 ECB や CBC などを指定する。
`padding` は、暗号化対象のデータがブロックサイズの整数倍でなかったときに、足りない分をパディングする方法を指定する。

なお `Cipher.getInstance("AES")` のように `algorithm` だけを指定することも可能となっている。
この場合、モードとパディングはプロバイダが決めているデフォルト値が利用される。
多くの場合、**デフォルトのモードは ECB になってしまう**ため、モードとパディングは常に明示しておいたほうが良い。

## 初期化
`Cipher` を使い始めるためには、まず先に初期化をしなければならない。

初期化には、「これから行う処理の種類」と「そのためのパラメータ」を指定する。
たとえば「暗号化」と「鍵」などを渡して初期化することになる。

```java:jshell
// 鍵を生成
jshell> var keyGen = KeyGenerator.getInstance("AES")
keyGen ==> javax.crypto.KeyGenerator@365185bd

jshell> var key = keyGen.generateKey()
key ==> javax.crypto.spec.SecretKeySpec@fffe87d2

// Cipher の初期化
jshell> cipher.init(Cipher.ENCRYPT_MODE, key)

// 初期化パラメータの取得
jshell> var params = cipher.getParameters()
params ==>
    iv:
[0000: F1 D7 23 45 DA A6 7B 42   66 AF ...  46  ..#E...Bf.z..a.F
]
```

初期化には `init()` メソッドを使う。

第一引数には初期化の種類を指定する。
初期化の種類は、 [Cipher に定義されている定数](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/Cipher.html#field.summary)を使用する。
`ENCRYPT_MODE` は暗号化モードで、 `DECRYPT_MODE` は復号モードになる。

第二引数以降には初期化のパラメータを渡す。
基本は暗号化/復号で使う鍵を指定する。

暗号化のアルゴリズムによっては追加のパラメータが必要になる場合もある。
その場合は、 `AlgorithmParameterSpec` など追加のパラメータを受け取る [init()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/Cipher.html#init(int,java.security.Key,java.security.spec.AlgorithmParameterSpec)) メソッドを使用する。
[AlgorithmParameterSpec](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/spec/AlgorithmParameterSpec.html) は、アルゴリズム固有のパラメータを表すインターフェースで、様々な暗号化アルゴリズム固有の実装クラスが用意されている。

追加パラメータが必要なアルゴリズムでも、プロバイダがよしなにデフォルト値を設定してくれることがある。
その場合は、処理モードと鍵を受け取る `init()` メソッドだけで初期化ができる。
このとき使用されたパラメータは、 [getParameters()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/Cipher.html#getParameters()) メソッドで取得できる。

```java:jshell
// Cipher の初期化
jshell> cipher.init(Cipher.ENCRYPT_MODE, key)

// 初期化パラメータの取得
jshell> var params = cipher.getParameters()
params ==>
    iv:
[0000: F1 D7 23 45 DA A6 7B 42   66 AF ...  46  ..#E...Bf.z..a.F
]
```

ここでは AES 用の鍵を生成して、 `Cipher` を暗号化モードで初期化している。

`Cipher` 生成時のモードを CBC にしていたため、本来は初期化ベクトル（IV : **I**nitialization **V**ector）のパラメータが必要となる。
しかし、 `init()` で初期化ベクトルの指定を省略していたため、プロバイダがよしなに初期化ベクトルを生成してくれている。

初期化ベクトルは復号モードの初期化でも必要になるので `getParameters()` で取得しておく必要がある。
仮に使用した暗号化アルゴリズムが追加のパラメータを必要としなかった場合は、 `getParameters()` は `null` を返す。

初期化を行うと、 `Cipher` は内部状態が全てリセットされる。
つまり、一度暗号化に使った `Cipher` のインスタンスを、今度は復号に再利用することができる。
逆にいうと、暗号化の途中で復号モードに初期化してしまうと、暗号化処理の途中で設定していた情報が失われるので注意が必要。

## 暗号化/復号
```java:jshell
jshell> cipher.doFinal("Java Cryptography Architecture".getBytes())
$63 ==> byte[32] { -24, 54, 9, 79, -2, 118, 101, 69, -63, 30, -104, 77, -21, -24, -28, -4, 31, -56, -125, -121, 24, -115, 55, -92, -68, -35, -6, 90, -108, -122, -16, 21 }
```

[doFinal()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/Cipher.html#doFinal(byte%5B%5D)) メソッドに暗号化/復号したい値（`byte` 配列）を渡すことで、暗号文/平文（`byte` 配列）を得ることができる。

`MessageDigest` と同様で、入力は [update()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/Cipher.html#update(byte%5B%5D)) を使って分割することもできる。
ただし、 `MessageDigest` の場合とは異なり暗号化/復号の結果は `update()` のたびに返される。

```java:jshell
jshell> cipher.update("Java ".getBytes())
$59 ==> byte[0] {  }

jshell> cipher.update("Cryptography ".getBytes())
$60 ==> byte[16] { -24, 54, 9, 79, -2, 118, 101, 69, -63, 30, -104, 77, -21, -24, -28, -4 }

jshell> cipher.update("Architecture".getBytes())
$61 ==> byte[0] {  }

jshell> cipher.doFinal()
$62 ==> byte[16] { 31, -56, -125, -121, 24, -115, 55, -92, -68, -35, -6, 90, -108, -122, -16, 21 }
```

さらにブロック暗号を使っている場合、入力サイズがブロックのサイズになるまでは空の `byte` 配列が返される。
AES の場合、ブロックのサイズは 128 ビット（16 バイト）なので、入力が 16 バイトに達するまでは `update()` の戻り値は空配列になる。

`doFinal()` が呼ばれると、 `Cipher` のインスタンスは直前の初期化時に戻る。
つまり、そのまま別の入力を暗号化/復号するのに再利用できる。

## CipherInputStream/CipherOutputStream
`DigestInputStream`/`DigestOutputStream` と同じように、 `Cipher` と `InputStream`/`OutputStream` を連携させたクラスが用意されている。

- [CipherInputStream](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/CipherInputStream.html)
- [CipherOutputStream](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/CipherOutputStream.html)

それぞれのデータと暗号化/復号の流れは、下図のようなイメージになる。

![jca.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/42e90600-f97c-0f9d-4142-2ac963b570c0.jpeg)

まずは `CipherInputStream` を使った場合。

```java:jshell
// 暗号化対象となる InputStream を作成
jshell> var is = new ByteArrayInputStream("Java Cryptography Architecture".getBytes())
is ==> java.io.ByteArrayInputStream@5fcd892a

// CipherInputStream を作成
jshell> var cis = new CipherInputStream(is, cipher)
cis ==> javax.crypto.CipherInputStream@b9afc07

// 出力先の OutputStream を作成
jshell> var out = new ByteArrayOutputStream()
out ==>

// InputStream から全情報を取り出し、暗号化結果を OutputStream に書き出す
jshell> cis.transferTo(out)
$67 ==> 32

// OutputStream に書き出された結果を確認
jshell> out.toByteArray()
$68 ==> byte[32] { -24, 54, 9, 79, -2, 118, 101, 69, -63, 30, -104, 77, -21, -24, -28, -4, 31, -56, -125, -121, 24, -115, 55, -92, -68, -35, -6, 90, -108, -122, -16, 21 }
```

次に `CipherOutputStream` を使った場合。

```java:jshell
// 暗号化対象となる InputStream を作成
jshell> var is = new ByteArrayInputStream("Java Cryptography Architecture".getBytes())
is ==> java.io.ByteArrayInputStream@133e16fd

// 暗号文の出力先となる OutputStream を作成
jshell> var out = new ByteArrayOutputStream()
out ==>

// CipherOutputStream を作成
jshell> var cos = new CipherOutputStream(out, cipher)
cos ==> javax.crypto.CipherOutputStream@51b279c9

// 暗号化対象を CipherOutputStream に流し込む
jshell> is.transferTo(cos)
$92 ==> 30

// doFinal() を実行させるため CipherOutputStream を close()
jshell> cos.close()

// 暗号文を確認
jshell> out.toByteArray()
$94 ==> byte[32] { -24, 54, 9, 79, -2, 118, 101, 69, -63, 30, -104, 77, -21, -24, -28, -4, 31, -56, -125, -121, 24, -115, 55, -92, -68, -35, -6, 90, -108, -122, -16, 21 }
```

`CipherInputStream` と `CipherOutputStream` は、どちらも `close()` 時に `Cipher` の `doFinal()` が実行される。
なので、 `close()` は忘れずに実行しておかないと結果が中途半端になったりする[^1]（ストリームなので、基本 try-with-resources などを使うから気にする必要はないと思うが）。

[^1]: `CipherInputStream` は `close()` してなくても結果が中途半端になっていないが、実装をみると入力ストリームが末尾まで達すると `doFinal()` しているっぽい

### transferTo() は使うべきでない？
`CipherInputStream` の [Javadoc](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/CipherInputStream.html) には次のように書かれている。

> このクラスを使用するプログラマは、**このクラスで定義されていないメソッド、またはオーバーライドされていないメソッド(あとでスーパー・クラスのいずれかに追加された新しいメソッドやコンストラクタなど)を絶対に使用しないでください**。それらのメソッドの設計と実装では、CipherInputStreamに関するセキュリティ上の影響が考慮されていない可能性があるためです。

一方、 `InputStream` には Java 9 で [transferTo()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/InputStream.html#transferTo(java.io.OutputStream)) という便利なメソッドが追加されている。

この `transferTo()` は `CipherInputStream` でオーバーライドされていないので、使うべきでない条件を満たしてしまっている。

ただ、この `transferTo()` は OpenJDK 11 では次のように実装されている。

```java:InputStream.java
    public long transferTo(OutputStream out) throws IOException {
        Objects.requireNonNull(out, "out");
        long transferred = 0;
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        int read;
        while ((read = this.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) {
            out.write(buffer, 0, read);
            transferred += read;
        }
        return transferred;
    }
```

ここでは、 `InputStream` の `read()` と `OutputStream` の `write()` だけが使われている。

もし `transferTo()` を使わずに処理を書こうとしても、結局この `transferTo()` がやっていることと同じ実装を書くことになる。
そして、そのとき使用するメソッドは `CipherInputStream` がオーバーライドしている `read()` か `CipherOutputStream` の `write()` になる。

となると、結局 `transferTo()` を使っても問題ないと個人的には思う。

ただし、この実装はあくまで OpenJDK に限った話なので、他の Java 実装だとセキュリティ上問題のある実装になっている可能性もゼロではないかもしれない。

ご利用は自己責任で。

## AES
AES の暗号化と復号を実装してみる。

### 鍵の生成
```java:jshell
// アルゴリズムを AES にして KeyGenerator を作成
jshell> var keyGen = KeyGenerator.getInstance("AES")
keyGen ==> javax.crypto.KeyGenerator@4d50efb8

// 鍵の生成
jshell> var key = keyGen.generateKey()
key ==> javax.crypto.spec.SecretKeySpec@177c6
```

鍵の生成には [KeyGenerator](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/KeyGenerator.html) を使用する。
SunJCE プロバイダでは、デフォルトの鍵長は 128 ビットになる。

#### 鍵長の指定
```java:jshell
jshell> keyGen.init(256)
```

`init()` メソッドで鍵長を指定できる。指定可能な鍵長は 128, 192, 256 のいずれかのみ（AES の仕様）。

### 暗号化
```java:jshell
// Cipher を AES で生成
jshell> var cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher ==> Cipher.AES/CBC/PKCS5Padding, mode: not initialize ... orithm from: (no provider)

// 暗号化モードで初期化
jshell> cipher.init(Cipher.ENCRYPT_MODE, key)

// 初期化ベクトルの情報を取得
jshell> var params = cipher.getParameters()
params ==>
    iv:
[0000: 88 8E 06 A0 89 80 0D 11   FF ED ...  45  ...........&.'.E
]

// 暗号化
jshell> var c = cipher.doFinal("Java Cryptography Architecture".getBytes())
c ==> byte[32] { -35, -80, -57, 91, -75, -46, -123, 49, ...  22, 90, -121, 124, -108 }

// 暗号化できているか確認
jshell> new String(c)
$24 ==> "ﾝｰﾇ[ｵﾒ?1juｾｴﾋ｢ﾘ\017}初ｮPW｣\026Z?|?"
```

復号時に初期化ベクトルの情報が必要になるので、 `getParameters()` でパラメータの情報を取得しておく。

### 復号
```java:jshell
// 復号モードで初期化
jshell> cipher.init(Cipher.DECRYPT_MODE, key, params)

// 復号
jshell> var p = cipher.doFinal(c)
p ==> byte[30] { 74, 97, 118, 97, 32, 67, 114, 121, 112 ... , 99, 116, 117, 114, 101 }

// 復号できているか確認
jshell> new String(p)
$23 ==> "Java Cryptography Architecture"
```

`init()` のときに、鍵だけでなく暗号化の初期化で使用したパラメータも指定しなければならない。

### 鍵をファイルに出力する
```java:jshell
// 鍵をファイルに出力
jshell> writeFile("secret-key", key.getEncoded())

// 暗号化時に使用したパラメータをファイルに出力
jshell> writeFile("aes-params", params.getEncoded())

// 暗号文の生成
jshell> var c = cipher.doFinal("Hello World!!".getBytes())
c ==> byte[16] { -77, 124, 124, -4, -92, 80, -89, 102,  ... 1, 114, 7, 117, -104, 11 }

// 暗号文をファイルに出力
jshell> writeFile("aes-cryptogram", c)
```

秘密鍵のエンコードされたデータは `Key` の [getEncoded()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Key.html#getEncoded()) で取得できる。
また、パラメータの情報は `AlgorithmParameters` の [getEncoded()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/AlgorithmParameters.html#getEncoded()) で取得できる。

### 鍵をファイルから復元する
```java:jshell
// 秘密鍵の情報をファイルから読み取り、 SecretKeySpec を生成する
jshell> Key key = new SecretKeySpec(readFile("secret-key"), "AES")
key ==> javax.crypto.spec.SecretKeySpec@178a8

// AlgorithmParameters のインスタンスを取得し、ファイルから読み込んだ情報で初期化する
jshell> var params = AlgorithmParameters.getInstance("AES")
params ==>

jshell> params.init(readFile("aes-params"))

// Cipher を生成して読み込んだ情報で初期化
jshell> var cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher ==> javax.crypto.Cipher@df27fae

jshell> cipher.init(Cipher.DECRYPT_MODE, key, params)

// 暗号文をファイルから読み取り、復号
jshell> var m = cipher.doFinal(readFile("aes-cryptogram"))
m ==> byte[13] { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 33 }

// 結果を確認
jshell> new String(m)
$34 ==> "Hello World!!"
```

秘密鍵は [SecretKeySpec](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/spec/SecretKeySpec.html) を使って復元できる（アルゴリズムは `"AES"` を指定）。
このクラスは `KeySpec` でありながら `Key` も実装しているため、このまま鍵として利用できる。

パラメータは [AlgorithmParameters](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/AlgorithmParameters.html) で復元できる。
まずは [getInstance()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/AlgorithmParameters.html#getInstance(java.lang.String)) で `"AES"` を指定してインスタンスを取得する。
そして、 [init()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/AlgorithmParameters.html#init(byte%5B%5D)) でパラメータのデータを設定する。

### パスワードベース暗号（PBE）
```java:jshell
// パスワードベース暗号用の KeySpec を生成
jshell> char[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' }
password ==> char[8] { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' }

jshell> var keySpec = new PBEKeySpec(password)
keySpec ==> javax.crypto.spec.PBEKeySpec@5a8e6209

// SecretKeyFactory を使って KeySpec から Key を生成
jshell> var keyFac = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128")
keyFac ==> javax.crypto.SecretKeyFactory@3234e239

jshell> var key = keyFac.generateSecret(keySpec)
key ==> com.sun.crypto.provider.PBEKey@855e49d8
```

PBE では、 [PBEKey](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/interfaces/PBEKey.html) という鍵を使用する。

`PBEKey` は、パスワードをもとに [PBEKeySpec](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/spec/PBEKeySpec.html) を生成し、[SecretKeyFactory](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/SecretKeyFactory.html) を使って `PBEKey` に変換して取得する。

パスワードは `char` の配列で指定しなければならない。
これは、 `PBEKeySpec` の Javadoc に理由が書いてあるが、 `String` だと値が不変であるためあとでクリアができないかららしい。

`SecretKeyFactory` のインスタンス取得では、アルゴリズムに `PBEWithHmacSHA256AndAES_128` を指定している。

アルゴリズム名は `PBEWith<digest|prf>And<encryption>` という書式になっている。
`digest|prf` が MD5 や SHA1 の場合、 RFC8018 でいうところの PBES1 になり、 `Hmac*` の場合は PBES2 を使うことになる。

```java:jshell
// パスワードベース暗号用の Cipher を生成して暗号化モードで初期化
jshell> var cipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_128/CBC/PKCS5Padding")
cipher ==> javax.crypto.Cipher@5891e32e
```

`Cipher` も同じアルゴリズムでインスタンスを取得する。
（SunJCE プロバイダの `Cipher` が PBE でサポートしているモードとパディングは `CBC/PKCS5Padding` のみなので明示的に指定しなくてもよさそうな気がするが、念の為）


```java:jshell
// 疑似乱数生成器の作成
jshell> var random = new SecureRandom()
random ==> Hash_DRBG,SHA-256,128,reseed_only

// salt の生成
jshell> var salt = new byte[64];
salt ==> byte[64] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... , 0, 0, 0, 0, 0, 0, 0, 0 }

jshell> random.nextBytes(salt)
```

ソルトを生成する。

ソルトは暗号論的疑似乱数生成器（[SecureRandom](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/SecureRandom.html)）を使って生成する。

[RFC8018](https://tools.ietf.org/html/rfc8018#section-4.1) ではソルトのサイズを最低 64 ビット以上としているので、とりあえず 64 ビットで生成した。

```java:jshell
// イテレーション回数の宣言
jshell> int iterationCount = 1000
iterationCount ==> 1000

// パスワードベース暗号用の AlgorithmParameterSpec を生成
jshell> var keyParamSpec = new PBEParameterSpec(salt, iterationCount)
keyParamSpec ==> javax.crypto.spec.PBEParameterSpec@eec5a4a

jshell> cipher.init(Cipher.ENCRYPT_MODE, key, keyParamSpec)
```

イテレーション回数とソルトの値を使って、 [PBEParameterSpec](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/spec/PBEParameterSpec.html) を生成する。

そして、先程作った鍵とともに `Cipher` の初期化を行う。

```java:jshell
// 暗号化実行
jshell> var c = cipher.doFinal("Hello World!!".getBytes())
c ==> byte[16] { 101, -52, -106, 26, 67, 118, -20, 22,  ...  42, -71, 115, 123, -122 }

// 暗号結果を確認
jshell> new String(c)
$18 ==> "eﾌ?\032Cv?\026\031ｬ\000*ｹs{?"
```

あとは、他のアルゴリズムと同じように暗号化を行う。

```java:jshell
// 暗号化時に使用したパラメータを取得
jshell> var params = cipher.getParameters()
params ==> PBEWithHmacSHA256AndAES_128

jshell> PBEParameterSpec pbeParamSpec = params.getParameterSpec(PBEParameterSpec.class)
pbeParamSpec ==> javax.crypto.spec.PBEParameterSpec@35a50a4c

// IvParameterSpec を取得
jshell> IvParameterSpec ivSpec = (IvParameterSpec)pbeParamSpec.getParameterSpec()
ivSpec ==> javax.crypto.spec.IvParameterSpec@281e3708

// IV (初期化ベクトル)の値を確認
jshell> var iv = ivSpec.getIV()
iv ==> byte[16] { -67, 47, 111, -2, 34, -17, -89, 74, -7 ... 46, -124, 99, 79, 23, 88 }
```

CBC で暗号化したので、復号時のために初期化ベクトルを取得しておく必要がある。
暗号化時に自動設定された初期化ベクトルの情報（`IvParameterSpec`）は、 [PBEParameterSpec](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/spec/PBEParameterSpec.html) の `getParameterSpec()` メソッドで取得できる。

```java:jshell
// 復号用に PBEParameterSpec を生成
jshell> var ivSpec = new IvParameterSpec(iv)
ivSpec ==> javax.crypto.spec.IvParameterSpec@dbd940d

jshell> PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, iterationCount, ivSpec)
pbeParamSpec ==> javax.crypto.spec.PBEParameterSpec@17695df3

// 復号モードで初期化（※key の生成は暗号化時と同じ）
jshell> cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec)

// 復号を実行
jshell> var m = cipher.doFinal(c)
m ==> byte[13] { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 33 }

// 復号結果を確認
jshell> new String(m)
$21 ==> "Hello World!!"
```

復号時は、再び「パスワード」「ソルト」「イテレーション回数」の情報を使って `Key` と `AlgorithmParameterSpec` を生成し、 `Cipher` を初期化する。
（IV の情報は暗号化時のものを使う必要があるので、 `PBEParameterSpec` の生成は若干異なっている）

## RSA
公開鍵暗号の RSA を使用してみる。

### 鍵の生成
```java:jshell
// 鍵ペアを生成するための KeyPairGenerator を取得
jshell> var keyPairGen = KeyPairGenerator.getInstance("RSA")
keyPairGen ==> java.security.KeyPairGenerator$Delegate@42dafa95

// 鍵ペアを生成
jshell> var keyPair = keyPairGen.generateKeyPair()
keyPair ==> java.security.KeyPair@1dfe2924
```

公開鍵暗号の鍵ペアを生成するには、 [KeyPairGenerator](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyPairGenerator.html) を使用する。
RSA を使うので、アルゴリズム名は `"RSA"` を渡す。

`generateKeyPair()` を実行すると、公開鍵とプライベート鍵のセットを含んだ [KeyPair](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyPair.html) を取得できる。

#### 鍵長の指定
```java:jshell
jshell> keyPairGen.initialize(4096)

jshell> keyPairGen.generateKeyPair().getPublic()
$9 ==> Sun RSA public key, 4096 bits
  params: null
  modulus: 58218231801097229661983999673649965349728454764714757172625245890343045...
  public exponent: 65537
```

`KeyPairGenerator` の [initialize()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyPairGenerator.html#initialize(int)) メソッドで鍵長を指定できる。

### 暗号化
```java:jshell
// RSA 用の Cipher を取得
jshell> var cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")
cipher ==> javax.crypto.Cipher@35a50a4c

// 暗号化モードで初期化
jshell> cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic())

// 暗号化実行
jshell> var c = cipher.doFinal("Hello World!!".getBytes())
c ==> byte[256] { 44, 48, -56, -42, -117, -48, 17, -76, ... , -15, 63, 46, -115, -42 }

// 暗号結果を確認
jshell> new String(c)
$14 ==> ",0ﾈﾖ巾\021ｴ\030?0\022:ﾘ斃w?9ｧ,萬|ﾉ_?轍ﾓｫﾉmﾄЩｱ]\002錥)ﾏ\024L5XD?齡ﾂﾙ訶({ｘ?ｼ\024,5､S@wM-?杣ｳg1ﾝ\025｢02鷦ﾍﾗﾄJ盆ﾐ?\tｩﾚmﾌF悊^?\"ﾐｳ\023ｩ&ﾓﾎ輙ﾓ瓸ｺ.Mri\037ｸi?9ﾑEDｹﾚgg\023ｑ亥X4\021pM3廱ﾔ塗覆ﾎ\017G\024億ｱ\017ｿ?;ｨ Q\177冬U5wgLﾞB浅簗涓ﾐ>\006e?g?9lｴ醋棒:\t]垉R\020ｿ?ｎﾐ?!?&sｴ?ｫﾗ\016`H\f6漬?ﾚ?ｭ4\nfU呶?.斎"
```

`KeyPair` の `getPublic()` で公開鍵を取得できるので、これで暗号化を行う。
手順は AES の場合と一緒。

### 復号
```java:jshell
// 暗号化時に生成されたパラメータを取得
jshell> var params = cipher.getParameters()
params ==> MD: SHA-256
MGF: MGF1SHA-1
PSource: PSpecified

// 復号モードで初期化
jshell> cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate(), params)

// 復号実行
jshell> var m = cipher.doFinal(c)
m ==> byte[13] { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 33 }

// 復号結果を確認
jshell> new String(m)
$18 ==> "Hello World!!"
```

`KeyPair` の `getPrivate()` でプライベート鍵が取得できるので、それを使って復号を行う。
こちらも、 `Cipher` の使い方は AES の場合と同じ感じ。

# メッセージ認証コード（MAC）
```java:jshell
// MAC 用に秘密鍵を生成する
jshell> var keyGen = KeyGenerator.getInstance("HmacSHA256")
keyGen ==> javax.crypto.KeyGenerator@17579e0f

jshell> var key = keyGen.generateKey()
key ==> javax.crypto.spec.SecretKeySpec@588390c

// MAC のインスタンスを生成する
jshell> var mac = Mac.getInstance("HmacSHA256")
mac ==> javax.crypto.Mac@7a765367

// MAC を初期化する
jshell> mac.init(key)

// MAC 値を計算する
jshell> var mv = mac.doFinal("Hello World!!".getBytes())
mv ==> byte[32] { 0, -103, -60, 37, -74, 97, 115, -50, 7 ... 80, 57, -84, -59, 90, 23 }
```

MAC 値の計算には [Mac](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/Mac.html) クラスを使用する。

MAC には秘密鍵が必要になるので、 `KeyGenerator` を使って鍵を生成する。

`Mac` のインスタンスを生成したら、まずは秘密鍵を使って `init()` メソッドで初期化を行う。
次に、 `doFinal()` に MAC 値を計算したい値（`byte` 配列）を渡す。
計算された MAC 値は、 `byte` 配列として返される。

## 分割して計算する
```java:jshell
jshell> mac.update("Hello ".getBytes())

jshell> mac.update("World!!".getBytes())

jshell> mac.doFinal()
$9 ==> byte[32] { 0, -103, -60, 37, -74, 97, 115, -50, 77, 6, 107, 27, 15, -41, -15, 111, -99, -50, -85, -68, 11, -66, 69, -54, 44, -92, 80, 57, -84, -59, 90, 23 }
```

`Cipher` などと同様に、 `update()` を使って計算対象の値を分割して設定できる。

# デジタル署名
## RSA 署名
```java:jshell
// RSA の鍵ペアを生成する
jshell> var keyPairGen = KeyPairGenerator.getInstance("RSA")
keyPairGen ==> java.security.KeyPairGenerator$Delegate@42dafa95

jshell> var keyPair = keyPairGen.generateKeyPair()
keyPair ==> java.security.KeyPair@1dfe2924
```

まずは、 RSA 暗号のときと同じように RSA の鍵ペアを生成する。

```java:jshell
// Signature インスタンス取得
jshell> var signature = Signature.getInstance("SHA256WithRSA")
signature ==> Signature object: SHA256WithRSA<not initialized>

// 署名モードで初期化
jshell> signature.initSign(keyPair.getPrivate())

// 署名対象のデータを登録
jshell> signature.update("Hello World!!".getBytes())

// デジタル署名を生成
jshell> var sign = signature.sign()
sign ==> byte[256] { 29, -91, 95, 8, -19, -56, 118, -29, 1 ... 7, -1, 53, 58, -99, -117 }
```

デジタル署名を作成するには、 [Signature](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Signature.html) クラスを使用する。

`Signature` は２つの初期化メソッドを持っている。
１つが [initSign()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Signature.html#initSign(java.security.PrivateKey)) で、もう１つが [initVerify()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Signature.html#initVerify(java.security.PublicKey)) になる。

署名を作成する場合は、 `initSign()` を使って初期化を行う。
このとき、引数には署名で使用する署名鍵（プライベート鍵）を渡す。

初期化が済んだら、次は `update()` メソッドで署名対象のデータを登録していく。
最後に [sign()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Signature.html#sign()) メソッドを実行すると、デジタル署名が `byte` 配列で返される。

```java:jshell
// Signature を検証モードで初期化
jshell> signature.initVerify(keyPair.getPublic())

// 検証対象のデータを登録
jshell> signature.update("Hello World!!".getBytes())

// 検証を実行
jshell> signature.verify(sign)
$12 ==> true
```

検証を行う場合は、 `initVerify()` で `Signature` を初期化する。
このとき、引数には検証鍵（公開鍵）を渡す。

こちらも `update()` で検証対象のデータを登録する。
そして、最後に [verify()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/Signature.html#verify(byte%5B%5D)) メソッドで検証を実行する。
このとき、引数にはデジタル署名の `byte` 配列を渡す。

検証が成功した場合は `true` が返る。

## DSA 署名
```java:jshell
// DSA 用の鍵ペアを生成
jshell> var keyPairGen = KeyPairGenerator.getInstance("DSA")
keyPairGen ==> sun.security.provider.DSAKeyPairGenerator$Current@f6c48ac

jshell> var keyPair = keyPairGen.generateKeyPair()
keyPair ==> java.security.KeyPair@1f36e637

// DSA 用の Signature インスタンスを取得
jshell> var signature = Signature.getInstance("SHA256WithDSA")
signature ==> Signature object: SHA256WithDSA<not initialized>

// 署名モードで初期化
jshell> signature.initSign(keyPair.getPrivate())

// 署名対象データを登録
jshell> signature.update("Hello World!!".getBytes())

// デジタル署名を生成
jshell> var sign = signature.sign()
sign ==> byte[63] { 48, 61, 2, 29, 0, -72, 46, -94, 42, 12 ... 4, 59, -38, -5, -48, 127 }

// 検証モードで初期化
jshell> signature.initVerify(keyPair.getPublic())

// 検証対象データを登録
jshell> signature.update("Hello World!!".getBytes())

// 検証を実行
jshell> signature.verify(sign)
$12 ==> true
```

アルゴリズム名などを DSA 用に変えただけで、実装の方法は RSA の場合と同じ感じでできる。

# keytool
[keytool | Java Platform, Standard Editionツール・リファレンス](https://docs.oracle.com/javase/jp/10/tools/keytool.htm)

JDK には **keytool** というコマンドラインツールが同梱されている。
keytool を使うと、鍵や証明書を生成したり管理することができる。

```bash:keytoolのヘルプ
$ keytool -h
キーおよび証明書管理ツール

コマンド:

 -certreq            証明書リクエストを生成します
 -changealias        エントリの別名を変更します
 -delete             エントリを削除します
 -exportcert         証明書をエクスポートします
 -genkeypair         鍵ペアを生成します
 -genseckey          秘密鍵を生成します
 -gencert            証明書リクエストから証明書を生成します
 -importcert         証明書または証明書チェーンをインポートします
 -importpass         パスワードをインポートします
 -importkeystore     別のキーストアから1つまたはすべてのエントリをインポートします
 -keypasswd          エントリの鍵パスワードを変更します
 -list               キーストア内のエントリをリストします
 -printcert          証明書の内容を出力します
 -printcertreq       証明書リクエストの内容を出力します
 -printcrl           CRLファイルの内容を出力します
 -storepasswd        キーストアのストア・パスワードを変更します

このヘルプ・メッセージを表示するには"keytool -?、-hまたは--help"を使用します
command_nameの使用方法については、"keytool -command_name --help"を使用します。
事前構成済のオプション・ファイルを指定するには、-conf <url>オプションを使用します。
```

## コマンドの書式
```
keytool <コマンド> <オプション>
```

keytool は、実行するコマンドと、そのオプションを渡すことで利用できる。
コマンドとオプションは、全て `-名前` という形式になっている。

コマンドの一覧は `-h` で確認できる。
さらに各コマンドのヘルプは、そのコマンドのオプションに `-h` を渡すことで詳細を確認できる。

```bash:-certreqコマンドのヘルプ
$ keytool -certreq -h
keytool -certreq [OPTION]...

証明書リクエストを生成します

オプション:

 -alias <alias>          処理するエントリの別名
 -sigalg <alg>           署名アルゴリズム名
 -file <file>            出力ファイル名
 -keypass <arg>          鍵のパスワード
 -keystore <keystore>    キーストア名
 -dname <name>           識別名
 -ext <value>            X.509拡張
 -storepass <arg>        キーストアのパスワード
 -storetype <type>       キーストアのタイプ
 -providername <name>    プロバイダ名
 -addprovider <name>     名前でセキュリティ・プロバイダを追加する(SunPKCS11など)
   [-providerarg <arg>]    -addproviderの引数を構成する
 -providerclass <class>  完全修飾クラス名でセキュリティ・プロバイダを追加する
   [-providerarg <arg>]    -providerclassの引数を構成する
 -providerpath <list>    プロバイダ・クラスパス
 -v                      詳細出力
 -protected              保護メカニズムによるパスワード

このヘルプ・メッセージを表示するには"keytool -?、-hまたは--help"を使用します
```

## キーストア
keytool はキーストアと呼ばれるファイルに鍵や証明書などの情報を保存する。
キーストアは、 Java 8 までは JKS という Oracle 独自のファイルフォーマットで作成されていた。
Java 9 以降は PKCS#12 という規格（[RFC7292](https://tools.ietf.org/html/rfc7292#section-1)）で定義されているファイルフォーマットで作成される。

keytool の各コマンドは `-keystore` というオプションを受け取るようになっている。
このオプションで、キーストアのファイルの場所を指定する。
未指定の場合は、デフォルトで `ホームディレクトリ/.keystore` がキーストアファイルのパスとして使用される。

コマンドの一覧を見るとわかるが、キーストアを新規作成するための専用のコマンドは存在しない。
キーストアファイルは、最初に鍵などを生成しようとしたときに、ファイルが無ければ自動的に作成されるようになっている。

キーストア自体はパスワードベース暗号で暗号化されていて、中身にアクセスするためにはパスワードの入力が必要になる。
キーストアのパスワードは、そのキーストアを最初に作成するときに入力を求められる。
パスワードの長さは 6 桁以上でなければならない。

キーストアの情報は、Java プログラムからもアクセスできるようになっている。
そのためのクラスが [KeyStore](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html) になる（詳細後述）。

### エントリ
キーストア内に格納する鍵や証明書は、総称して**エントリ**と呼ばれる。

キーストアは一種のキーバリューストアになっていて、各エントリには**エイリアス**と呼ばれる名前をつけて管理する。
特定のエントリの情報を表示・編集するためには、エイリアスでエントリを指定することになる。

### エントリの種類
エントリは２種類存在する。

- 鍵のエントリ
- 信頼された証明書のエントリ

「鍵のエントリ」は、秘密鍵か、プライベート鍵とそのペアとなる公開鍵証明書のセットを格納したエントリになる。
公開鍵証明書は自己署名証明書の場合もあれば、ルート証明書までチェーンしている場合もある。

一方「信頼できる証明書のエントリ」には**単独の**公開鍵証明書が格納される。
他の公開鍵によって署名されている場合も、このエントリに格納される公開鍵証明書は１つだけになる（チェーンはしていない）。


## キーストアの中身を確認する
### 一覧の表示
```bash
$ keytool -list
キーストアのタイプ: PKCS12
キーストア・プロバイダ: SUN

キーストアには8エントリが含まれます

hogekey,2019/04/10, SecretKeyEntry,
pbekey,2019/04/10, SecretKeyEntry,
rsakey,2019/04/13, PrivateKeyEntry,
証明書のフィンガプリント(SHA-256): CB:D1:0C:85:5F:1D:50:CC:62:C6:68:11:9F:7E:7D:4D:F7:1B:45:40:44:71:99:B3:41:2B:71:7A:E1:A6:02:FB
dsakey,2019/04/13, PrivateKeyEntry,
証明書のフィンガプリント(SHA-256): E9:70:1D:B2:8C:8B:18:C2:7C:A4:A0:DC:32:A5:37:06:ED:1B:DF:30:65:32:77:B5:43:77:4D:9D:42:15:70:B2
qiita-cert,2019/04/15, trustedCertEntry,
証明書のフィンガプリント(SHA-256): 57:28:2C:9D:D0:43:19:08:A4:CC:D3:52:CF:5F:32:16:EC:9D:DA:4A:4E:D1:5C:1F:3A:EB:39:3F:76:A3:91:D4
```

`-list` コマンドでエントリの一覧を確認できる。

`PrivateKeyEntry` と書いてるのが、プライベート鍵と公開鍵のペアが格納されたエントリになる（鍵エントリ）。
`SecretKeyEntry` と書いてるのが、秘密鍵のエントリになる（こちらも鍵エントリ）。
`trustedCertEntry` と書いてるのが、信頼できる証明書のエントリになる。


### エントリを指定して表示
```bash
$ keytool -list -alias hogekey
hogekey,2019/04/10, SecretKeyEntry,

$ keytool -list -alias rsakey
rsakey,2019/04/13, PrivateKeyEntry,
証明書のフィンガプリント(SHA-256): CB:D1:0C:85:5F:1D:50:CC:62:C6:68:11:9F:7E:7D:4D:F7:1B:45:40:44:71:99:B3:41:2B:71:7A:E1:A6:02:FB
```

`-alias` オプションで個別に表示したいエントリのエイリアスを指定すると、そのエイリアスの情報だけを出力できる。

さらに `-v` オプションをつけることで詳細な情報を出力できる。

```bash
$ keytool -list -alias hogekey -v
別名: hogekey
作成日: 2019/04/10
エントリ・タイプ: SecretKeyEntry

$ keytool -list -alias rsakey -v
別名: rsakey
作成日: 2019/04/13
エントリ・タイプ: PrivateKeyEntry
証明書チェーンの長さ: 1
証明書[1]:
所有者: CN=Alice, C=JP
発行者: CN=Alice, C=JP
シリアル番号: 6a59c918
有効期間の開始日: Sat Apr 13 08:41:13 JST 2019終了日: Fri Jul 12 08:41:13 JST 2019
証明書のフィンガプリント:
         SHA1: 1F:11:0E:8C:30:0B:DA:D8:2C:79:AD:C7:4B:2B:70:62:85:94:94:CA
         SHA256: CB:D1:0C:85:5F:1D:50:CC:62:C6:68:11:9F:7E:7D:4D:F7:1B:45:40:44:71:99:B3:41:2B:71:7A:E1:A6:02:FB
署名アルゴリズム名: SHA256withRSA
サブジェクト公開鍵アルゴリズム: 2048ビットRSA鍵
バージョン: 3

拡張:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 92 68 47 49 D0 26 A7 1D   04 51 64 3E 96 5B B3 05  .hGI.&...Qd>.[..
0010: 40 7B 3B E2                                        @.;.
]
]
```

## キーストアのパスを変更する
```bash
$ keytool -storepasswd
新規keystore password:
新規keystore passwordを再入力してください:
```

`-storepasswd` コマンドで、キーストアのパスワードを変更できる。

## エントリを削除する
```bash
$ keytool -delete -alias foo
```

`-delete` コマンドで、任意のエントリを削除できる。

削除対象のエントリは、 `-alias` オプションで指定する。

## エントリのエイリアスを変更する
```bash
$ keytool -changealias -alias from-alias -destalias to-alias
```

`-changealias` コマンドで、任意のエントリのエイリアスを変更できる。

`-alias` は、変更対象のエイリアスを指定する。
`-destalias` は、変更後のエイリアスの名前を指定する。

## JRE に組み込まれているキーストアを確認する
Java は実行環境にルート証明書を記録したキーストアを保持している。

具体的なファイルは `${JAVA_HOME}/lib/security/cacerts` になる。
これもキーストアなので、 keytool を使って内容を確認したり編集したりできる。

```bash:cacertsの内容を確認する
$ keytool -list -cacerts
キーストアのパスワードを入力してください:
キーストアのタイプ: JKS
キーストア・プロバイダ: SUN

キーストアには93エントリが含まれます

verisignclass2g2ca [jdk],2018/06/13, trustedCertEntry,
証明書のフィンガプリント(SHA-256): 3A:43:E2:20:FE:7F:3E:A9:65:3D:1E:21:74:2E:AC:2B:75:C2:0F:D8:98:03:05:BC:50:2C:AF:8C:2D:9B:41:A1
digicertassuredidg3 [jdk],2017/12/01, trustedCertEntry,
証明書のフィンガプリント(SHA-256): 7E:37:CB:8B:4C:47:09:0C:AB:36:55:1B:A6:F4:5D:B8:40:68:0F:BA:16:6A:95:2D:B1:00:71:7F:43:05:3F:C2
verisignuniversalrootca [jdk],2017/12/01, trustedCertEntry,
証明書のフィンガプリント(SHA-256): 23:99:56:11:27:A5:71:25:DE:8C:EF:EA:61:0D:DF:2F:A0:78:B5:C8:06:7F:4E:82:82:90:BF:B8:60:E8:4B:3C

...

証明書のフィンガプリント(SHA-256): 43:48:A0:E9:44:4C:78:CB:26:5E:05:8D:5E:89:44:B4:D8:4F:96:62:BD:26:DB:25:7F:89:34:A4:43:C7:01:61
addtrustqualifiedca [jdk],2017/12/01, trustedCertEntry,
証明書のフィンガプリント(SHA-256): 80:95:21:08:05:DB:4B:BC:35:5E:44:28:D8:FD:6E:C2:CD:E3:AB:5F:B9:7A:99:42:98:8E:B8:F4:DC:D0:60:16
```

デフォルトのパスワードは `changeit` となっている。
ちなみに、ドキュメントには「システム管理者は、SDKのインストール後、このファイルのパスワードとデフォルト・アクセス権を変更する必要があります。」と書かれている。

オプションの `-cacerts` を指定すると、 cacerts ファイルをキーストアとして指定したのと同じ状態になるので、 `-keystore` でファイルパスを指定しなくてもアクセスできる。

Java で SSL 通信などを実装すると、デフォルトではこの cacerts を使って証明書の検証が行われる。
また、後述する証明書のインポートのときにも参照される場合がある。

## 秘密鍵を生成する
```bash:
$ keytool -genseckey -keyalg AES -keysize 256 -alias HogeKey
キーストアのパスワードを入力してください:**********
新規パスワードを再入力してください:**********
```

共通鍵暗号の秘密鍵を生成するには、 `-genseckey` コマンドを使用する。

パスワードの入力を求められているが、これはキーストアファイル自体を暗号化/復号するためのパスワードになる。
特に今回は初回アクセスだった（キーストアを初めて作成した）ので、新規パスワードの再入力も促されている。
次回以降は、パスワードの入力は一回だけになる。

`-keyalg` で鍵のアルゴリズムとして AES を指定し、鍵長を 256 としている。

`-alias` では、登録される秘密鍵につけるエイリアスを指定している。
省略可能だが、その場合は `mykey` という値になる。

### パスワードベース鍵は生成できない？
`-keyalg` にパスワードベース鍵のアルゴリズムを指定してエントリを作成してみた。

```bash
$ keytool -genseckey -keyalg PBEWithHmacSHA256AndAES_128 -keysize 256 -alias pbeKey
キーストアのパスワードを入力してください:*******
保存するパスワードを入力してください:**************
パスワードを再入力してください:**************
```

特にエラーもなくエントリが生成できたので、成功した感じがする。
しかし、このエントリを Java プログラムから取得すると、次のようになる。

```java:jshell
（前略）

jshell> var entry = keystore.getEntry("pbeKey", password)
entry ==> Secret key entry with algorithm PBEWithMD5AndDES
```

アルゴリズムが `PBEWithMD5AndDES` になってる。。。
正式なサポートはされていないということだろうか？

## 公開鍵暗号の鍵ペアを生成する
```bash
$ keytool -genkeypair -keyalg RSA -keysize 2048 -dname CN=Alice,C=JP -alias rsakey
```

公開鍵暗号の鍵ペアを生成するには、 `-genkeypair` コマンドを使用する。

共通鍵暗号のときと同様で、 `-keyalg` で鍵のアルゴリズムを、 `-keysize` で鍵長（ビット数）を指定できる。
（DSA の鍵ペアを作るなら、 `-keyalg` に `DSA` と指定すればいい）

`-genkeypair` では、公開鍵は自己署名証明書（X.509）の形で生成される。
`-dname` では、証明書の発行者（issuer）と所有者（subject）に設定される X.500 識別名を指定する。
`-dname` の指定を省略した場合は、コマンドラインから X.500 識別名の入力を求められる。

### X.500 識別名
X.500 識別名は、 `CN=Amazon, OU=Server CA 1B, O=Amazon, C=US` のように `<属性名>=<値>` をカンマで区切った形で記述する。
属性名には次の値を指定できる。

|属性名|意味|
|----|----|
|`CN`|一般名（common name）<br>所有者の名前など|
|`OU`|部門名（organization unit）<br>部や課などの名前|
|`O`|組織名（organization name）<br>会社名など|
|`L`|地域名（locality name）<br>都市名など|
|`S`|地方名（state name）<br>州名など（日本なら都道府県レベル？）|
|`C`|国コード（country）<br>国を識別する２桁のコード（`JP`, `US` など）|

実際の例をいくつか。

|確認したサイト|subjectの値|
|-----|-----------|
|Qiita<br>https://qiita.com/|`CN=qiita.com`|
|日本オラクル<br>https://www.oracle.com/jp/index.html|`CN=www-cs-01.oracle.com`<br>`OU=Content Management Services IT`<br>`O=Oracle Corporation`<br>`L=Redwood Shores`<br>`S=California`<br>`C=US`|
|Amazon<br>https://www.amazon.co.jp/|`CN=www.amazon.co.jp`<br>`O=Amazon.com, Inc.`<br>`L=Seattle`<br>`S=Washington`<br>`C=US`|
|内閣府<br>https://www.cao.go.jp/|`CN=*.cao.go.jp`<br>`O=Cabinet Office`<br>`L=Chiyoda-ku`<br>`S=Tokyo`<br>`C=JP`|
|Panasonic<br>https://panasonic.jp/|`CN=panasonic.jp`<br>`O=Panasonic Corporation`<br>`L=Kadoma`<br>`S=Osaka`<br>`C=JP`|

各属性名は、全てを指定する必要はない。
ただし、順序には制約があり、 `CN`, `OU`, `O`, `L`, `S`, `C` の順に並んでいなければならない。

`-dname` オプションで指定する場合、空白スペースを含めたい場合はオプションの値を引用符（`"`）で囲う必要がある。

```bash:-dnameを引用符で囲う例
$ keytool ... -dname "CN=Alice, S=Osaka Fu, C=JP"
```

また、半角カンマは属性を区切る特別な意味を持つので、属性値に半角含めたい場合はバックスラッシュでエスケープする必要がある。

```bash:属性値に半角カンマを使用する
$ keytool ... -dname CN=Hoge\,Fuga
```

### 証明書の有効期間
証明書の有効期間は、次の２つのオプションで制御できる。

- `-startdate`
- `-validity`

`-startdate` は、証明書の有効期間の開始日時になる。
`-validity` は、証明書の有効期間を日数で指定する。

つまり、 `-startdate` で指定した日時から `-validity` で指定した日数の間が、証明書の有効期間になる。

`-startdate` のデフォルト値は、鍵を作成したときの実行日時で、 `-validity` のデフォルト値は `90` となっている。
したがって、証明書のデフォルトの有効期間は、鍵ペアを作成した日時から 90 日間ということになる。

`-startdate` は、相対的な方法と絶対的な方法のいずれかで指定できる。

相対的な方法では、実行日時からの相対的な時間で開始日時を指定する。
具体的には次のように記述する。

```bash:相対的な開始日時の指定
$ keytool ... -startdate +10y-1m+5d+8H
```

この指定は、 `+10y` で「プラス１０年」、`-1m` で「マイナス１ヶ月」、`+5d` で「プラス５日」、`+8H` で「プラス８時間」を表している。

相対指定の書式は `([+-]nnn[ymdHMS])+` となっている。
つまり、年・月・日・時・分・秒をそれぞれプラス/マイナスでどれくらいシフトさせるかで指定する。

一方、絶対指定は次のように指定する。

```bash:絶対的な開始日時の指定
$ keytool ... -startdate "2019/12/01 12:00:00"
```

こちらは見たまんま。

書式としては、 `[yyyy/mm/dd] [HH:MM:SS]` となる。
省略した方は実行日時が利用される。

つまり、年月日だけを指定した場合は、時分秒が実行時間になり、
時分秒だけを指定した場合は、年月日が実行日になる。

## 証明書を出力する
```bash
$ keytool -exportcert -alias alice-key -file alice-cert
証明書がファイル<alice-cert>に保存されました
```

`-exportcert` コマンドを使うと、指定したエントリにある証明書を X.509 形式で出力できる。
（秘密鍵のエントリは証明書がないので指定できない）

デフォルトは標準出力に出力される。
ファイルに出力するには `-file` オプションを指定する。

出力形式は、デフォルトはバイナリ形式となる。
PEM 形式にするには、 `-rfc` オプションを指定する。

```bash:PEM形式で出力する例
$ keytool -exportcert -alias alice-key -rfc
-----BEGIN CERTIFICATE-----
MIIC2zCCAcOgAwIBAgIEFj7+/DANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJj
...略...
7ticcWecmUspMZ2dx/lO
-----END CERTIFICATE-----
```

## 証明書ファイルの内容を表示する
```bash
$ keytool -printcert -file alice-cert
所有者: CN=alice
発行者: CN=ca
シリアル番号: 163efefc
有効期間の開始日: Sat Apr 13 11:45:48 JST 2019終了日: Fri Jul 12 11:45:48 JST 2019
証明書のフィンガプリント:
         SHA1: 07:E5:76:25:83:D1:C2:C5:66:C3:87:9E:E9:7A:B0:E8:1C:07:63:83
         SHA256: 55:ED:F8:4B:CC:3F:C9:A9:3F:5D:D8:B3:CC:51:33:58:3E:36:4A:10:06:1F:E7:19:94:25:73:66:30:E7:85:EF
署名アルゴリズム名: SHA256withRSA
サブジェクト公開キー・アルゴリズム: 2048ビットRSAキー
バージョン: 3

拡張:
...
```

`-printcert` コマンドを `-file` オプションを指定して実行すると、指定した証明書ファイルの中身を確認できる。
`-file` を指定しない場合は、デフォルトで標準入力から情報を読み取る。

デフォルトは、上述のように人間が読みやすい形式で出力される。
PEM 形式で出力するには `-rfc` オプションを指定する。

```bash:PEM形式で出力した場合
$ keytool -printcert -file alice-cert -rfc
-----BEGIN CERTIFICATE-----
MIIC2zCCAcOgAwIBAgIEFj7+/DANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJj
...略...
7ticcWecmUspMZ2dx/lO
-----END CERTIFICATE-----
```

`-printcert` コマンドはキーストアに関係ないので、 `-keystore` を指定せずに実行できる。

### 任意の SSL サーバーの証明書を表示する
```bash
$ keytool -printcert -sslserver qiita.com
Certificate #0
====================================
所有者: CN=qiita.com
発行者: CN=Amazon, OU=Server CA 1B, O=Amazon, C=US
シリアル番号: 793de44ec0e815de65a3fbb3e35a1e2
有効期間の開始日: Sun Mar 31 09:00:00 JST 2019終了日: Thu Apr 30 21:00:00 JST 2020
証明書のフィンガプリント:
         SHA1: C0:D8:EE:56:3B:9F:68:22:0B:36:F3:9E:2A:D3:69:4E:3C:1D:61:44
         SHA256: 57:28:2C:9D:D0:43:19:08:A4:CC:D3:52:CF:5F:32:16:EC:9D:DA:4A:4E:D1:5C:1F:3A:EB:39:3F:76:A3:91:D4
署名アルゴリズム名: SHA256withRSA
サブジェクト公開キー・アルゴリズム: 2048ビットRSAキー
バージョン: 3

...

Certificate #3
====================================
所有者: CN=Starfield Services Root Certificate Authority - G2, O="Starfield Technologies, Inc.", L=Scottsdale, ST=Arizona, C=US
発行者: OU=Starfield Class 2 Certification Authority, O="Starfield Technologies, Inc.", C=US
シリアル番号: a70e4a4c3482b77f
...
```

`-printcert` コマンドに `-sslserver` オプションをつけて実行すると、指定した SSL サーバーの証明書を出力できる。

`-sslserver` にはホストとポートを `host:port` の形で指定できる。
ポート番号は省略可能で、デフォルトは 443 になる。

プロキシ環境下で実行する場合は、 `-J-Dhttps.proxyHost` と `-J-Dhttps.proxyPort` オプションでプロキシの情報を指定する（例：`-J-Dhttps.proxyHost=proxy.host.name -J-Dhttps.proxyPort=8080`）。

こちらもデフォルトは人間が読める形式になる。
PEM 形式にするには、 `-rfc` オプションを指定する。

証明書ファイルとしてローカルに保存したい場合は、 `-rfc` オプションをつけたうえで出力結果をファイルにリダイレクトすればいい（`-file` は入力ファイルのオプションなので、これを指定してもファイルには保存されない）。


## 証明書署名要求を作成する
```bash
$ keytool -certreq -alias alice-key
-----BEGIN NEW CERTIFICATE REQUEST-----
MIICkDCCAXgCAQAwGzELMAkGA1UEBhMCSlAxDDAKBgNVBAMTA1RvbTCCASIwDQYJ
...略...
wX7FTUBXCD8CjXFxosmZ97YJf7Xy89cmdArgVNE9T9pJJpht
-----END NEW CERTIFICATE REQUEST-----
```

`-certreq` コマンドを使うと、証明書署名要求を作成できる。

`-alias` オプションには、対象となる公開鍵エントリのエイリアスを指定する。

デフォルトは標準出力に出力される。
ファイルに出力したい場合は、リダイレクトするか `-file` オプションで出力先のファイルを指定する。

```bash:証明書署名要求をファイルに出力する
$ keytool -certreq -alias alice-key -file alice-certreq
```

## 証明書署名要求から証明書を生成する
```bash
# キーストアの内容を確認
$ keytool -list
...

キーストアには2エントリが含まれます

ca-key,2019/04/13, PrivateKeyEntry,
...
alice-key,2019/04/13, PrivateKeyEntry,
...

# 証明書署名要求ファイルから証明書を生成
$ keytool -gencert -infile alice-certreq -alias ca-key -outfile alice-cert
```

`-gencert` コマンドを使うと、証明書署名要求ファイルから証明書（X.509 形式）を生成できる。

上の例は、証明書署名要求ファイル `alice-certreq` に対して、キーストア内に存在する鍵ペアエントリ `ca-key` を使って証明書を生成している。

`-infile` は、処理対象となる証明書署名要求ファイルを指定する。
未指定の場合は標準入力から入力する。

`-alias` では、証明書の署名に使用する鍵のエントリを指定する。

`-outfile` は、結果の証明書を出力するファイルを指定している。
未指定の場合は標準出力に出力される。

デフォルトでは、証明書はバイナリ形式で出力される。
`-rfc` オプションを指定すると、 PEM 形式で出力できる。

```bash:-rfcオプションを指定した場合
$ keytool -gencert -infile alice-certreq -alias ca-key -rfc
-----BEGIN CERTIFICATE-----
MIIC2zCCAcOgAwIBAgIEF+xeBDANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJj
...略...
es0hcACS0jLw0VoJYjSB
-----END CERTIFICATE-----
```

## 証明書を取り込む
証明書を取り込むには `-importcert` コマンドを使用する。

インポートできるデータの形式は、 PKCS#7 形式の証明書チェーンか X.509 証明書の単独またはシーケンスになる。

このコマンドには２つの機能が存在する。

1. 証明書応答のインポート
2. 信頼できる証明書のインポート

`-importcert` コマンドでは、条件によってこのいずれかの機能が実行される。

次の条件を満たした場合は、証明書応答のインポートとして動作する。

- `-alias` で指定した名前のエントリがすでにキーストアに存在する
- そのエントリがプライベート鍵のエントリである

一方、次の条件を満たした場合は、信頼できる証明書のインポートとして動作する。

- `-alias` で指定した名前のエントリがキーストアに存在しない

### 証明書応答のインポート
`-alias` で指定した名前のエントリがプライベート鍵のエントリとして既にキーストアに存在する場合、インポートしようとしている証明書は証明書応答と判断される。
つまり、その鍵エントリ内の公開鍵証明書から生成した証明書署名要求に対して、 CA が署名して返してきた証明書応答をインポートしようとしている、という扱いになる。

したがって、 keytool は鍵エントリに含まれる公開鍵証明書を、インポート対象に指定した証明書応答で置き換えるように動作する。

#### 証明書応答の検証
インポートが実際に行われる前に、証明書応答が正当なものか検証が行われる。

検証は、大きく次の２つが行われる。

1. 証明書応答が、確かに `-alias` で指定された鍵エントリに対応するものかどうか
2. 証明書応答が信頼できるかどうか

証明書応答が `-alias` で指定した鍵エントリに対応するものかどうかは、証明書応答の中に鍵エントリ内の公開鍵と同じものがあるかどうかで判断される。
証明書応答の中に鍵エントリ内の公開鍵証明書が存在しないのであれば、その証明書応答は全然関係のないモノなので、エラーとなる。

![jca.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/3b709df2-615c-49df-ab1f-f4c77da321cd.jpeg)

証明書応答が信頼できるかどうかの判定方法は、証明書応答内に存在する証明書の数によって大きく２つに分かれる。

##### 証明書応答内に複数の証明書が含まれる場合
証明書応答内に複数の証明書が含まれる場合は、それらの証明書で有効な証明書チェーンが構築できるかどうかが試される。

まず最初に、証明書応答の中からインポート先の鍵エントリと同じ公開鍵を持つ証明書を見つけ出す。
そして、その証明書を先頭にして、署名の検証が OK となる証明書を次々と繋げていく。

![jca.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/32e91f76-4645-f213-9dbf-8320b6ba40e3.jpeg)

末尾にきた証明書が自己署名証明書だった場合、その証明書と同じ証明書がキーストア内に存在するかが確認される。
キーストア内に同じ自己署名証明書が存在した場合は、証明書チェーンは正当なものと判断され、構築された証明書チェーンが鍵エントリにインポートされる。
同じ自己署名証明書が存在しなかった場合は、インポートを続行して良いか keytool が確認を求めてくる。

一方、末尾にきた証明書が自己署名証明書でなかった場合、その署名の検証が通る証明書がキーストア内に存在しないか確認される。
キーストア内に該当する証明書が見つかった場合は、証明書チェーンの末尾にキーストア内で見つかった証明書を追加したうえで、鍵エントリに証明書チェーンがインポートされる。
該当する証明書が見つからなかった場合は、インポートを続行して良いか keytool が確認を求めてくる。

![jca.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/0ff773bb-52fd-a3de-57a2-df7a8a34c3c6.jpeg)


##### 証明書応答が単独の証明書の場合
証明書応答内に証明書が１つだけ存在する場合は、検証の動作が少し変わる。

この場合、 keytool はインポート先のキーストア内に存在する証明書を使って証明書チェーンが再現できないか試みる。

![jca.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/42c4e302-33f3-e0f2-bfda-4662dc118472.jpeg)

最終的にルートまで証明書チェーンが再現できた場合は、信頼できるモノとして再現されたチェーンが鍵エントリに登録される。
チェーンが再現できなかった場合（途中で検証OKとなる公開鍵を見つけられなかった場合）、インポートを続行して良いか keytool が確認を求めてくる。


### 信頼できる証明書のインポート
`-alias` で指定したエントリがキーストア内に存在しない場合、信頼できる証明書としてインポートが行われる。

この場合、インポートしようとしているデータに証明書が複数存在しても、先頭の１つだけが取り込まれる。

このときも、証明書が正当なものか検証が行われる。
この検証は、証明書応答が単独の証明書だった場合と同じ動作になる。

つまり、キーストア内の証明書を使って証明書チェーンが再現できるかどうかで、正当な証明書かどうかが判定される。

チェーンが再現できればそのままインポートが完了する。
再現できない場合は、インポートを続行して良いか keytool が確認を求めてくる。

### cacerts の証明書も検証に利用する
`-importcert` コマンドのオプションで `-trustcacerts` を指定すると、検証のときに cacerts 内のルート証明書も利用されるようになる。
（デフォルトは、対象のキーストア内の証明書だけが利用される）


### 実際にやってみる
```bash
# Alice のキーストアを確認
$ keytool -keystore alice.keystore -list
...
キーストアには1エントリが含まれます

alice-key,2019/04/18, PrivateKeyEntry,
証明書のフィンガプリント(SHA-256): C7:55:BB:57:C4:3F:57:02:BE:2E:57:1E:7C:6F:F4:A9:55:E5:90:92:6B:7D:DF:5F:14:FF:F2:65:16:81:E1:56

# CA のキーストアを確認
$ keytool -keystore ca.keystore -list
...
キーストアには2エントリが含まれます

middle-ca-key,2019/04/18, PrivateKeyEntry,
証明書のフィンガプリント(SHA-256): CE:79:D5:32:D0:1E:5E:3A:55:BB:3E:CD:CE:5E:5F:9F:4D:79:97:2E:E7:EF:F9:60:62:C5:D0:4E:AD:28:B4:60
root-ca-key,2019/04/18, PrivateKeyEntry,
証明書のフィンガプリント(SHA-256): C0:06:6C:5C:2C:F5:3D:8B:DB:D4:EC:19:C5:30:A0:90:71:00:CC:17:40:E8:EB:38:64:5B:8F:40:3A:D4:3F:7C
```

検証のために、２つのキーストアを用意した。

１つは Alice のキーストアで、現在は自作した自己署名証明書の公開鍵ペアのエントリが１つだけ存在している（`alice-key`）。

もう１つは CA （認証局）のキーストアで、中間認証局用の公開鍵ペア（`middle-ca-key`）とルート認証局用の公開鍵ペア（`root-ca-key`）の２つのエントリが存在している。
（ちなみに、 `middle-ca-key` は `root-ca-key` で署名済み）

ここから、 Alice の公開鍵ペアから証明書署名要求を生成し、認証局の鍵で署名し、 Alice のキーストアにインポートしてみる。

```bash
# Alice の鍵から証明書署名要求を生成
$ keytool -keystore alice.keystore -certreq -alias alice-key -file alice.csr

# 中間認証局の鍵で署名
$ keytool -keystore ca.keystore -gencert -alias middle-ca-key -infile alice.csr -outfile alice.cer

# 生成された証明書の中身を確認
$ keytool -printcert -file alice.cer
証明書[1]:
所有者: CN=alice
発行者: CN=middle-ca
シリアル番号: 25fc2e01
...

証明書[2]:
所有者: CN=middle-ca
発行者: CN=root-ca
シリアル番号: 248f2111
...
```

Alice の鍵ペアから証明書署名要求を生成し、中間認証局の鍵で署名した公開鍵証明書を生成した。

中身をみると、 Alice→中間認証局の順に証明書がチェーンしていることがわかる（ルート認証局の証明書は入っていない）。

これを、 Alice の元の鍵エントリにインポートする（つまり、証明書応答としてインポートする）。

```bash
$ keytool -keystore alice.keystore -importcert -file alice.cer -alias alice-key

応答したトップレベルの証明書:

所有者: CN=middle-ca
発行者: CN=root-ca
シリアル番号: 248f2111
...

... は信頼されていません。 応答をインストールしますか。[いいえ]:
```

トップレベルの証明書が信頼できないと判断され、インポートして良いか確認を促された。
`root-ca` の証明書は Alice のキーストアには存在しないので、インポートしようとした証明書応答は信頼できないモノと判断されている。

ここは一旦インポートを中断し、先にルート認証局の証明書を Alice のキーストアにインポートする。

```bash
# ルート認証局の公開鍵証明書をエクスポート
$ keytool -keystore ca.keystore -exportcert -alias root-ca-key -file root-ca.cer
証明書がファイル<root-ca.cer>に保存されました

# Alice のキーストアにルート認証局の証明書をインポート
$ keytool -keystore alice.keystore -importcert -file root-ca.cer -alias root-ca-cert
所有者: CN=root-ca
発行者: CN=root-ca
シリアル番号: 13b64cb6
...
証明書のフィンガプリント:
         SHA1: E9:D2:EF:D2:1D:86:8F:04:50:0C:ED:DD:5B:C1:C5:DD:FE:64:77:3D
         SHA256: C0:06:6C:5C:2C:F5:3D:8B:DB:D4:EC:19:C5:30:A0:90:71:00:CC:17:40:E8:EB:38:64:5B:8F:40:3A:D4:3F:7C
...

この証明書を信頼しますか。 [いいえ]:  y
証明書がキーストアに追加されました
```

`-alias` に指定したエントリは存在しないので、 `-importcert` コマンドは「信頼できる証明書のインポート」として動作する。

この証明書は自己署名証明書なので、インポートする人（キーストアの管理者）が信頼するかどうか決めるしか無い。
ここは信頼するので、 `y` でインポートを完了させた。

実際に信頼できる証明書をインポートする場合は、画面に表示されているフィンガープリントなどの情報を見て、確かに意図した証明書をインポートしていることを注意深く確認しなければならない。

あらためて Alice の証明書応答をインポートしてみる。

```bash
# Alice の証明書応答をインポート
$ keytool -keystore alice.keystore -importcert -file alice.cer -alias alice-key
証明書応答がキーストアにインストールされました

# インポート後の Alice の鍵エントリを確認
$ keytool -keystore alice.keystore -storepass password -list -v -alias alice-key
別名: alice-key
作成日: 2019/04/18
エントリ・タイプ: PrivateKeyEntry
証明書チェーンの長さ: 3
証明書[1]:
所有者: CN=alice
発行者: CN=middle-ca
シリアル番号: 25fc2e01
...

証明書[2]:
所有者: CN=middle-ca
発行者: CN=root-ca
シリアル番号: 248f2111
...

証明書[3]:
所有者: CN=root-ca
発行者: CN=root-ca
シリアル番号: 13b64cb6
...
```

今度はルート認証局の証明書がインストール済みだったため、証明書チェーンの検証がすんなり成功し、そのままインストールが完了した。

また、インポート後は Alice の鍵エントリ内の公開鍵証明書に証明書チェーンが格納されているのがわかる。

# KeyStore
keytool コマンドで生成・管理するキーストアファイルは、 Java のプログラムからもアクセスできるように API が用意されている。

## KeyStore インスタンスを取得する
```java:jshell
jshell> var keystore = KeyStore.getInstance("PKCS12")
keystore ==> java.security.KeyStore@3fd7a715
```

キーストアにアクセスするには、 [KeyStore](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html) クラスを使用する。

`getInstance()` では、キーストアのタイプを指定する。
Java 9 以降であれば、キーストアのタイプはデフォルトで PKCS12 なので、 `"PKCS12"` でインスタンスを生成している。
（古いキーストアであれば JKS の可能性もあるので、その場合は `"JKS"` と指定する）

## キーストアファイルを読み込む
```java:jshell
jshell> char[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' }
password ==> char[8] { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' }

jshell> keystore.load(new FileInputStream("alice.keystore"), password)
```

キーストアファイルを読み込むには、 [load()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#load(java.io.InputStream,char%5B%5D)) メソッドを使う。
第一引数にはキーストアファイルの `InputStream` を指定し、第二引数にはキーストアファイルのパスワードを `char` 配列で指定する。

### インスタンス取得と同時に読み込む
```java:jshell
jshell> var keystore = KeyStore.getInstance(new File("alice.keystore"), password)
keystore ==> java.security.KeyStore@525b461a
```

[getInstance(File, char[])](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#getInstance(java.io.File,char%5B%5D)) なら、 `KeyStore` インスタンスの生成からキーストアファイルのロードまでを一回で実行できる。

キーストアのタイプは自動判定される。

## 全エントリのエイリアス名を取得する
```java:jshell
jshell> keystore.aliases().asIterator().forEachRemaining(System.out::println)
alice-key
aes-key
root-ca-cert
```

[aliases()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#aliases()) メソッドで、全エントリのエイリアス名を取得できる。

## エイリアス名からエントリの種類を確認する
```java:jshell
jshell> keystore.isKeyEntry("alice-key")
$12 ==> true

jshell> keystore.isCertificateEntry("alice-key")
$13 ==> false

jshell> keystore.isKeyEntry("root-ca-cert")
$14 ==> false

jshell> keystore.isCertificateEntry("root-ca-cert")
$15 ==> true
```

エイリアスが指すエントリが鍵エントリかどうかは [isKeyEntry()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#isKeyEntry(java.lang.String)) で確認できる。

信頼できる証明書エントリかどうかは、 [isCertificateEntry()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#isCertificateEntry(java.lang.String)) で確認できる。

## 秘密鍵を取得する
```java:jshell
jshell> var seckey = keystore.getKey("seckey", password)
seckey ==> javax.crypto.spec.SecretKeySpec@fffe8f5b

jshell> seckey.getAlgorithm()
$29 ==> "AES"
```

[getKey()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#getKey(java.lang.String,char%5B%5D)) で、指定したエイリアスの鍵を秘密鍵として取得できる。

第一引数はエイリアス名を渡し、第二引数は鍵を保護しているパスワード（普通はキーストアのパスワードと同じ）を渡す。

## プライベート鍵を取得する
```java:jshell
jshell> var alicePrivateKey = keystore.getKey("alice-key", password)
alicePrivateKey ==> SunRsaSign RSA private CRT key, 2048 bits
  param ... 96220784207006016968977089

jshell> alicePrivateKey.getClass()
$38 ==> class sun.security.rsa.RSAPrivateCrtKeyImpl
```

公開鍵ペアの鍵エントリに対して `getKey()` を使うと、プライベート鍵を取得できる。

## 公開鍵（証明書）を取得する
```java:jshell
// 証明書を取得
jshell> Certificate aliceCert = keystore.getCertificate("alice-key")
aliceCert ==> [
[
  Version: V3
  Subject: CN=alice
  Signature ... 3 D5  .........%$.m...

]

// 証明書から公開鍵を取得
jshell> PublicKey alicePublicKey = aliceCert.getPublicKey()
alicePublicKey ==> Sun RSA public key, 2048 bits
  params: null
  mo ... 9
  public exponent: 65537
```

公開鍵ペアの鍵エントリに対して [getCertificate()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#getCertificate(java.lang.String)) を使うと、公開鍵証明書（[java.security.cert.Certificate](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/cert/Certificate.html)）を取得できる。

## 証明書チェーンを取得する
```java:jshell
jshell> Certificate[] chain = keystore.getCertificateChain("alice-key")
chain ==> Certificate[3] { [
[
  Version: V3
  Subject: CN= ... C0  .;;.O...&.....N.

] }
```

[getCertificateChain()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#getCertificateChain(java.lang.String)) を使うと、指定したエントリに存在する証明書チェーンを `Certificate` の配列で取得できる。

## 秘密鍵エントリを追加する
```java:jshell
// 秘密鍵を生成
jshell> var secKey = KeyGenerator.getInstance("AES").generateKey()
secKey ==> javax.crypto.spec.SecretKeySpec@fffe806b

// 秘密鍵のエントリを生成
jshell> var secKeyEntry = new KeyStore.SecretKeyEntry(secKey)
secKeyEntry ==> Secret key entry with algorithm AES

// 秘密鍵エントリをキーストアに登録
jshell> var protection = new KeyStore.PasswordProtection(password)
protection ==> java.security.KeyStore$PasswordProtection@27d415d9

jshell> keystore.setEntry("sec-key", secKeyEntry, protection)

// 登録された秘密鍵エントリを取り出して確認
jshell> keystore.getKey("sec-key", password)
$10 ==> javax.crypto.spec.SecretKeySpec@fffe806b
```

キーストアにエントリを追加するには、 [setEntry()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#setEntry(java.lang.String,java.security.KeyStore.Entry,java.security.KeyStore.ProtectionParameter)) を使う。

第一引数は、エントリのエイリアス。
第二引数は、登録するエントリ。
第三引数は、エントリの保護に関するパラメータを渡す。

秘密鍵のエントリは、 [KeyStore.SecretKeyEntry](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.SecretKeyEntry.html) で作成する。

パラメータは、エントリを暗号化するときに使用するパスワードを渡すことになる。
一応キーストアとは別のパスワードを指定可能だが、同じにしておくのが無難？（keytool は、キーストアのタイプが PKCS12 ならキーストアと同じパスワードを使うようになっている）

## ファイルに出力する
```java:jshell
jshell> keystore.store(new FileOutputStream("alice.keystore"), password)
```

[store()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/KeyStore.html#store(java.io.OutputStream,char%5B%5D)) で、指定した出力ストリームに `KeyStore` の内容を出力できる。


# 証明書
## 新規作成する API はない？
証明書を表すクラスとして [java.security.cert.Certificate](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/cert/Certificate.html) というクラスが用意されており、このインスタンスを構築するためのクラスとして [java.security.cert.CertificateFactory](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/cert/CertificateFactory.html) というクラスが存在する。

しかし、 `CertificateFactory` は既存の証明書を読み込んで `Certificate` を構築するもので、ゼロから証明書を生成するものではない。

ざっと [java.security.cert](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/cert/package-summary.html) パッケージ を見ても、証明書を生成するようなクラスは見当たらない。

一応 Keytool の `-gencert` コマンドの実装を見てみたが、 `sun.security.x509` パッケージ（内部API）のクラスが使われていた。

標準 API だけで証明書を生成する方法はなさげ。
keytool を使って生成するしかなさそう。

## 証明書を読み込む
### 指定可能な証明書タイプ
エンコードされた証明書を読み込むには、 [CertificateFactory](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/cert/CertificateFactory.html) を使用する。

`getInstance()` で指定できる証明書のタイプは、標準機能としては `X.509` だけで、 Javadoc なども基本的に X.509 の説明が中心となって説明されている。

[CertificateFactory型 | Javaセキュリティ標準アルゴリズム名](https://docs.oracle.com/javase/jp/11/docs/specs/security/standard-names.html#certificatefactory-types)

ということで、ここでの説明も X.509 を前提として記述する。

### 証明書を１つずつ読み込む
```java:jshell
// CertificateFactory を生成
jshell> var factory = CertificateFactory.getInstance("X.509")
factory ==> java.security.cert.CertificateFactory@3abbfa04

// 読み込む証明書ファイル（alice.cer）の入力ストリームを生成
jshell> var in = new FileInputStream("alice.cer")
in ==> java.io.FileInputStream@31ef45e3

// 入力ストリームから証明書の情報を読み込む
jshell> factory.generateCertificate(in)
$5 ==> [
[
  Version: V3
  Subject: CN=alice
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
...
]

jshell> factory.generateCertificate(in)
$6 ==> [
[
  Version: V3
  Subject: CN=middle-ca
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
...
]

jshell> factory.generateCertificate(in)
|  例外java.security.cert.CertificateException: Could not parse certificate: java.io.IOException: Empty input
|        at X509Factory.engineGenerateCertificate (X509Factory.java:110)
|        at CertificateFactory.generateCertificate (CertificateFactory.java:355)
|        at (#7:1)
|  原因: java.io.IOException: Empty input
|        at X509Factory.engineGenerateCertificate (X509Factory.java:106)
|        ...
```

エンコードされた X.509 証明書を読み込むには、 [generateCertificate()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/cert/CertificateFactory.html#generateCertificate(java.io.InputStream)) を使用する。

対象データは、 DER エンコードされたバイナリ形式のデータか、もしくは PEM 形式のデータである必要がある。

対象データに複数の証明書のデータが含まれている場合、 `generateCertificate()` を呼び出すごとに先頭から１つずつ証明書を読み込むことができる。
（証明書がもう存在しないのにさらに読み込もうとすると、例外がスローされる）

### 複数の証明書をまとめて読み込む
```java:jshell
jshell> Collection<? extends Certificate> certs = factory.generateCertificates(new FileInputStream("alice.cer"))
certs ==> [[
[
  Version: V3
  Subject: CN=alice
  Signatur ...  70  ....$..&...2.n.p

]]

jshell> certs.size()
$12 ==> 2
```

[generateCertificates()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/cert/CertificateFactory.html#generateCertificates(java.io.InputStream)) なら、対象データ内のすべての証明書をまとめて読み込める。

## 証明書の署名を検証する
```java:jshell
// ↑で読み込んだ証明書のコレクションから、１つずつ証明書を抽出
jshell> var iterator = certs.iterator()
iterator ==> java.util.ArrayList$Itr@27808f31

// alice の証明書を取得
jshell> var aliceCert = iterator.next()
aliceCert ==> [
[
  Version: V3
  Subject: CN=alice
  Signature ... 3 D5  .........%$.m...

]

// 中間認証局の証明書を取得
jshell> var middleCaCert = iterator.next()
middleCaCert ==> [
[
  Version: V3
  Subject: CN=middle-ca
  Signa ... 0 70  ....$..&...2.n.p

]

// 中間認証局の公開鍵で、 alice の証明書を検証（検証OK）
jshell> aliceCert.verify(middleCaCert.getPublicKey())

// alice 自身の公開鍵で、 alice の証明書を検証（検証NG）
jshell> aliceCert.verify(aliceCert.getPublicKey())
|  例外java.security.SignatureException: Signature does not match.
|        at X509CertImpl.verify (X509CertImpl.java:459)
|        at X509CertImpl.verify (X509CertImpl.java:391)
|        at (#18:1)
```

証明書の署名を検証するには [verify()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/cert/Certificate.html#verify(java.security.PublicKey)) を使用する。

引数には、検証で使用する公開鍵を渡す。

検証が OK であれば何も起こらず、 NG の場合は例外がスローされる。

# 乱数
```java:jshell
// SecureRandom インスタンスを生成
jshell> var random = new SecureRandom()
random ==> Hash_DRBG,SHA-256,128,reseed_only

// 100 未満の乱数を取得
jshell> random.nextInt(100)
$3 ==> 91

// 512 ビットのランダムなバイト配列を生成
jshell> byte[] bytes = new byte[64]
bytes ==> byte[64] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... , 0, 0, 0, 0, 0, 0, 0, 0 }

jshell> random.nextBytes(bytes)

jshell> bytes
bytes ==> byte[64] { 97, 86, -17, 9, 81, 39, -18, -43, 40, -43, -21, 93, 112, -78, -1, 59, -5, 110, 115, 13, -86, 34, -81, -90, -107, -82, -102, -72, 19, 87, 9, -38, -91, -7, 47, -119, 103, -24, 52, -40, -3, 36, -9, -62, 10, -32, 36, 27, -74, -75, 58, -54, -124, -49, -6, 77, 78, 32, 96, 66, 67, -86, -39, -98 }
```

暗号用の乱数生成には、 [SecureRandom](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/SecureRandom.html) を使用する。

Java には乱数を生成するためのクラスとして [Random](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Random.html) クラスも存在する。
しかし、こちらは予測不可能性を持たないため、暗号技術には利用してはいけない。

`SecureRandom` は、他のエンジンクラスとは異なり [getInstace()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/SecureRandom.html#getInstance(java.lang.String)) 以外にも [コンストラクタ](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/SecureRandom.html#%3Cinit%3E()) でインスタンスを生成できる。

コンストラクタでインスタンスを生成した場合、優先度の高いプロバイダからアルゴリズムが選択される。

環境ごとにどのアルゴリズムが選択されるかは、 [SecureRandom実装 | 4 JDKプロバイダ・ドキュメント](https://docs.oracle.com/javase/jp/11/security/oracle-providers.html#GUID-9DC4ADD5-6D01-4B2E-9E85-B88E3BEE7453) で確認できる。


## getInstance() を使うべきか、コンストラクタを使うべきか
`SecureRandom` のインスタンスは、他のエンジンクラス同様 `getInstance()` で取得することもできるし、コンストラクタで取得することもできる。
前者はアルゴリズムを明示する必要があるが、後者はあらかじめ決められた優先順位によってアルゴリズムが決定する。

どちらを使って `SecureRandom` のインスタンスは生成すべきなのか？

Javadoc や JCA の公式ドキュメントを見ても、明確に「こちらを使え」と書かれているところは見つけられなかった。

いろいろ調べて出した個人的な結論は、「特に理由が無いのならコンストラクタを使う」となった（あくまで個人的な結論なので、間違っているかも）。

理由は、コンストラクタを使用した方が、環境ごとに最適なアルゴリズムを選択しつつ可搬性を維持することができるから。

Unix 系の環境では、デフォルトのアルゴリズムは `NativePRNG` になる。
しかし、このアルゴリズムは Windows 環境では使用できない。
つまり、アルゴリズムを `NativePRNG` 指定にしていると、そのプログラムは Windows には移植できないことになる。
（逆に、 `Windows-PRNG` は Unix 系環境では使用できない）

また、 Windows 環境では Java 8 までデフォルトのアルゴリズムは `SHA1PRNG` だった。
しかし、 Java 9 で `DRBG` が追加されたことで、最優先は `DRBG` に置き換わった（[JEP 273: DRBG-Based SecureRandom Implementations](https://openjdk.java.net/jeps/273)）。

`DRBG` は [NIST Special Publication 800-90A Revision 1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf) で説明されている疑似乱数生成方法で、 `SHA1PRNG` よりも強力らしい。

このようにアルゴリズムが新しいものに置き換わるといったケースにも対応しやすいので、コンストラクタを使ったほうが良いのかなと思う。
（あくまで、個人的な結論です）

## getInstanceStrong()
Java 8 で [getInstanceStrong()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/SecureRandom.html#getInstanceStrong()) というメソッドが追加されている。

名前に `Strong` とあるように、強力な乱数を生成するアルゴリズムが選択される。
公開鍵暗号の鍵ペア生成など、重要な値の生成のときに利用するらしい。

例えば Unix 系環境で使用すると、具体的なアルゴリズムは `NativePRNGBlocking` になる。

デフォルトで選択される `NativePRNG` と比べると何が違うのかというと、 [nextBytes()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/SecureRandom.html#nextBytes(byte%5B%5D)) と [generateSeed()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/SecureRandom.html#generateSeed(int)) が、それぞれ `/dev/random` と `/dev/urandom` のどちらを使用するかが異なる。

|アルゴリズム|`nextBytes()`|`generateSeed()`|
|-----------|-------------|----------------|
|`NativePRNG`           |`/dev/urandom`|`/dev/random`|
|`NativePRNGBlocking`   |`/dev/random` |`/dev/random`|
|`NativePRNGNonBlocking`|`/dev/urandom`|`/dev/urandom`|

[SecureRandom | 表4-4 SUNプロバイダでのアルゴリズム](https://docs.oracle.com/javase/jp/11/security/oracle-providers.html#d52720e815)

この `/dev/random` と `/dev/urandom` は乱数を生成するための疑似デバイスで、 Unix 系の OS で利用できる。

`/dev/random` は、環境ノイズを収集して乱数を生成するため、真の乱数を得ることができる。
しかし、情報が十分に集まっていない状態で乱数を得ようとすると、処理がブロック（待機）させられてしまう。

一方、 `/dev/urandom` は収集した情報を使い回すことで、処理をブロックすることなく乱数を生成できる。
しかし、情報を再利用しているため乱数としての安全性は `/dev/random` に劣る。

**参考**

- [/dev/random - Wikipedia](https://ja.wikipedia.org/wiki//dev/random)
- [【Linux】/dev/random と /dev/urandomの違い - Man On a Mission](https://oplern.hatenablog.com/entry/2017/05/29/221313)


`/dev/random` は、安全性は高いが、代わりにプログラムの性能を低下させる恐れがある。
`/dev/urandom` は、安全性は劣るが、代わりにプログラムの性能は落とさずに済む。

デフォルトで選択される `NativePRNG` は、シードの生成には真の乱数を得られる `/dev/random` を使用するが、通常の乱数生成では `/dev/urandom` を使用している。
つまり、 `NativePRNG` は安全性をやや落としつつも、性能は確保できるようになっている。

一方 `NativePRNGBlocking` は、常に `/dev/random` から乱数を得るようになっている。
つまり、性能は捨てて安全性に全振りしている。

プログラムの性能よりも安全性を優先しなければならない場面では `getInstanceStrong()` を使い、どちらも両立しなければならない場面ではコンストラクタでデフォルト実装を使うようにすればいいのだと思う（個人の意見）。


# Diffie-Hellman 鍵交換
Diffie-Hellman 鍵交換を実現するためのクラスとして、 [KeyAgreement](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/KeyAgreement.html) が用意されている。

`KeyAgreement` を用いた鍵交換の流れは、以下のような感じになる。

![jca.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/5ea91af7-8713-3079-fc47-4e326137a5b1.png)

以下、 Alice を表す jshell と Bob を表す jshell をそれぞれ立ち上げて、鍵の交換を試してみる（カレントディレクトリは２つとも同じ場所）。

```java:Alice
// 鍵ペアを生成する
jshell> var aliceKeyPairGen = KeyPairGenerator.getInstance("DH")
aliceKeyPairGen ==> java.security.KeyPairGenerator$Delegate@6d7b4f4c

// 鍵ペアを生成
jshell> var aliceKeyPair = aliceKeyPairGen.generateKeyPair()
aliceKeyPair ==> java.security.KeyPair@1786dec2

// Alice の KeyAgreement を生成
jshell> var aliceKeyAgree = KeyAgreement.getInstance("DH")
aliceKeyAgree ==> javax.crypto.KeyAgreement@2c039ac6

// Alice の秘密鍵で初期化
jshell> aliceKeyAgree.init(aliceKeyPair.getPrivate())

// Alice の公開鍵をファイルに出力
jshell> writeFile("alice-dh-public-key", aliceKeyPair.getPublic().getEncoded())

// 公開鍵のフォーマットは X.509
jshell> aliceKeyPair.getPublic().getFormat()
$32 ==> "X.509"
```

鍵交換には、まずは `KeyPairGenerator` を使って鍵ペアを生成する。
アルゴリズム名には `DiffieHellman` か、略称の `DH` を指定する。

この鍵ペアの公開鍵には、素数 $p$ と原始元 $g$、そして適当に選択された乱数 $a$ から計算した値 $y = g^{a} \bmod p$ が含まれている。

`KeyAgreement` も `DiffieHellman` （または `DH`）でインスタンスを取得し、秘密鍵で初期化しておく。

```java:Bob
// Alice の公開鍵を読み取り
jshell> var encodedAlicePublicKey = readFile("alice-dh-public-key")
encodedAlicePublicKey ==> byte[556] { 48, -126, 2, 40, 48, -126, 1, 27, 6,  ...  70, 34, 8, -3, -119, 14 }

// Alice のエンコードされた公開鍵を復元
jshell> var bobKeyFactory = KeyFactory.getInstance("DH")
bobKeyFactory ==> java.security.KeyFactory@7a9273a8

jshell> var alicePublicKeySpec = new X509EncodedKeySpec(encodedAlicePublicKey)
alicePublicKeySpec ==> java.security.spec.X509EncodedKeySpec@e874448

jshell> var alicePublicKey = bobKeyFactory.generatePublic(alicePublicKeySpec)
alicePublicKey ==> SunJCE Diffie-Hellman Public Key:
y:
    17cc14 ...
g:
    02
l:
    1024
```

Alice から受け取った公開鍵は X.509 形式なので、 [X509EncodedKeySpec](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/security/spec/X509EncodedKeySpec.html) を使って復元できる。

`KeySpec` から `Key` を生成するのは `KeyFactory` の役割なので、 `KeyFactory` を使って復元を行う。
アルゴリズム名は、 `DiffieHellman` または `DH` を指定する。

```java:Bob
// Bob の鍵ペアを生成
jshell> var bobKeyPairGen = KeyPairGenerator.getInstance("DH")
bobKeyPairGen ==> java.security.KeyPairGenerator$Delegate@42d8062c

jshell> var dhParams = ((DHPublicKey)alicePublicKey).getParams()
dhParams ==> javax.crypto.spec.DHParameterSpec@cb51256

jshell> bobKeyPairGen.initialize(dhParams)

jshell> var bobKeyPair = bobKeyPairGen.generateKeyPair()
bobKeyPair ==> java.security.KeyPair@5bcea91b
```

復元された Alice の公開鍵は、 [DHPublicKey](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/interfaces/DHPublicKey.html) という型になっている。
この [getPrams()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/interfaces/DHKey.html#getParams()) メソッドを使って、 Alice 側で生成された鍵交換の具体的なパラメータを取得する。

鍵交換のパラメータが取得できたら、その値から Bob 側の鍵ペアを生成する。

```java:Bob
// Bob の KeyAgreement を生成して、 Bob の秘密鍵で初期化
jshell> var bobKeyAgree = KeyAgreement.getInstance("DH")
bobKeyAgree ==> javax.crypto.KeyAgreement@27f723

jshell> bobKeyAgree.init(bobKeyPair.getPrivate())

// Bob の公開鍵をファイルに出力
jshell> writeFile("bob-dh-public-key", bobKeyPair.getPublic().getEncoded())
```

Bob 側の `KeyAgreement` は、 Bob の秘密鍵で初期化しておく。

そして、 Bob の公開鍵を Alice に送る。

```java:Alice
// Bob の公開鍵を読み取り
jshell> var encodedBobPublicKey = readFile("bob-dh-public-key")
encodedBobPublicKey ==> byte[557] { 48, -126, 2, 41, 48, -126, 1, 27, 6,  ... 5, -20, 52, -85, -95, 51 }

// Alice 側で Bob の公開鍵を復元
jshell> var aliceKeyFactory = KeyFactory.getInstance("DH")
aliceKeyFactory ==> java.security.KeyFactory@7b69c6ba

jshell> var bobPublicKeySpec = new X509EncodedKeySpec(encodedBobPublicKey)
bobPublicKeySpec ==> java.security.spec.X509EncodedKeySpec@12f41634

jshell> var bobPublicKey = aliceKeyFactory.generatePublic(bobPublicKeySpec)
bobPublicKey ==> SunJCE Diffie-Hellman Public Key:
y:
    f82361 ...
g:
    02
l:
    1024
```

Alice は、 Bob から受け取った公開鍵を復元する。
この手順は、 Bob が Alice の公開鍵を復元したときと同じ。

```java:Alice
// Bob の公開鍵の情報をもとに、秘密鍵を生成
jshell> aliceKeyAgree.doPhase(bobPublicKey, true)
$20 ==> null

jshell> var secret = aliceKeyAgree.generateSecret()
secret ==> byte[256] { 102, 50, -83, 94, 119, -90, -46, -27, ... 66, -94, -121, -19, -119 }
```

```java:Bob
// Alice の公開鍵の情報をもとに、秘密鍵を生成
jshell> bobKeyAgree.doPhase(alicePublicKey, true)
$25 ==> null

jshell> var secret = bobKeyAgree.generateSecret()
secret ==> byte[256] { 102, 50, -83, 94, 119, -90, -46, -27, ... 66, -94, -121, -19, -119 }
```

最後に、それぞれ `KeyAgreement` の [doPhase()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/KeyAgreement.html#doPhase(java.security.Key,boolean)) メソッドを呼ぶことで、秘密鍵生成の準備が完了する。

秘密鍵の生成は、 [generateSecret()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/javax/crypto/KeyAgreement.html#generateSecret()) で行う。

以下、生成された秘密鍵を使って暗号化と復号ができることを確認している。

```java:Alice
// 生成された鍵の先頭 128 ビットを使って AES の秘密鍵を生成
jshell> var secretKey = new SecretKeySpec(secret, 0, 16, "AES")
secretKey ==> javax.crypto.spec.SecretKeySpec@fffe86a4

// AES で暗号化
jshell> var cipher = Cipher.getInstance("AES/CBC/PKCS5PAdding")
cipher ==> javax.crypto.Cipher@12cdcf4

jshell> cipher.init(Cipher.ENCRYPT_MODE, secretKey)

// 暗号文を書き出し
jshell> writeFile("cryptograph", cipher.doFinal("Hello Diffie-Hellman!!".getBytes()))

// 初期化ベクトルを書き出し
jshell> writeFile("iv", cipher.getParameters().getEncoded())
```

```java:Bob
// Alice 同様、 AES 用の秘密鍵を生成
jshell> var secretKey = new SecretKeySpec(secret, 0, 16, "AES")
secretKey ==> javax.crypto.spec.SecretKeySpec@fffe86a4

// 初期化ベクトルをファイルから復元
jshell> var params = AlgorithmParameters.getInstance("AES")
params ==>

jshell> params.init(readFile("iv"))

// Cipher を復号モードで準備
jshell> var cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher ==> javax.crypto.Cipher@cb51256

jshell> cipher.init(Cipher.DECRYPT_MODE, secretKey, params)

// 暗号文を復号
jshell> var message = cipher.doFinal(readFile("cryptograph"))
message ==> byte[22] { 72, 101, 108, 108, 111, 32, 68, 105, 1 ... 08, 109, 97, 110, 33, 33 }

jshell> new String(message)
$31 ==> "Hello Diffie-Hellman!!"
```



# 参考
- [セキュリティ開発者ガイド | JDK 11ドキュメント](https://docs.oracle.com/javase/jp/11/security/preface.html)