ROBOT PAYMENT TECH-BLOG

株式会社ROBOT PAYMENTのテックブログです

AWS KMSを使用して暗号化、復号化をコーディングしてみよう(C#編)

こんにちは、決済サービスの開発を担当しているtaniguchikunです。
今回はAWSのKMSを使用して暗号鍵の発行や暗号化、復号化をするサンプルコードを実装してみたいと思います。
下記サンプルコードで暗号化と復号化に関する簡易的な処理は実装ができると思います

CMK(カスタマーマスターキー)を作ってみよう

CMKのキータイプには対称鍵と非対称鍵の二種類があります。

対称鍵とは俗に共通鍵暗号化方式の事を言います。
AES256-GCMで鍵が作成されます。
暗号化できる最大テキストサイズ:4096バイト(SYMMETRIC_DEFAULT)
非対称鍵とは俗に公開鍵暗号化方式の事を言います。
RSA-2048で鍵が作成されます。
キーと暗号化アルゴリズムで暗号化できるテキストサイズは違うので注意が必要です。

暗号化する文字数(テキストサイズ)の制限は下記ドキュメントに記載されています https://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html

CMKによる暗号化のサンプルコードは下記になります

CMKのKeyIdを指定して暗号化したい文字列、復号化したい暗号化された文字列をKMSにリクエストするとKMS側でCMKを用いて暗号化や復号化した文字列をレスポンスで得る事が出来ます。
なので小難しい事を考えて自身で暗号化処理や復号化処理を実装しなくて済みます。
※注意 暗号化する文字列は最大4KBになります 単純なサンプルコードを下記に記載したいと思います(対称鍵と非対称鍵)

// NugetPackageでAWS Encryption SDK for .Netを取り込んでください
async void Main()
{
    var input = "12345678901234567890";

    var client = new AmazonKeyManagementServiceClient("awsAccessKeyId", "awsSecretAccessKey", Amazon.RegionEndpoint.APNortheast1);
    using (var stream = new MemoryStream())
    {
        var data = Encoding.UTF8.GetBytes(input);
        stream.Write(data, 0, data.Length);

        var encResponse = await client.EncryptAsync(new EncryptRequest
        {
            EncryptionAlgorithm = EncryptionAlgorithmSpec.SYMMETRIC_DEFAULT, // 対称鍵
            EncryptionAlgorithm = EncryptionAlgorithmSpec.RSAES_OAEP_SHA_1, // 非対称鍵
            Plaintext = stream,
            KeyId = "CMKの対称鍵のKeyId"
        });

        var decResponse = await client.DecryptAsync(new DecryptRequest
        {
            KeyId = "CMKの対称鍵のKeyId",
            EncryptionAlgorithm = EncryptionAlgorithmSpec.SYMMETRIC_DEFAULT, // 対称鍵
            EncryptionAlgorithm = EncryptionAlgorithmSpec.RSAES_OAEP_SHA_1, // 非対称鍵
            CiphertextBlob = encResponse.CiphertextBlob
        });

        var num = Encoding.UTF8.GetString(decResponse.Plaintext.ToArray());

        Console.WriteLine($"入力値:{input}");
        Console.WriteLine($"出力値:{num}");
    }
}

エンベロープ暗号について

下記2つの鍵を別々の環境で管理する事でセキュリティ強度を上げることになります。
1. データを暗号化する暗号化鍵
2. 1の暗号化鍵を暗号化する暗号化鍵
- 詳細な内容は公式ドキュメントをご覧ください - https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html#enveloping

上記までのサンプルコードは下記に実装します(.Net 8で実装)

async void Main()
{
    // awsAccessKeyIdとawsSecretAccessKeyはIAMで作成出来ます

    var client = new AmazonKeyManagementServiceClient("awsAccessKeyId", "awsSecretAccessKey", Amazon.RegionEndpoint.APNortheast1);

    // CMKのKeyID
    // CMKはKMSのカスタマー管理型のキーで作成出来ます
    var keyId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

    // 平文の暗号化鍵は暗号化処理で使用後は使わない(dataKeyRes.Plaintext)
    // 保持して良いのは暗号化された復号化鍵のみ(dataKeyRes.CiphertextBlob)
    var dataKeyRes = await client.GenerateDataKeyAsync(new Amazon.KeyManagementService.Model.GenerateDataKeyRequest
    {
        KeySpec = Amazon.KeyManagementService.DataKeySpec.AES_256,
        KeyId = keyId
    });

    var key = Convert.ToBase64String(dataKeyRes.Plaintext.ToArray());
    Console.WriteLine($"暗号化鍵:{key}");

    var cardnumber = "12345678901234567890";
    Console.WriteLine($"暗号化前文字列:{cardnumber}");

    var encrypted_str = Aes256Manager.Encrypt(key, cardnumber);

    var encDataKey = Convert.ToBase64String(dataKeyRes.CiphertextBlob.ToArray());
    Console.WriteLine($"暗号化されたCDK:{encDataKey}");

    var ciphertextBlob = Convert.FromBase64String(encDataKey);
    var stream = new MemoryStream();
    stream.Write(ciphertextBlob, 0, ciphertextBlob.Length);

    var decRes = await client.DecryptAsync(new Amazon.KeyManagementService.Model.DecryptRequest
    {
        EncryptionAlgorithm = Amazon.KeyManagementService.EncryptionAlgorithmSpec.SYMMETRIC_DEFAULT,
        CiphertextBlob = stream
    });

    var descStr = Aes256Manager.Descrypt(Convert.ToBase64String(decRes.Plaintext.ToArray()), encrypted_str);

    Console.WriteLine($"復号化された文字列:{descStr}");
}

public class Aes256Manager
{
    private static string _aesIv = "1234567890123456";

    public static string Encrypt(string key, string encStr)
    {

        using (Aes aes = Aes.Create())
        {
            using (ICryptoTransform encryptor = aes.CreateEncryptor(Convert.FromBase64String(key), Encoding.UTF8.GetBytes(_aesIv)))                           // Encryptorを作成
            using (MemoryStream out_stream = new MemoryStream())
            {
                // 暗号化して書き出す
                using (CryptoStream cs = new CryptoStream(out_stream, encryptor, CryptoStreamMode.Write))
                using (StreamWriter sw = new StreamWriter(cs, Encoding.UTF8))
                {
                    sw.Write(encStr);
                }
                byte[] result = out_stream.ToArray();
                var encrypted_str = Convert.ToBase64String(result);

                Console.WriteLine($"AESで暗号化後の文字列:{encrypted_str}");

                return encrypted_str;
            }
        }
    }

    public static string Descrypt(string key, string encStr)
    {
        using (Aes aes = Aes.Create())
        {
            Console.WriteLine($"復号化鍵:{key}");

            var descStr = Convert.FromBase64String(encStr);

            using (ICryptoTransform decryptor = aes.CreateDecryptor(Convert.FromBase64String(key), Encoding.UTF8.GetBytes(_aesIv))) // Decryptorを作成
            using (MemoryStream in_stream = new MemoryStream(descStr))
            {
                // 復号化して読み出す
                using (CryptoStream cs = new CryptoStream(in_stream, decryptor, CryptoStreamMode.Read))
                using (StreamReader sr = new StreamReader(cs))
                {
                    var result = sr.ReadToEnd();
                    var decrypted_str = result.ToString();

                    Console.WriteLine($"AESで復号化後の文字列:{decrypted_str}");

                    return decrypted_str;
                }
            }
        }
    }
}

結果

暗号化鍵:0k1Pp7N/RWK94QgvXI+q6nQ+O6gDv/LtRAzwLMaZJKg=
暗号化前文字列:12345678901234567890
AESで暗号化後の文字列:YhKF6NT919nTjJdxsdkRL4uHcZh2FXMh+yscF536XkE=
暗号化された復号化鍵:AQIDAHhoLcz2IoEQ58BgvCPZ7WRD0X9x5e1UMZN/kMfWWrMeqgGH6DO7f9HC2oXHVBFbVUVvAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMlvwzYziJeR1ll21PAgEQgDtFPg/ldYQP3mM7klZHZT9TPlq1JsbyeL3TU/8e6PVX8zSWbbwyay6V2Yv3V6dr7mlBiX6RcFF4Enx9rA==
復号化鍵:0k1Pp7N/RWK94QgvXI+q6nQ+O6gDv/LtRAzwLMaZJKg=
AESで復号化後の文字列:12345678901234567890
復号化された文字列:12345678901234567890

以上でAWSのKMSを使用して暗号化と復号化に関する簡易なサンプルコードの実装を終わります。

参考資料 https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/requests-per-second.html

最後まで読んでいただきありがとうございます。 良いエンジニアライフに幸あれ!!



We are hiring!!

ROBOT PAYMENTでは一緒に働く仲間を募集しています!!!

speakerdeck.com
www.robotpayment.co.jp