ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

Unity - AES:AES を用いて、暗号化・復号化する -

初めに

 ゲームを作るとき、セーブデータやステータスなどデータをファイルで保存する際、暗号化していなければプレイヤーから書き換えが可能になってしまいます。そこで、本記事では AES を用い、暗号化・復号化する方法をまとめます。


AES (Advanced Encryption Standard)

AES とは

 Advanced Encryption Standard (AES) とは、

アメリカが2001年に標準暗号として定めた共通鍵暗号アルゴリズム

です。アメリカ国立標準技術研究所(NIST)が公募し、Rijndael(ラインダール)がAESとして採用されました。
※厳密には「AES」は、選出されなかった暗号も含む、手続き期間中から使われた「新しい標準暗号」の総称であり、Rijindael が採用されたようです。

ja.wikipedia.org

共通鍵暗号とは

 共通鍵暗号とは

暗号化と復号に同じ鍵を用いる暗号方式

です。

 比較対象として、公開鍵暗号があります。公開鍵暗号は暗号化のための鍵を公開しますが、共通鍵暗号は鍵を公開してはいけないので、秘密鍵暗号ともよばれます。

AES 処理の実装

 暗号化・復号するための関数がどこからでも呼び出せるようにしていきます。

コード全体
using System;
using System.IO;
using System.Text;
using UnityEngine;
using System.Security.Cryptography;  //AESを使うために追加

public static class AesExample
{
    // 初期化ベクトル"<半角16文字(1byte=8bit, 8bit*16=128bit>"
    private const string AES_IV_256 = @"mER5Ve6jZ/F8CY%~";
    // 暗号化鍵<半角32文字(8bit*32文字=256bit)>
    private const string AES_Key_256 = @"kxvuA&k|WDRkzgG47yAsuhwFzkQZMNf3";

    //暗号化のための関数
    //引数は暗号化したいデータ(string)
    public static byte[] EncryptStringToBytes_Aes(string plainText)
    {
        byte[] encrypted;

        using (Aes aesAlg = Aes.Create())
        {
            //AESの設定
            aesAlg.BlockSize = 128;  //
            aesAlg.KeySize = 256;
            aesAlg.Mode = CipherMode.CBC;
            aesAlg.Padding = PaddingMode.PKCS7;

            aesAlg.IV = Encoding.UTF8.GetBytes(AES_IV_256);
            aesAlg.Key = Encoding.UTF8.GetBytes(AES_Key_256);

            // Create an encryptor to perform the stream transform.
            ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

            // Create the streams used for encryption.
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        //Write all data to the stream.
                        swEncrypt.Write(plainText);
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
        }

         // Return the encrypted bytes from the memory stream.
         return encrypted;
    }

    //復号のための関数
    //引数は暗号化されたデータ(byte[])
    public static string DecryptStringFromBytes_Aes(byte[] cipherText)
    {
        string plaintext = null;

        using (Aes aesAlg = Aes.Create())
        {
            //AESの設定(暗号と同じ)
            aesAlg.BlockSize = 128;
            aesAlg.KeySize = 256;
            aesAlg.Mode = CipherMode.CBC;
            aesAlg.Padding = PaddingMode.PKCS7;

            aesAlg.IV = Encoding.UTF8.GetBytes(AES_IV_256);
            aesAlg.Key = Encoding.UTF8.GetBytes(AES_Key_256);

            // Create a decryptor to perform the stream transform.
            ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

            // Create the streams used for decryption.
            using (MemoryStream msDecrypt = new MemoryStream(cipherText))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    {
                        // Read the decrypted bytes from the decrypting stream
                        // and place them in a string.
                        plaintext = srDecrypt.ReadToEnd();
                    }
                }
            }
        }

        return plaintext;
    }
}
コードの説明:名前空間

 データの暗号や復号に関する操作をするために次の名前空間を使います。

System.Security.Cryptography

 System.Security.Cryptography のクラスを使うために、名前空間に次の記述を追加します。

using System.Security.Cryptography;

 今回は、System.Security.Cryptography の AES クラスを用います。

コードの説明:鍵・初期化ベクトル・AESの設定

 鍵、初期化ベクトルの設定を初めにしています。

 今回は、AES の設定でブロックサイズを 128、鍵のサイズを 256 にするため、初期化ベクトル、暗号化鍵をそれぞれ 128 ビットの半角16文字、256 ビットの32文字にします。

// 初期化ベクトル"<半角16文字(1byte=8bit, 8bit*16=128bit>"
private const string AES_IV_256 = @"ここに半角16文字入力";
// 暗号化鍵<半角32文字(8bit*32文字=256bit)>
private const string AES_Key_256 = @"ここに半角32文字入力";

 また、暗号化関数、復号関数の中で AES の設定をします。

 AES クラスのインスタンスを作り、設定を記述しています。

using (Aes aesAlg = Aes.Create())  //AES クラスのインスタンスを作る
{
    //AESの設定(暗号と同じ)
    aesAlg.BlockSize = 128;
    aesAlg.KeySize = 256;
    aesAlg.Mode = CipherMode.CBC;
    aesAlg.Padding = PaddingMode.PKCS7;

    aesAlg.IV = Encoding.UTF8.GetBytes(AES_IV_256);  //初期ベクトルを string から byte[] に変換して代入
    aesAlg.Key = Encoding.UTF8.GetBytes(AES_Key_256);  //鍵を string から byte[] に変換して代入

    //以下、暗号化・復号のための記述があります。
}

 今回の設定は次のようにしています。

  • ブロックサイズは 128 ビット
  • 鍵のサイズは 256 ビット
  • CipherMode(暗号化に使用するブロック暗号モード)は CBC
  • Padding は既定値である PKC57

 CipherMode から暗号化に使用するブロック暗号モードを5種類(CBC、CFB、CTS、ECB、OFB)の中から指定します。違いについては、ドキュメントをご覧ください。
learn.microsoft.com

 Padding(パディング)はブロック暗号方式において文字を足す事を意味します。64ビットや 128ビットなどのブロック単位で暗号化するため、元データがブロックサイズの整数倍でない場合、パディングを追加して整数倍にする必要があります。その方式を指定します。基本的には、 PKC57 で問題なさそうです。
learn.microsoft.com

コードの説明:暗号化・復号

(暗号化)
 引数(string)で与えられたデータを暗号化し、byte[] で返す関数になっています。

//暗号化のための関数
//引数は暗号化したいデータ(string)
public static byte[] EncryptStringToBytes_Aes(string plainText)
{
    byte[] encrypted;

    using (Aes aesAlg = Aes.Create())
    {
        //AESの設定
        //略

        // Create an encryptor to perform the stream transform.
        ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

        // Create the streams used for encryption.
        using (MemoryStream msEncrypt = new MemoryStream())
        {
            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            {
                using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                {
                    //Write all data to the stream.
                    swEncrypt.Write(plainText);
                }
                encrypted = msEncrypt.ToArray();
            }
        }
    }

     // Return the encrypted bytes from the memory stream.
     return encrypted;
}

 基本的な流れは、

  1. 引数として与えられたデータ(string plainText)を StreamWriter を使って csEncrypt に書き込み
  2. CryptoStream のインスタンスである csEncrypt が暗号化したものを MemoryStream である msEncrypt に渡す
  3. 暗号化したもの(msScript)を ToArray をつかって、byte[] にし、return

となっています。

(復号)
 ほぼ暗号化と同じです。主に、異なる部分は

  • 引数が byte[] に、返り値が string になった
  • CreateEncryptor から CreateDecryptor になった
  • StreamWriter から StreamReader になった

となっています。暗号を受け取り、復号し、読み込み、return するという流れで構成されています。

 暗号化・復号のコードは AES クラスのドキュメントの例を参考にしています。各関数についてもすべてまとめてありますので、ぜひご覧ください。
learn.microsoft.com

確認

 次のスクリプトを使って暗号・復号ができているか確認をします。

using System.Collections;
using System.Collections.Generic;
using System;
using System.Text;
using System;
using UnityEngine;

public class Aes_Test : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        string text = "サンプルテキスト"; //これを暗号・復号する

        //暗号化
        byte[] encrypted = AesExample.EncryptStringToBytes_Aes(text);

        //Debug.Logで確認しやすいようにstringに変換
        string encryptedtext = BitConverter.ToString(encrypted);
        Debug.Log($"暗号{encryptedtext}");

        //復号
        string decrypted = AesExample.DecryptStringFromBytes_Aes(encrypted);

        Debug.Log($"復号:{decrypted}");
    }
}

(結果)

補足

Rijndael クラス、RijndaelManaged クラスの廃止

 System.Security.Cryptography には、Rijndael クラスや RijndaelManaged クラスがあります。こちらを使っても同じことができましたが、ドキュメントを確認すると、これらのクラスは廃止されたので、AES クラスを代わりに使ってくださいと書いてあります。

Json ファイルに暗号化したものを保存

 暗号化したものを Json ファイルに書き込み、読み込みすることもできます。その場合、Json ファイルには byte[] ではなく string の形で書き込むため、次のように変換してから書き込みます。

(暗号化)

//暗号化のための関数
//返り値を byte[] から string に変更
public static string EncryptStringToBytes_Aes(string plainText)
{
    byte[] encrypted;

/* - 変更がないので省略 - */

     // 返り値を string に変換してreturn
     return (System.Convert.ToBase64String(encrypted));
}

(復号)

//引数をbyte[] から string に変更
public static string Decrypt(string cipher)
{
/* 略 */
    //MemoryStream の引数は byte[] なので、string から byte[] への変換が追加された
    using (MemoryStream msDecrypt = new MemoryStream(System.Convert.FromBase64String(cipherText))))
    {
        /* 略 */
    }
}

 あとは、Json ファイルに書き込む前に暗号化、Json ファイルを読み込んだ後に復号化すればよいです。Json ファイルの書き込み、読み込みについては、下の記事をご覧ください。
fineworks-fine.hatenablog.com

Quick Save を使う方法

 セーブデータを暗号化したい場合は Quick Save という無料アセットを用いる方法もあります。説明は下の記事をご覧ください。

fineworks-fine.hatenablog.com

最後に

 AES を使った暗号化・復号についてまとめました。初めから byte[] ではなく、Json ファイルの保存の部分で補足したように、引数も返り値も string の形のほうが使いやすいかもしれません。