[TIL] Unity - Save & Load 암호화 실전 - day 97

뭉크의 개발·2023년 12월 4일
0

Unity - Camp

목록 보기
65/70

🐧 들어가기 앞서

이번에는 저번에 세이브 로드를 암호화 하는 과정을 구상만 했었는데,

실제로 프로젝트에 암호화를 적용해야할 일이 생겼다.

  • Json Raw Data

우리 프로젝트의 Save Json 원본이다.

여기서 값만 수정한다면, 게임에서도 수정된 파일이 그대로 실행되었다.

이를 방지하기 위해 암호화 알고리즘을 적용했다.


🐧 오늘 배운 것

1. 키와 초기화 벡터(IV) 초기화

private const int SaveSlot = 0;
private byte[] key;
private byte[] iv;

InitializeKeyAndIV 메서드는 암호화에 사용될 키(key)와 초기화 벡터(IV)를 설정한다.

만약 이전에 저장된 키와 IV가 없다면 새로 생성하고, 있으면 기존의 값을 사용한다.

private void InitializeKeyAndIV()
{
    if (PlayerPrefs.HasKey("EncryptionKey") && PlayerPrefs.HasKey("EncryptionIV"))
    {
        key = Convert.FromBase64String(PlayerPrefs.GetString("EncryptionKey"));
        iv = Convert.FromBase64String(PlayerPrefs.GetString("EncryptionIV"));
    }
    else
    {
        using (var aesAlg = Aes.Create())
        {
            aesAlg.GenerateKey();
            aesAlg.GenerateIV();
            key = aesAlg.Key;
            iv = aesAlg.IV;

            SaveKeyAndIVToPlayerPrefs(key, iv);
        }
    }
}

이 키와 IV는 PlayerPrefs에 Base64 인코딩 형태로 저장되어 애플리케이션 재시작 후에도 사용할 수 있다.

private void SaveKeyAndIVToPlayerPrefs(byte[] key, byte[] iv)
{
    PlayerPrefs.SetString("EncryptionKey", Convert.ToBase64String(key));
    PlayerPrefs.SetString("EncryptionIV", Convert.ToBase64String(iv));
    PlayerPrefs.Save();
}

이 글을 보고 구현하시는 분들은 PlayerPrefs보다 서버를 이용할 수 있으면 서버에 저장하거나, 안전한 곳에 관리하시길 바랍니다.

2. 데이터 저장 및 암호화

SaveData 메서드는 제공된 데이터를 JSON 형식으로 직렬화하고, AES 알고리즘을 사용하여 암호화한다.

public void SaveData(DataManager data)
{
    string json = JsonConvert.SerializeObject(data, Formatting.Indented);
    string encrypted = Encrypt(json);
    string hash = ComputeSha256Hash(encrypted);

    string path = GetSaveFilePath();
    File.WriteAllText(path, encrypted);
    File.WriteAllText(path + ".hash", hash);
}
  • 암호화 AES 적용
private string Encrypt(string plainText)
{
    using (Aes aesAlg = Aes.Create())
    {
        aesAlg.Key = key;
        aesAlg.IV = iv;

        ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
        using (MemoryStream msEncrypt = new MemoryStream())
        {
            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            {
                using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                {
                    swEncrypt.Write(plainText);
                }
            }
            return Convert.ToBase64String(msEncrypt.ToArray());
        }
    }
}

암호화된 데이터는 지정된 파일 경로에 저장되며, 데이터의 무결성을 검증하기 위해 SHA-256 해시 값도 함께 저장된다.

private string ComputeSha256Hash(string rawData)
{
    using (SHA256 sha256hash = SHA256.Create())
    {
        byte[] bytes = sha256hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < bytes.Length; i++)
        {
            builder.Append(bytes[i].ToString("x2"));
        }
        return builder.ToString();
    }
}

3. 데이터 로드 및 복호화

LoadData 메서드는 저장된 파일에서 암호화된 데이터와 해시 값을 읽어온다.
읽어온 데이터의 해시 값이 저장된 해시 값과 일치하는지 확인하여 파일의 무결성을 검증합니다.

public DataManager LoadData()
{
    string path = GetSaveFilePath();
    if (File.Exists(path))
    {
        string encrypted = File.ReadAllText(path);
        string hash = File.ReadAllText(path + ".hash");
        string newHash = ComputeSha256Hash(encrypted);

        if (hash != newHash)
        {
            Debug.LogError("파일 무결성 검증 실패");
            return null;
        }

        string decrypted = Decrypt(encrypted);
        return JsonConvert.DeserializeObject<DataManager>(decrypted);
    }

    return null;
}

무결성이 확인되면 데이터를 복호화하여 원래의 형태로 복원합니다.

private string Decrypt(string cipherText)
{
    string plaintext = null;
    byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
    using (Aes aesAlg = Aes.Create())
    {
        aesAlg.Key = key;
        aesAlg.IV = iv;

        ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
        using (MemoryStream msDecrypt = new MemoryStream(cipherTextBytes))
        {
            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
                using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                {
                    plaintext = srDecrypt.ReadToEnd();
                }
            }
        }
    }
    return plaintext;
}

4. 데이터 삭제

DeleteData 메서드는 저장된 데이터 파일과 해당 파일의 해시 값을 삭제한다.
혹시나 데이터를 삭제할 일이 필요하다면, 유니티와 연결하기 위해 임시로 구현했다.

public void DeleteData()
{
    string path = GetSaveFilePath();
    if (File.Exists(path))
    {
        File.Delete(path);
    }
    if (File.Exists(path + ".hash"))
    {
        File.Delete(path + ".hash");
    }
}

🐧 게임에 구현한다면?

  • Json 파일 내부 암호화 완료.

  • Json 파일을 SHA-256으로 해싱하여 무결성 검사를 진행한다.

0개의 댓글