暗号化についてまとめ

# 概要
暗号化の種類やハッシュ化と暗号化について記載する

# 暗号化に伴う基礎単語

## 暗号化
対象の内容をある一定の法則により変換することにより
第三者が見ても特別な知識なしでは読めないように変換すること
[wiki](https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7)
## 復号
暗号化された内容をもとに戻すこと
復号化とはいわないので注意([リンク先](http://kei-sakaki.jp/2013/08/09/encryption-and-decryption/)でわかりやすく解説)
## 平文
暗号化されていない元データのこと
[wiki](https://ja.wikipedia.org/wiki/%E5%B9%B3%E6%96%87)

# 暗号化の大まかな種類について
可逆暗号化と不可逆暗号化があり、使い方等全く異なる。以下に記載

## 可逆暗号化
読んで字のごとく、元に戻すことができる暗号化
zipの暗号化や通信の暗号化に使用

## 不可逆暗号化について
もとに戻すことができない暗号化のこと
一般的に`ハッシュ化`というと不可逆暗号化のことを指す
暗号化前の値が同じであれば必ず暗号化された際の値は同じになる方式

用途としては、主にパスワードをDB等に格納する場合
不可逆暗号化した値をDBに格納しておくことにより
たとえデータを見られても元の値がわからないが、
ユーザが入力したパスワードを同じように暗号化し、その結果を比較することで
正しいパスワードを入力したかの確認ができる

# 可逆暗号化の基本単語、アルゴリズム
暗号化に使用するアルゴリズムや基本単語を記載します

## 暗号鍵
同じ暗号方式を使用しながら利用者毎に暗号化結果を異なるものにするために使用するデータ
暗号鍵を使った暗号化には以下の２種類があります

* 共通鍵暗号化
暗号化と復号を同じ鍵をつかっておこなう暗号化のこと
共通鍵が第三者にわたると復号できてしまうので、共通鍵は厳重に保管する必要がある
また、デバイスに組み込む際は厳重に運搬を行い組み込む

* 公開鍵暗号
公開鍵で暗号化を行い、秘密鍵で復号する暗号化のこと 
公開鍵が第三者にわたっても復号できるわけではないので、SSH通信で使用されている

## 初期化ベクトル
同じデータを常に同じ暗号文に置き換えると、ある単語が複数暗号化文内に登場する場合、
平文が推測されてしまいます。
そのため、同じデータでも違う暗号文に置き換える仕組みが必要です。
そのため使用するのが初期化ベクトルです
[wiki](https://ja.wikipedia.org/wiki/%E5%88%9D%E6%9C%9F%E5%8C%96%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB)

## 共通鍵暗号化のアルゴリズム
よく使用される代表的なものを列挙

* AES
 * Advanced Encryption Standard
 * DESの安全性低下に伴い開発された、鍵長128bit超のブロック暗号。
 * zipやrarの暗号化等で用いられている。
* DES
  * Data Encryption Standard
  * 鍵長56bitのブロック暗号。
  * 鍵長が短すぎるため、現在では安全ではないとされるが、暗号化復号化処理を3回実行するトリプルDESという形で実用されている。

## ブロック暗号について
ブロック暗号とは、ある特定のビット数のまとまり(`ブロック長`という)の単位で暗号を行うアルゴリズムの総称です。
[リンク先](https://qiita.com/shoichi0599/items/6082b765c1257b71985b)の記事が詳しすぎるのでそちらで

## パディング方式
暗号化する際にブロック長の倍数に満たなかった際にバイトを保管します。
その際の方式は以下となります

* NoPadding  
パディングしない
* ZerobytePadding  
Nullバイトで埋める
ブロック長の倍数ぴったりの時はパディングされない  
このため、最終バイトがデータとして意味のあるNullなのか判定できないと使用できない
* PKS#5 Padding  
ブロック長に満たないサイズを示すバイト値で足りない分を埋める  
ブロック長の倍数長とだった場合は１ブロック分まるごとパディングされる

## 可逆暗号化/復号サンプルコード
Javaの標準ライブラリ内にあるコードを使用
(JDK1.8)

### 暗号化

~~~ java
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AesEncryptionUtil {
    /**
     * 指定バイトを暗号化します.
     *
     * @param encryptTarget  暗号化対象バイト.
     * @param initialVector  初期化ベクター.
     * @param secretKey      暗号化鍵.
     * @return 暗号化されたバイト.
     */
    public static byte[] encrypt(byte[] encryptTarget, byte[] initialVector,
                                 byte[] secretKey) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
        //鍵情報作成
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "AES");

        // 初期化ベクトルを作成する.
        IvParameterSpec iv = new IvParameterSpec(initialVector);

        // Cipherオブジェクト生成
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
        return cipher.doFinal(encryptTarget);
    }
}
~~~

### 複合 

``` java 
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
```
で指定する定数を以下のように変更するのみです

``` java 
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
```

`Cipher.getInstance(String)`で指定する文字列は`/`区切りで以下の順で指定

1. 暗号化アルゴリズム  
   上記サンプルでは`AES`
1. ブロック暗号のモード  
   上記サンプルでは`CBC`
1. パディング方式  
   上記サンプルでは`PKCS5Padding`

### 32bit暗号化の方法
[別の方の記事](https://qiita.com/oohira/items/18e55207e92d3d1f83d2)に詳細記載されているのでそちら参照

## 公開鍵の手順、アルゴリズム
### 公開鍵の手順
共通鍵と異なり、若干の手順があるのでシーケンス図で説明

![暗号化シーケンス.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242808/d239eba1-77fb-b2a0-4f43-4d5d1724a094.png)

### アルゴリズム
* RSA   
RSA暗号とは、桁数が大きい合成数の素因数分解問題が困難であることを安全性の根拠とした公開鍵暗号の一つである。 暗号化とデジタル署名を実現できる方式として最初に公開されたものである。 ([Wiki](https://ja.wikipedia.org/wiki/RSA%E6%9A%97%E5%8F%B7)より)

http://www.trustss.co.jp/Java/JEncrypt100.html

#### 暗号鍵の生成

以下の方法で公開鍵/秘密鍵を作成できる
ただし、以下の方法だとオレオレ証明書にしかならないので
検証時にのみ使用すること

~~~ java
import java.security.*;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

// 中略

        // 公開鍵・秘密鍵を生成する。
        KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA");
        kg.initialize(1024);
        KeyPair keyPair = kg.generateKeyPair();
        KeyFactory keyfactory = KeyFactory.getInstance("RSA");
        RSAPublicKeySpec publicKeySpec = keyfactory.getKeySpec(keyPair.getPublic(), RSAPublicKeySpec.class);
        RSAPrivateKeySpec privateKeySpec = keyfactory.getKeySpec(keyPair.getPrivate(), RSAPrivateKeySpec.class);
        PublicKey publicKey = keyfactory.generatePublic(publicKeySpec);
        PrivateKey privateKey = keyfactory.generatePrivate(privateKeySpec);
        
        //公開鍵のデータを取得
        //あとは任意の方法でデータ送信側に送付する
        byte[] publicKeyByte = publicKey.getEncoded();
        
        //秘密鍵のデータを取得
        //あとは任意の方法で安全に保管
        byte[] privateKeyByte = privateKey.getEncoded();
~~~

#### 公開鍵を使用した暗号化

~~~ java
    /**
     * 公開鍵を使用して暗号化します.
     * @param publicKeyData　公開鍵データ
     * @param encryptTarget 暗号化対象データ
     * @return 暗号化後のデータ
     */
    public static byte[] encryptByPublicKey(byte[] publicKeyData, byte[] encryptTarget) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
        //公開鍵を作成
        PublicKey key = new RSAPublicKeyImpl(publicKeyData);
        Cipher encryptWithPublicKey = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        encryptWithPublicKey.init(Cipher.ENCRYPT_MODE, key);
        // データを暗号化して返却
        return encryptWithPublicKey.doFinal(encryptTarget);
    }
~~~

#### 秘密鍵を使用した複合

~~~ java
    /**
     * 秘密鍵を使用して複合します.
     * @param privateKeyData 秘密鍵データ
     * @param encryptedData 暗号化されたデータ(複合対象データ)
     * @return 複合されたデータ
     * @throws Exception 発生例外
     */
    public static byte[] decryptByPrivateKey(byte[] privateKeyData,byte[] encryptedData) throws Exception{
        //秘密鍵を作成
        PrivateKey privateKey = RSAPrivateCrtKeyImpl.newKey(privateKeyData);
        Cipher decryptWithPrivateKey = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        decryptWithPrivateKey.init(Cipher.DECRYPT_MODE, privateKey);
        //複合して返却
        return decryptWithPrivateKey.doFinal(encryptedData);
    }
~~~

# 不可逆暗号化の基本単語、アルゴリズム
## 基本単語
### ハッシュ化
ハッシュ化とは、元のデータから一定のロジックでてハッシュ値と呼ばれる規則性のない固定長の値を求め、その値によって元のデータを置き換えること。

#### ハッシュ化の例(Java/String)

~~~ java
private final char value[];
private int hash; // Default to 0
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
~~~

元の値が同じであれば必ず得られる値は同じ

#### 暗号化ハッシュ関数
上記で示したものとは別に暗号化用に作成されたHash化関数がある
現在で有名なものは以下

* SHA-1/SHA-2
* MD5

### ストレッチング
上記のHash化を繰り返し行うこと
それを行うことによりより元の値を求めるのは困難となる

1. 元の値をハッシュ化する(得られた値をHash1と呼称)
2. Hash1をハッシュ化する
以下繰り返し

#### 注意点
ストレッチングを繰り返し実施すればするほど解読は困難になるが
その分処理速度が遅くなるので(当然だが)要件に応じて必要な回数を設定する

### ソルト
暗号化する元の分に加えてまとめて暗号化させるデータ
ソルトの値が異なると、元データが同じであっても、作られるハッシュ値はまったく別になる

以下例([Wiki](https://ja.wikipedia.org/wiki/%E3%82%BD%E3%83%AB%E3%83%88_(%E6%9A%97%E5%8F%B7))より転載)

|元データ|ソルト|ハッシュ化される値|ハッシュ値 = SHA256 (ソルト + パスワード)|
|--------|-----|------------------|----------------------------------------|
|password|E1F53135E559C253|password123E1F53135E559C253|72AE25495A7981C40622D49F9A52E4F1565C90F048F59027BD9C8C8900D5C3D8|
|password|84B03D034B409D4E|password12384B03D034B409D4E|B4B6603ABC670967E99C7E7F1389E40CD16E78AD38EB1468EC2AA1E62B8BED3A|

## Hash化を行うサンプルコード(Java)

~~~ java
    public static String encryptByMd5(byte[] source,int strechingCount,byte[] salt) throws NoSuchAlgorithmException {
        //SaltとSourceを結合
        byte[] encrypted = ArrayUtils.addAll(source,salt);
        //MessageDigestのインスタンスをアルゴリズム指定して作成
        MessageDigest md = MessageDigest.getInstance("MD5");
        for(int i = 0; i < strechingCount;i++){
            encrypted= md.digest(encrypted);
        }
        // 16進で返却
        return DatatypeConverter.printHexBinary(encrypted);
    }
~~~

``` java
MessageDigest.getInstance("MD5");
```
の`MD5`でアルゴリズムを指定しているので、ここを変えれば別のアルゴリズムで暗号化が行えます