Unity에서 JSON 데이터 저장 및 검증: HMACSHA256을 이용한 보안
2024. 6. 12. 22:33ㆍ유니티 unity
게임 개발에서 데이터 저장과 보안은 매우 중요한 문제입니다. 특히 플레이어의 진행 상황이나 설정을 저장할 때, 데이터의 무결성을 보장하는 것이 중요합니다. 이번 포스트에서는 Unity에서 Newtonsoft.Json 라이브러리를 사용하여 데이터를 JSON 형식으로 직렬화하고, HMACSHA256 해시 알고리즘을 사용하여 데이터를 서명하고 검증하는 방법을 다루겠습니다. 이를 통해 데이터가 변조되지 않았는지 확인할 수 있습니다.
먼저, Unity 프로젝트에 Newtonsoft.Json 라이브러리를 추가해야 합니다.
단순하게 Json을 저장 불러오기하면 손 쉽게 데이터를 변조가 가능하니 HMACSHA256를 사용하여 데이터를 서명하고 검증합니다.
Class
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using Newtonsoft.Json;
public static class SecureSaveLoadManager
{
// 문자열을 기반으로 암호화 키를 초기화
private static readonly byte[] Key = Encoding.UTF8.GetBytes("KEY1234"); // 16바이트 키 (AES-128)
/// <summary>
/// 파일 경로를 반환합니다.
/// </summary>
/// <param name="fileName">파일 이름</param>
/// <returns>파일 경로</returns>
private static string GetFilePath(string fileName)
{
string folderPath;
const string SaveFileName = "SaveData";
#if UNITY_EDITOR
// 에디터에서는 Application.dataPath 사용
folderPath = Path.Combine(Application.dataPath, SaveFileName);
#else
// 빌드된 애플리케이션에서는 Application.persistentDataPath 사용
folderPath = Path.Combine(Application.persistentDataPath, SaveFileName);
#endif
// 폴더가 존재하지 않으면 생성
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
return Path.Combine(folderPath, fileName + ".dat");
}
/// <summary>
/// 데이터를 파일에 저장합니다.
/// </summary>
/// <typeparam name="T">저장할 데이터의 타입</typeparam>
/// <param name="data">저장할 데이터</param>
/// <param name="fileName">파일 이름</param>
public static void SaveData<T>(T data, string fileName)
{
// 파일 경로를 얻습니다.
string filePath = GetFilePath(fileName);
// 데이터를 JSON 형식으로 직렬화합니다.
string json = JsonConvert.SerializeObject(data);
byte[] bytes = Encoding.UTF8.GetBytes(json);
// 데이터를 서명합니다.
byte[] signedData = SignData(bytes);
// 서명된 데이터를 파일에 저장합니다.
File.WriteAllBytes(filePath, signedData);
Debug.Log("Data saved to " + filePath);
}
/// <summary>
/// 파일에서 데이터를 로드합니다.
/// </summary>
/// <typeparam name="T">로드할 데이터의 타입</typeparam>
/// <param name="fileName">파일 이름</param>
/// <returns>로드한 데이터</returns>
public static T LoadData<T>(string fileName)
{
// 파일 경로를 얻습니다.
string filePath = GetFilePath(fileName);
// 파일이 존재하는지 확인합니다.
if (File.Exists(filePath))
{
// 파일에서 서명된 데이터를 읽어옵니다.
byte[] signedData = File.ReadAllBytes(filePath);
// 서명된 데이터를 검증하고 원본 데이터를 얻습니다.
if (VerifyData(signedData, out byte[] data))
{
// 데이터를 JSON 형식으로 역직렬화합니다.
string json = Encoding.UTF8.GetString(data);
T deserializedData = JsonConvert.DeserializeObject<T>(json);
Debug.Log("Data loaded from " + filePath);
return deserializedData;
}
else
{
Debug.LogWarning("Data verification failed!");
return default(T);
}
}
else
{
Debug.LogWarning("Save file not found!");
return default(T);
}
}
/// <summary>
/// 데이터를 서명하여 해시 값과 원본 데이터를 결합한 바이트 배열을 반환합니다.
/// </summary>
/// <param name="data">서명할 원본 데이터</param>
/// <returns>해시 값과 원본 데이터가 결합된 바이트 배열</returns>
private static byte[] SignData(byte[] data)
{
// HMACSHA256을 사용하여 키를 기반으로 해시 생성
using (HMACSHA256 hmac = new HMACSHA256(Key))
{
// 데이터의 해시 값을 계산
byte[] hash = hmac.ComputeHash(data);
// 해시 값과 원본 데이터를 결합
byte[] signedData = new byte[hash.Length + data.Length];
Buffer.BlockCopy(hash, 0, signedData, 0, hash.Length); // 해시 값을 결합
Buffer.BlockCopy(data, 0, signedData, hash.Length, data.Length); // 원본 데이터를 결합
return signedData; // 해시 값과 원본 데이터가 결합된 배열 반환
}
}
/// <summary>
/// 서명된 데이터를 검증하고, 원본 데이터를 반환합니다.
/// </summary>
/// <param name="signedData">검증할 서명된 데이터</param>
/// <param name="data">원본 데이터 (출력)</param>
/// <returns>검증 성공 여부</returns>
private static bool VerifyData(byte[] signedData, out byte[] data)
{
// HMACSHA256을 사용하여 키를 기반으로 해시 생성
using (HMACSHA256 hmac = new HMACSHA256(Key))
{
int hashLength = hmac.HashSize / 8; // 해시 값의 길이 (바이트)
data = new byte[signedData.Length - hashLength]; // 원본 데이터 배열 생성
byte[] hash = new byte[hashLength]; // 해시 값 배열 생성
// 서명된 데이터에서 해시 값과 원본 데이터를 분리
Buffer.BlockCopy(signedData, 0, hash, 0, hashLength); // 해시 값 분리
Buffer.BlockCopy(signedData, hashLength, data, 0, signedData.Length - hashLength); // 원본 데이터 분리
// 원본 데이터의 해시 값을 계산
byte[] computedHash = hmac.ComputeHash(data);
// 분리된 해시 값과 계산된 해시 값을 비교
return CompareHashes(hash, computedHash);
}
}
/// <summary>
/// 두 해시 값을 비교하여 동일한지 여부를 확인합니다.
/// </summary>
/// <param name="hash1">첫 번째 해시 값</param>
/// <param name="hash2">두 번째 해시 값</param>
/// <returns>해시 값이 동일한지 여부</returns>
private static bool CompareHashes(byte[] hash1, byte[] hash2)
{
//해시 값이 동일한지 확인
if (hash1.Length != hash2.Length)
{
return false;
}
for (int i = 0; i < hash1.Length; i++)
{
if (hash1[i] != hash2[i])
{
return false; // 해시 값이 다르면 false 반환
}
}
return true;
}
}
'유니티 unity' 카테고리의 다른 글
unity6 빌드에러 "property#android.adservices.AD_SERVICES_CONFIG@resource=@xml/gma_ad_services_config` in com.google.android.gms:play-services-measurement-api:22.1.2 collides with another value" (0) | 2024.12.24 |
---|---|
unity // 구글플레이 인앱업데이트 안드로이드14 타켓 오류 (1) | 2024.06.09 |
[유니티] 문자열을 이용하여 클래스 인스턴스 생성하기 :상속 관계에서의 동적 클래스 생성 (0) | 2024.03.24 |
유니티(Unity) 오브젝트 풀링 (2) | 2022.09.13 |
유니티(Unity) Job 시스템 사용법-1 (0) | 2022.08.23 |