나 개발자 진짜 되냐?

[ 11月 4日 ] 오늘 내가 배운 것 _ 35日次_JSON 암호화 (AES) 본문

오늘 공부를 정리해봐요!

[ 11月 4日 ] 오늘 내가 배운 것 _ 35日次_JSON 암호화 (AES)

Snow Rabbit 2024. 11. 4. 19:20
반응형

 

오늘 특강에서

직렬화를 배웠다.

 

예전에 내 친구가

직렬화를 아주 쉽게 설명해주었던 기억이 난다.

 

김밥을 직렬화 하면

김 밥 햄 계란 당근 등등으로 다 흩어져서 저장되었다가

역직렬화를 하면

김밥이 말아지는 형태가 되는 것이다.

 

이 직렬화를 하는 종류는 다양한데..

https://qua28.tistory.com/150

 

[ 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

 

반응형