Unity で MessagePack for C# を利用する(暗号化)
※ これは 2022/02/03 時点の Unity 2021.2.10f1、MessagePack for C# v2.3.85 の情報です
最新版では動作が異なる可能性がありますのでご注意ください
前回、MessagePack for C# のファイルへの読み書きができたので、さらに暗号化・復号化を組み合わせて堅牢にしてみる
とりあえず下記の ICryptPersistentData
とその拡張クラスを用意
CryptIV
と CryptKey
は適当な半角16文字の文字列を指定する
using Cysharp.Threading.Tasks; using MessagePack; using System; using System.IO; using System.Security; using System.Security.Cryptography; namespace MatatabiUx.Common { /// <summary> /// 暗号化する永続化データのインタフェース /// </summary> public interface ICryptPersistentData {} /// <summary> /// 暗号化する永続化データを処理する拡張クラス /// </summary> public static class ICryptPersistentDataExtentions { /// <summary> /// 暗号化 IV /// </summary> private const string CryptIV = @"matatabi_cat_eye"; /// <summary> /// 暗号化 Key /// </summary> private const string CryptKey = @"cafe_mocha_choco"; /// <summary> /// 暗号化アルゴリズム /// </summary> /// <param name="CreateAlgorithm()"></param> /// <returns></returns> public static SymmetricAlgorithm Algorithm = algorithm ?? (algorithm = CreateAlgorithm()); private static SymmetricAlgorithm algorithm = null; private static SymmetricAlgorithm CreateAlgorithm() => new RijndaelManaged() { BlockSize = 128, KeySize = 128, Padding = PaddingMode.Zeros, Mode = CipherMode.CBC, Key = System.Text.Encoding.UTF8.GetBytes(CryptKey), IV = System.Text.Encoding.UTF8.GetBytes(CryptIV), }; /// <summary> /// 暗号化器 /// </summary> private static ICryptoTransform Encryptor = encryptor ?? (encryptor = Algorithm.CreateEncryptor()); private static ICryptoTransform encryptor = null; /// <summary> /// 復号化器 /// </summary> private static ICryptoTransform Decryptor = decryptor ?? (decryptor = Algorithm.CreateDecryptor()); private static ICryptoTransform decryptor = null; /// <summary> /// ファイルから暗号化データを読み込む /// </summary> /// <param name="data">読み込むデータモデル</param> /// <param name="path">読み込み先パス</param> /// <typeparam name="T">読み込むデータモデルの型</typeparam> /// <returns>読み込んだデータ、ファイルがなかった場合はデフォルト値</returns> public static async UniTask<T> LoadAsync<T>(this T data, string path) where T : ICryptPersistentData, new() { if (!File.Exists(path)) { return new T(); } try { using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) using (var cryptStream = new CryptoStream(fileStream, Decryptor, CryptoStreamMode.Read)) return await MessagePackSerializer.DeserializeAsync<T>(cryptStream); } catch (IOException) { } catch (UnauthorizedAccessException) { } catch (SecurityException) { } return default; } /// <summary> /// ファイルに暗号化データを書き込む /// </summary> /// <param name="data">書き込むデータモデル</param> /// <param name="path">書き込み先パス</param> /// <typeparam name="T">書き込むデータモデルの型</typeparam> /// <returns>書き込みが成功した場合は true, それ以外は false</returns> public static async UniTask<bool> SaveAsync<T>(this T data, string path) where T : ICryptPersistentData, new() { try { using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write)) using (var cryptStream = new CryptoStream(fileStream, Encryptor, CryptoStreamMode.Write)) await MessagePackSerializer.SerializeAsync(cryptStream, data); return true; } catch (IOException) { } catch (UnauthorizedAccessException) { } catch (SecurityException) { } return false; } } }
次に ICryptPersistentData
を継承した CryptData.cs
を作る
SaveData.cs
で記述した内容とあまり変わらず、Message
というデータだけ持たせた
using Cysharp.Threading.Tasks; using MessagePack; using System; using System.IO; using UnityEngine; namespace MatatabiUx.Common { /// <summary> /// 暗号化データ /// </summary> [MessagePackObject] [Serializable] public partial class CryptData : ICryptPersistentData { /// <summary> /// Message /// </summary> /// <value></value> [Key(0)] public string Message { get; set; } = default; public CryptData() { } [SerializationConstructor] public CryptData(string message) { this.Message = message; } #region Singleton protected static CryptData Instance => instance ?? (instance = new CryptData(Path.Combine(Application.persistentDataPath, "crypt.dat"), "")); private static CryptData instance = null; #endregion //Singleton #region File IO protected static string FilePath = null; protected CryptData(string filePath, string message) { FilePath = filePath; } /// <summary> /// データを読み込む /// </summary> /// <typeparam name="CryptData">暗号化データ</typeparam> /// <returns>読み込んだデータ</returns> public static UniTask<CryptData> LoadAsync() => Instance.LoadAsync<CryptData>(FilePath); /// <summary> /// データを書き込む /// </summary> /// <typeparam name="CryptData">暗号化データ</typeparam> /// <returns>書き込みが成功した場合は true, それ以外は false</returns> public UniTask<bool> SaveAsync() { return this.SaveAsync<CryptData>(FilePath); } #endregion //File IO } }
新しくデータ型を追加したので、下記の記事でやったように Serializer のコード再生成
https://www.matatabi-ux.com/entry/2022/02/03/100000
最後にテストスクリプトを書き換えて実行してみる
暗号化データを利用して読み書きしてるだけ
using MatatabiUx.Common; using UnityEngine; public class Test : MonoBehaviour { public void Awake() { // シリアライザの初期設定 MessagePack.Resolvers.StaticCompositeResolver.Instance.Register( MessagePack.Resolvers.GeneratedResolver.Instance, // コード生成した型解決クラス MessagePack.Unity.UnityResolver.Instance, MessagePack.Unity.Extension.UnityBlitWithPrimitiveArrayResolver.Instance, MessagePack.Resolvers.StandardResolver.Instance ); var option = MessagePack.MessagePackSerializerOptions.Standard .WithCompression(MessagePack.MessagePackCompression.Lz4BlockArray) // LZ4 圧縮利用 .WithResolver(MessagePack.Resolvers.StaticCompositeResolver.Instance); MessagePack.MessagePackSerializer.DefaultOptions = option; } public async void Start() { var data = await CryptData.LoadAsync(); data.Message = "ラグドールもふもふ巨大ねこ"; // ファイルに書き込み await data.SaveAsync(); // ファイルから読み込み data = await CryptData.LoadAsync(); UnityEngine.Debug.Log( $"message={data.Message}"); } }
ログを見ると正しく読み込めている様子
書き込んだ crypt.dat
を開いて中身を確認すると
���"F�6'�|�ő'f��� ���ur[�i�)b�j���zY�}!�q���k����9B��fq\�
全然読めない・・・平文ではなくちゃんと暗号化されたっぽい