AWS KMSをSwiftで使う

AWS KMSを使って暗号化、復号化を行います。
データキーによる暗号化も出来るのですが、今回はマスターキーを使った暗号化をやります。

# 環境
XCode 9.4.1
Swift 4.1

# 手順
## CognitoでPool idを作成
CognitoはKMSというよりもAWS SDKそのものを使うために必要です。

<img width="1418" alt="スクリーンショット 2018-10-10 10.41.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/225d65b7-8aa4-d1eb-b50c-f1149ccca631.png">

「IDプールの管理」をクリックします。

<img width="1431" alt="スクリーンショット 2018-10-10 10.41.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/d629df74-9386-d413-31a0-cd876a31ce4b.png">

「新しいIDプールの作成」をクリック

<img width="1436" alt="スクリーンショット 2018-10-10 10.42.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/260c43da-5761-2e68-ffd1-10a16c1fe86c.png">

IDプール名を入力し、「認証されていないIDに対しアクセスを有効にする」にチェックを入れます。
「プールの作成」ボタンを押すとIDプールが作成されます。

<img width="1146" alt="スクリーンショット 2018-10-10 11.04.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/a19fd802-a94b-45e9-4337-579a443aa479.png">

ここのプールIDが後々必要になります。

## KMSでマスターキーを作成

IAMを開きます。

<img width="1435" alt="スクリーンショット 2018-10-10 10.45.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/74dcefd8-7b7b-1cd7-6411-53a0a6896efa.png">

マスターキーを作る前にCognitoのロールに対し権限を追加します。

<img width="1437" alt="スクリーンショット 2018-10-10 10.46.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/e3194796-587c-0acd-1c53-68b08428f8ff.png">

Cognitoの2つのロールに対しAWSKeyManagementServicePowerUserを付加しました。

<img width="1436" alt="スクリーンショット 2018-10-10 10.47.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/e347a2f2-4739-a789-589e-dabe4a5f2014.png">

いよいよマスターキー作成です。
KMSは単独のサービスではなく、IAMの中の「暗号化キー」にあります。

ここで一つ注意があります。
**上記画面はリージョンが米国東部になっていますので、必ず東京に変更してから操作を始めてください。**

<img width="1436" alt="スクリーンショット 2018-10-10 10.47.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/a261dfc6-faa5-15d7-42c9-20246a8b5bc9.png">

手順に従い進めます。

<img width="1437" alt="スクリーンショット 2018-10-10 10.48.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/b4b546ac-b1b6-efb5-0790-348d8e173767.png">

この辺りは適当に

<img width="1443" alt="スクリーンショット 2018-10-10 10.49.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/2e149ead-a124-432a-b56d-548e12fbdae2.png">

マスターキーの管理ユーザーを指定します。

<img width="1438" alt="スクリーンショット 2018-10-10 10.49.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/23b1c00c-d09b-ca51-3d13-a0b98c4207a8.png">

キーの使用許可ユーザーを指定します。
私はここに、管理ユーザーと先程のCognitoの2ロールを指定しました。

<img width="1438" alt="スクリーンショット 2018-10-10 10.49.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/575af3cd-d4f2-92cc-899a-6496482d14cc.png">

プレビューして完了です。

<img width="1204" alt="スクリーンショット 2018-10-10 11.14.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/dfd20900-78d8-a261-bdb9-14e231298971.png">

このARNを後で使います。

## Cocoapodでインストール
```podfile
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'awskmsTest' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for awskmsTest
  pod 'AWSKMS'

end
```

## Cognitoによる初期化

```swift:AppDelegate.swift
import AWSCore
import AWSKMS

let credentialsProvider = AWSCognitoCredentialsProvider(regionType:.APNortheast1,identityPoolId:"YOUR_POOL_ID")
let configuration = AWSServiceConfiguration(region:.APNortheast1, credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
```


## 暗号化、復号化
```swift:hoge.swift
let str = "本日は晴天なり"
let client = AWSKMS.default()

let encrypt = AWSKMSEncryptRequest()
let data_ = str.data(using: .utf8)!

var encryptData:Data?

encrypt?.plaintext = data_
encrypt?.keyId = "YOUR KEY ID"
client.encrypt(encrypt!).continueWith { (task: AWSTask<AWSKMSEncryptResponse>) -> Any? in
    let response = task.result
    print(task.error ?? "(nil)")
    print("Encrypted data")
    print(response?.ciphertextBlob?.base64EncodedString() ?? "(nil)")

    encryptData = response?.ciphertextBlob
    return nil
} .waitUntilFinished()

let decrypt = AWSKMSDecryptRequest()
decrypt?.ciphertextBlob = encryptData!

client.decrypt(decrypt!).continueWith { (task: AWSTask<AWSKMSDecryptResponse>) -> Any? in
    let response = task.result
    print(task.error ?? "(nil)")
    print("Decrypted data")
    print(String(data: (response?.plaintext)!, encoding: .utf8) ?? "(nil)")
    return nil
} .waitUntilFinished()
```

# パケットキャプチャで確認
Charles使って中身を確認してみます。

<img width="1321" alt="スクリーンショット 2018-10-09 14.32.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/1a21a615-2bfa-3fe6-4a66-4ace34b22013.png">

~~これはencryptのjsonなんですが、Plaintextの「本日は晴天なり」もそのまま送っているのではないようです。
byte列を16進化しただけであればF以降の文字は出ませんし。~~
node.jsのソースを見てbase64エンコードと気付きました。失礼しました。

# エラー、トラブル等
## An error occurred (NotFoundException) when calling the Encrypt operation: Invalid arn
リージョンを全て合わせておかないとエラーになるようです。
今回は全て東京で合わせましたが、最初は米国東部で作ってしまったため発見までに時間がかかりました。

## コードからマスターキーの生成が出来ない
ロールに対し権限を付与しているにも関わらず、権限が無いとのことで作成できませんでした。
この辺りは追加で調査が必要です。

# おまけ：Lambdaに暗号化データを送って復号化する方法
こちらは参考記事がありました。
https://qiita.com/na0AaooQ/items/cfd9f2babd2ce604049b
https://qiita.com/Yuki_BB3/items/5a95bcef2e6d6156e749

# 参考
https://github.com/aws/aws-sdk-ios/issues/681
http://techtipshoge.blogspot.com/2017/08/kms.html
https://teratail.com/questions/97100