golang で Cloud KMS を使って暗号化/復号化をしてみた。

こんばんわ！ @ktoshi です！
今回は機密な情報をDBなどで保管する際に有効な [Cloud KMS](https://cloud.google.com/kms/) を利用した話です。
昨今、クラウドサービスなどのAPIを利用する機会が増えていると思いますが、その認証情報をどのように保存するかは非常に重要な問題です。
現に私もその問題に少し…いやかなり悩まされました。
そんな折、Cloud KMS というサービスを利用をすることとなりました。

# Cloud KMS
暗号鍵をセキュアに管理できるGCPのサービスです。
自動ローテーションやIAMでの権限管理でセキュアなアクセスを実現できる上、APIを利用した暗号化/復号化を行えるので非常に柔軟な利用が可能です。
類似したサービスはAWSの「KMS」や Azureの「Key Vault」などがあります。

# Key Ring と Key
Cloud KMS では Key を Key Ring で管理しています。
関係性は字のごとく、鍵と鍵束ですね。
ある程度同じ用途で使用される鍵については同じ Key Ring でまとめておくとよいでしょう。
実際に暗号化などで利用されるのは Key となります。

# 目的
Cloud KMS に暗号鍵を用意し、golang + Cloud KMS で文字列を暗号化/復号化する。

# 実践
では実際に暗号化/復号化を行いましょう。
大きく分けて作業は3つです。

* 暗号鍵の準備
* 暗号化
* 復号化

※ 下記手順の[PROJECT_NAME]、[KEY_RING_NAME]、[KEY_NAME] は適宜置き換えてください。

## 暗号鍵の準備
暗号をするにはまず暗号鍵を準備しないことには始まりません。
私は terraform を利用しましたが、 gcloud コマンドでも簡単に作成できます。
terraform や gcloud のインストールについては話がそれるので割愛します。

### terraform の場合
```terraform
# 鍵束の作成
resource "google_kms_key_ring" "sample_key_ring" {
  name     = "[KEY_RING_NAME]"
  location = "global"
}

# 鍵の作成
resource "google_kms_crypto_key" "sample_crypto_key" {
  name     = "[KEY_NAME]"
  key_ring = google_kms_key_ring.sample_key_ring.self_link
}

```

### gcloud コマンドの場合
```shell
# 鍵束の作成
$ gcloud kms keyrings create [KEY_RING_NAME] --location global --project [PROJECT_NAME]

# 鍵の作成
$ gcloud kms keys create [KEY_NAME] --location global --keyring [KEY_RING_NAME] --purpose encryption --project [PROJECT_NAME]
```
これで暗号鍵の準備は完了です。

## 暗号化
では、実際に golang で暗号化を行います。
今回は処理を行っている箇所のみ抜粋しています。

```golang
import (
	"context"
	kms "cloud.google.com/go/kms/apiv1"
	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

func encryption(str string) ([]byte, error) {
	ctx := context.Background()
	client, err := kms.NewKeyManagementClient(ctx)
	if err != nil {
		return nil, err
	}

	request := &kmspb.EncryptRequest{
		Name:      "projects/[PROJECT_NAME]/locations/global/keyRings/[KEY_RING_NAME]/cryptoKeys/[KEY_NAME]",
		Plaintext: []byte(str),
	}

	response, err := client.Encrypt(ctx, request)
	return response.GetCiphertext(), err
}

```
これで暗号化した文字列を返すことができます。
なお、Cloud KMS では本気で暗号化されるため、帰ってきた文字列が UTF-8 ではなくなり、
DBなどに直接突っ込むことができません。
DBへ突っ込むときなどは以下のように Base64 でエンコードしてあげましょう。

```go
import (
	"encoding/base64"
	"fmt"
)

func main() {
    encString, _ := encryption("sample")
    // とりあえず、error は切り捨てる。。。
    encStringEncode := base64.StdEncoding.EncodeToString(encString)
    // encStringEncode を DB とかに突っ込む
}
```


## 復号化
暗号化処理はできたので、次に復元しましょう。
やってることはほぼ、暗号化と一緒です。

```golang
import (
    "context"
    kms "cloud.google.com/go/kms/apiv1"
    kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

func decryption(ciphertext []byte) (string, error) {
    ctx := context.Background()
    client, err := kms.NewKeyManagementClient(ctx)
    if err != nil {
        return nil, err
    }
    request := &kmspb.DecryptRequest{
        Name:      "projects/[PROJECT_NAME]/locations/global/keyRings/[KEY_RING_NAME]/cryptoKeys/[KEY_NAME]",
        Ciphertext: ciphertext,
    }
    response, err := client.Decrypt(ctx, request)
    return string(response.GetPlaintext()), err
}
```
これで復号化された文字列を取得できます。
なお、暗号化の際に Base64 でエンコードしている場合はデコードしてから渡しましょう

```go
import (
	"encoding/base64"
	"fmt"
)

func main() {
    encStringDecode := base64.StdEncoding.EncodeToString(encString)
    decString, _ := decryption(encStringDecode)
    // decString を 使ってほげほげ
    // とりあえず、error は切り捨てる。。。
}
```


# まとめ
今までパスワードなどを用いる時にはハッシュ化した値を保存し、
認証する際には同じくハッシュ化したもので比較することが多かったと思います。
しかし、今後はAPIの認証情報など可逆暗号化が求められるケースが増えてきます。
そんなときにはぜひ Cloud KMS を使ってみてはいかがでしょうか。
PHP でも Cloud KMS を使ったのでその記事も後日あげようと思います。
それでは、みなさまも安全で快適な暗号化ライフを。