[ 11月 4日 ] 오늘 내가 배운 것 _ 35日次_JSON 암호화 (AES)
오늘 특강에서
직렬화를 배웠다.
예전에 내 친구가
직렬화를 아주 쉽게 설명해주었던 기억이 난다.
김밥을 직렬화 하면
김 밥 햄 계란 당근 등등으로 다 흩어져서 저장되었다가
역직렬화를 하면
김밥이 말아지는 형태가 되는 것이다.
이 직렬화를 하는 종류는 다양한데..
[ C# 기본 문법 24 ] 직렬화 Serialization & 역직렬화 Deserialization
이번에 큰마음 먹고 시작한게임 저장기능.. 저장과 불러오기를 통해알게된 직렬화 자바에 있다던..자바에서는 쉽다던..하지만 여기선..쉽지않은직렬화!!! 그 친구에 대해 탐구해보자.직렬화
qua28.tistory.com
종류는 적어놓았다.
그 중에서 자주쓰이는 JSON
이친구에게는 치명적인 단점이 있다.
암호화에 약하다.
즉
Json 파일로 데이터를 저장하는 것은 간편하지만,
파일이 그대로 노출되면 데이터가 쉽게 읽힐 수 있다.
민감한 데이터는 암호화하여 저장하는 것이 좋기에
Unity에서 Json 파일을 암호화하여 저장하고 불러오는 방법을
정리해보자.
자 먼저,
암호화와 복호화를 위해
난수를 왕 만들어보자!
using System.Security.Cryptography;
이 친구를 써주고 시작하자!
private byte[] key; // 암호화에 사용되는 키
private byte[] iv; // 초기화 벡터
private readonly string keyPath = Path.Combine(Application.persistentDataPath, "aesKey.dat");
private readonly string ivPath = Path.Combine(Application.persistentDataPath, "aesIV.dat");
public AESCrypto()
{
if (File.Exists(keyPath) && File.Exists(ivPath)) // 키와 IV가 존재하는 지 확인
{
// 존재한다면 해당 키와 IV를 읽어옴
key = File.ReadAllBytes(keyPath);
iv = File.ReadAllBytes(ivPath);
}
else
{
// 없다면 다시 생성
key = GenerateRandomBytes(32); // 256-bit key
iv = GenerateRandomBytes(16); // 128-bit IV
File.WriteAllBytes(keyPath, key);
File.WriteAllBytes(ivPath, iv);
}
}
// 지정된 길이의 랜덤 바이트 배열을 생성
private byte[] GenerateRandomBytes(int length)
{
byte[] randomBytes = new byte[length];
// RNGCryptoServiceProvider : 난수 발생기
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(randomBytes);
}
return randomBytes;
}
암호화를 하기위해서는 난수생성은 필수이다.
해주고 나서
밑에서는
위에서 만든 함수를 가지고
Json을 암호화 복호화를 진행한다.
public string EncryptString(string plainText)
{
// AES 객체와 같은 암호화 객체는 네이티브 리소스를 사용하므로 사용후에 반드시 해제해야한다.
// using을 사용하여 작업이 끝난 후에 자동으로 리소스가 해제되게 설계했다.
// 네이티브 리소스는 운영 체제나 하드웨어와 직접적으로 상호작용하는 리소스 (GC에 의해 자동으로 관리되지 않기 떄문에 수동으로 해제해줘야 함)
using (Aes aesAlg = Aes.Create()) // AES 알고리즘 생성
{
// 키, IV 설정
aesAlg.Key = key;
aesAlg.IV = iv;
// 암호화 변환기를 생성
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// 평문 텍스트를 암호화
byte[] encrypted = encryptor.TransformFinalBlock(Encoding.UTF8.GetBytes(plainText), 0, plainText.Length);
// 암호화된 바이트 배열을 Base64 문자열로 변환 후 반환
return System.Convert.ToBase64String(encrypted);
}
}
public string DecryptString(string cipherText) // 복호화 함수
{
// Base64 문자열을 바이트 배열로 변환
byte[] buffer = System.Convert.FromBase64String(cipherText);
using (Aes aesAlg = Aes.Create()) // AES 알고리즘 생성
{
// 키, IV 설정
aesAlg.Key = key;
aesAlg.IV = iv;
// 복호화 변환기를 생성
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// 암호화된 바이트 배열을 복호화
byte[] decrypted = decryptor.TransformFinalBlock(buffer, 0, buffer.Length);
// 복호화된 바이트 배열을 UTF-8 문자열로 변환 후 반환
return Encoding.UTF8.GetString(decrypted);
}
}
사용 할 때!
PlayerDataManager.cs
using UnityEngine;
using System.IO;
public class PlayerDataManager : MonoBehaviour
{
private AESCrypto crypto;
private string path;
private void Awake()
{
crypto = new AESCrypto();
path = Path.Combine(Application.persistentDataPath, "PlayerData.json");
}
public void DataSave(PlayerSaveData playerData)
{
string json = JsonUtility.ToJson(playerData, true); // 데이터 직렬화
string encryptedJson = crypto.EncryptString(json); // 직렬화 된 데이터 암호화
File.WriteAllText(path, encryptedJson);
}
public void DataLoad()
{
if (!File.Exists(path))
{
Debug.Log("데이터가 존재하지 않습니다 !");
return;
}
string encryptedJson = File.ReadAllText(path); // 암호화된 데이터 읽어옴
string json =crypto.DecryptString(encryptedJson); // 복호화 및 역직렬화
PlayerSaveData playerData = JsonUtility.FromJson<PlayerSaveData>(json);
}
}
AESCrypto.cs
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
public class AESCrypto
{
private byte[] key; // 암호화에 사용되는 키
private byte[] iv; // 초기화 벡터
private readonly string keyPath = Path.Combine(Application.persistentDataPath, "aesKey.dat");
private readonly string ivPath = Path.Combine(Application.persistentDataPath, "aesIV.dat");
public AESCrypto()
{
if (File.Exists(keyPath) && File.Exists(ivPath)) // 키와 IV가 존재하는 지 확인
{
// 존재한다면 해당 키와 IV를 읽어옴
key = File.ReadAllBytes(keyPath);
iv = File.ReadAllBytes(ivPath);
}
else
{
// 없다면 다시 생성
key = GenerateRandomBytes(32); // 256-bit key
iv = GenerateRandomBytes(16); // 128-bit IV
File.WriteAllBytes(keyPath, key);
File.WriteAllBytes(ivPath, iv);
}
}
// 지정된 길이의 랜덤 바이트 배열을 생성
private byte[] GenerateRandomBytes(int length)
{
byte[] randomBytes = new byte[length];
// RNGCryptoServiceProvider : 난수 발생기
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(randomBytes);
}
return randomBytes;
}
public string EncryptString(string plainText)
{
// AES 객체와 같은 암호화 객체는 네이티브 리소스를 사용하므로 사용후에 반드시 해제해야한다.
// using을 사용하여 작업이 끝난 후에 자동으로 리소스가 해제되게 설계했다.
// 네이티브 리소스는 운영 체제나 하드웨어와 직접적으로 상호작용하는 리소스 (GC에 의해 자동으로 관리되지 않기 떄문에 수동으로 해제해줘야 함)
using (Aes aesAlg = Aes.Create()) // AES 알고리즘 생성
{
// 키, IV 설정
aesAlg.Key = key;
aesAlg.IV = iv;
// 암호화 변환기를 생성
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// 평문 텍스트를 암호화
byte[] encrypted = encryptor.TransformFinalBlock(Encoding.UTF8.GetBytes(plainText), 0, plainText.Length);
// 암호화된 바이트 배열을 Base64 문자열로 변환 후 반환
return System.Convert.ToBase64String(encrypted);
}
}
public string DecryptString(string cipherText) // 복호화 함수
{
// Base64 문자열을 바이트 배열로 변환
byte[] buffer = System.Convert.FromBase64String(cipherText);
using (Aes aesAlg = Aes.Create()) // AES 알고리즘 생성
{
// 키, IV 설정
aesAlg.Key = key;
aesAlg.IV = iv;
// 복호화 변환기를 생성
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// 암호화된 바이트 배열을 복호화
byte[] decrypted = decryptor.TransformFinalBlock(buffer, 0, buffer.Length);
// 복호화된 바이트 배열을 UTF-8 문자열로 변환 후 반환
return Encoding.UTF8.GetString(decrypted);
}
}
}
[ 참고 블로그 ]
위 내용은 전 기수분께서 작성해주신 블로그를 참고하였습니다.
https://deff-dev.tistory.com/145