🐟

AWS KMSで暗号・復号する

2023/04/04に公開

Key Management Service

AWSのKMSは、データの暗号・復号の他、署名の検証なども出来ます。

自社でクレデンシャルを生成・保存するときは、暗号・復号のデータで得たい情報の取得や、検証処理自体を別の小さいなサービスにしてしまうなどが理想的と言えそうですが、自社のデータストアーで管理する際は、暗号処理を行って、漏洩時のリスクに備える形が最低限必要な対策かと思います。

KMSのキー作成

キーは、暗号化および復号化目的で対称鍵で作成し、キーはKMS側で自動ローテーションする設定です。対称鍵の場合、自動ローテーションが可能で、ローテーションが自動でされるメリットは大きいです。
https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/rotate-keys.html

暗号化

getKeyUserCredentialsについて割愛しましたが、AwsCredentialIdentityを応答する関数で、KMSのキーユーザのクレデンシャルが必要です。

AWSのクライアントのバージョンが異なると実装が異なりますが、流れは以下の通りです。

  • クレデンシャルを取得
  • KMSクライアントを生成
  • 暗号化したいプレーンテキストとKMSのKeyID、暗号化の文脈になる情報をKeyValueで渡す
  • 暗号化されたテキストを取得

「暗号化の文脈になる情報」とは、暗号化・復号化する情報へのアクセスの条件を付け加えることができます。例えば、キーをUserId、値をログインしているユーザIDにすることで、暗号・復号の条件にそういった特定のユーザであることを条件付けるということです。
https://aws.typepad.com/sajp/2015/11/how-to-protect-the-integrity-of-your-encrypted-data-by-using-aws-key-management.html

import {DecryptCommand, EncryptCommand, KMSClient} from "@aws-sdk/client-kms"
import {AwsCredentialIdentity} from "@aws-sdk/types"

const awsKmsKeyId = "xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx"

const uint8ToBase64 = (arr: Uint8Array): string =>
  Buffer.from(
    Array(arr.length)
    .fill("")
    .map((_, i) => String.fromCharCode(arr[i]))
    .join(""), "binary").toString("base64")

const encryptByKms = async (
  plaintext: string,
  context: Record<string, string>,
): Promise<null | string> => {
  const credentials = await getKeyUserCredentials()
  if (!credentials) return null
  const client = new KMSClient({
    region: "ap-northeast-1",
    credentials,
  })
  const result = await client.send(new EncryptCommand({
    KeyId: awsKmsKeyId,
    Plaintext: new TextEncoder().encode(plaintext),
    EncryptionContext: context,
  }))
  const ciphertextBlob = result?.CiphertextBlob
  return ciphertextBlob ? uint8ToBase64(ciphertextBlob) : null
}

const plainText = "アクセストークン"
const userId = "特定のユーザID"
const cipherText = await encryptByKms(plainText, {userId})

復号化

暗号化同様に、クレデンシャルの取得方法については、自社のニーズに合わせて取得ください。
暗号化と同じような流れで復号化していきます。暗号時に条件付けした場合は、復号時も同一の条件である必要があります。

const decryptByKms = async (
  ciphertextBlob: string,
  context: Record<string, string>,
): Promise<null | string> => {
  const credentials = await getKeyUserCredentials()
  if (!credentials) return null
  const client = new KMSClient({
    region: "ap-northeast-1",
    credentials,
  })
  const result = await client.send(new DecryptCommand({
    KeyId: awsKmsKeyId,
    CiphertextBlob: Buffer.from(ciphertextBlob, "base64"),
    EncryptionContext: context,
  }))
  const plaintext = result?.Plaintext
  return plaintext ? Buffer.from(plaintext).toString() : null
}

const cipherText = "暗号化されたテキスト"
const userId = "特定のユーザID"
const plainText = await decryptByKms(cipherText, {userId})

最後に

AWS KMSを使うことで、簡単に自社のニーズに合わせてデータの保管を安全に行うことができます。

Discussion