CryptoSwiftでAES暗号化する + PKCS#5とか#7のはなし

Swiftで文字列をAESで暗号化したい。

のですが、Security FrameworkはObjective-C向けのAPIしか提供していません。
しかもC インタフェースなのか…

* [Security \| Apple Developer Documentation](https://developer.apple.com/reference/security)

ということで、Swiftで利用可能な暗号化ライブラリを調べていたところ、CryptoSwiftが見つかったので利用してみます。

* [krzyzanowskim/CryptoSwift: CryptoSwift is a growing collection of standard and secure cryptographic algorithms implemented in Swift](https://github.com/krzyzanowskim/CryptoSwift#why)

READMEのWhyの項に、**'Why? Because I can.'** と書かれていてかなりCoolです。

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">Coolだ<a href="https://t.co/G6StAvFC35">https://t.co/G6StAvFC35</a></p>&mdash; てんてん (@tenten0213) <a href="https://twitter.com/tenten0213/status/829529863472062465">February 9, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

機能はREADMEの[Features](https://github.com/krzyzanowskim/CryptoSwift#features)に載っていて、Hash、CRC、AES、Blowfish等の暗号化、HMAC、PBKDFなどが提供されています。
一通り使いそうな機能が揃っているので、良さそうです。

## 試してみる

READMEの[All at once](https://github.com/krzyzanowskim/CryptoSwift#all-at-once)通りにやってみると以下のようになります。
AESのイニシャライザに`key`と`iv`を設定します。
`iv`([初期化ベクトル](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))はオプショナルで、Blockmode([暗号利用モード](https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7%E5%88%A9%E7%94%A8%E3%83%A2%E3%83%BC%E3%83%89))は省略すると[CBC](https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7%E5%88%A9%E7%94%A8%E3%83%A2%E3%83%BC%E3%83%89#Cipher_Block_Chaining_.28CBC.29)が、Paddingは省略すると[PKCS](https://ja.wikipedia.org/wiki/PKCS)7がデフォルトで選ばれます。

encryptの結果はバイト配列(`Array<UInt8>`)で返却されます。HexStringにして比較しています。

```swift
do {
    let aes = try AES(key: "passwordpassword", iv: "drowssapdrowssap") // aes128
    // let aes = try AES(key: "passwordpassword", iv: "drowssapdrowssap", blockMode: .CBC, padding: PKCS7()) と同等
    let ciphertext = try aes.encrypt(Array("Nullam quis risus eget urna mollis ornare vel eu leo.".utf8))
    XCTAssertEqual(ciphertext.toHexString(), "29951512f09c87d81e4ce8640f7cf8e348d4120f3df1807cfee22eadea19bb66ecfaba40259ffb5a6aa1f724f92115a39bcb63c0f519faec5a4275415e962daf")
} catch { }
```

### AES-256で暗号化するには

AES-256で暗号化するということは`256ビット長の暗号鍵を用いて暗号化する`ということなので、256ビット(32バイト)の暗号鍵を引数で渡します。

CBCではブロックサイズと同サイズの初期化ベクトルが必要です。
AESでは128ビット(16バイト)のブロックサイズを使用するので、128ビット(16バイト)の初期化ベクトルを引数で渡します。

```swift
do {
    let aes = try AES(key: "passwordpasswordpasswordpassword", iv: "drowssapdrowssap")
    let ciphertext = try aes.encrypt(Array("Nullam quis risus eget urna mollis ornare vel eu leo.".utf8))
    XCTAssertEqual(ciphertext.toHexString(), "5105c187e62c6f2c07ce9c0d5966ae01d2221f8a736e89f52dcc7fbf6d32dfd1b5db8b0a4e468dea273678b4be255a50811bfe14b6cf3a3a9116baac77d3bcfc")
} catch { }
```

#### 暗号化モードECBで行う場合

こんな感じです。
ECBは初期化ベクトルを必要としないため、インスタンス化しただけのStringを渡しています。

```swift
do {
    let aes = try AES(key: "passwordpasswordpasswordpassword", iv: String(), blockMode: .ECB, padding: PKCS7())
    let ciphertext = try aes.encrypt(Array("Nullam quis risus eget urna mollis ornare vel eu leo.".utf8))
    XCTAssertEqual(ciphertext.toHexString(), "7693ce6d6a8a0dd4626aca32441bbede21e287c71997b85cf71e59bb3997a2a7623c49eecf345b2511c113326712f33526d70110c756fd8d2550388afba53da5")
} catch { }
```

ECBモードは脆弱であるため、暗号化プロトコルとしての使用は推奨されません。採用する際は注意してください。

```
ECBモードの欠点は、同じ鍵を用いた場合ある平文ブロックを暗号化した結果の暗号文ブロックが常に同じとなることである。
このため、データのパターンを隠蔽することができない。
メッセージの機密性の保持には向かず、暗号化プロトコルにおける使用は推奨されない。
同じ入力に対して常に同じ出力を返すことから、ECBモードは反射攻撃に対しても脆弱である。
```
https://ja.wikipedia.org/wiki/暗号利用モード

## PKCS#5とか#7のはなし

PKCS#7と類似したパディング方式に[PKCS#5](https://tools.ietf.org/html/rfc2898)というものがあります。

PKCS#5はPKCS#7のサブセットで、PKCS#7は1〜255バイトの可変長ブロックに対応しているのに対してPKCS#5は8バイトのブロックにしか対応していません。
AESは16バイトのブロックサイズを使用するので、8バイトのブロックしか対応していないPKCS#5は、厳密にはAESで利用できません。

担当しているプロジェクトのサーバサイドはJavaで実装されているのですが、Javaの暗号化アーキテクチャ([JCA](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/crypto/CryptoSpec.html))にはPKCS#7を指定できるような定義はありません。

```
Javaプラットフォームの実装は、すべて次の標準のCipher変換とカッコ内の鍵サイズをサポートする必要があります。
AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
DES/CBC/NoPadding (56)
DES/CBC/PKCS5Padding (56)
DES/ECB/NoPadding (56)
DES/ECB/PKCS5Padding (56)
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
RSA/ECB/PKCS1Padding (1024、2048)
RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024、2048)
RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024、2048)
これらの変換については、Java暗号化アーキテクチャ標準アルゴリズム名のドキュメントのCipherのセクションで説明されています。サポートされているその他の変換については、実装のリリース・ドキュメントを
```
* https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Cipher.html

ただ、上記定義の通りAESのサポートをしており、PKCS#5のパディングを指定することが可能です。
実際はPKCS#7の処理が行われるとのことなので、上でCryptoSwiftを利用してAES-128で暗号化した処理(一番最初の例)と同様の処理をJavaで実装してみました。

`Cipher.getInstance("AES/CBC/PKCS5Padding")` とPKCS#5を指定していますが、同様の値が返却されていることが確認できます。

```java
Key key = new SecretKeySpec("passwordpassword".getBytes(), "AES");
IvParameterSpec ips = new IvParameterSpec("drowssapdrowssap".getBytes());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ips);
byte[] enc = cipher.doFinal("Nullam quis risus eget urna mollis ornare vel eu leo.".getBytes());
String actual = new String(Hex.encodeHex(enc)); // org.apache.commons.codec.binary.Hex

assertThat(actual, is("29951512f09c87d81e4ce8640f7cf8e348d4120f3df1807cfee22eadea19bb66ecfaba40259ffb5a6aa1f724f92115a39bcb63c0f519faec5a4275415e962daf"));
```

サーバはPKCS#5で、CryptoSwiftはPKCS#7しか対応していない！どうしよう！ってちょっと焦りましたが、どうやら問題無さそうです。

### 参考

* http://crypto.stackexchange.com/questions/11272/can-aes-use-pkcs5-padding
* http://stackoverflow.com/questions/25942165/aes-256-and-pkcs7padding-fails-in-java