C# AES 파일 암호화/복호화

Jinho Lee·2025년 2월 7일

개요

  • 외부 API의 인증 토큰을 암호화해서 저장할 필요가 생겼다. 그 암호화에 AES를 적용해보고자 한다.

AES (Advanced Encryption Standard)

  • 대칭키 암호화 알고리즘으로, 키 값을 가지고 암호화/복호화를 하므로 키 값을 모른다면 데이터를 확인 할 수 없다.

  • 암호화 키는 128, 192, 256 세 가지 중 하나가 될 수 있으며, 각각 AES-128, AES-192, AES-256라고 불린다.

  • AES 암호화 알고리즘을 사용하기 위해서는 Key 값IV 값을 가지고 있어야 한다.

    • Key : 데이터를 암호화/복호화를 하기 위해 필요한 값 즉, 키 값을 모른다면 데이터를 저장하거나 불러올 수 없다.

    • IV : 초기화 벡터로, 암호화 과정에서 입력 데이터의 패턴을 깨뜨려 동일한 텍스트가 동일하게 암호화되지 않도록 한다.

  • C#에서 C# 에서 AES 암호화 알고리즘을 사용하기 위해서는 아래 라이브러리를 사용한다고 선언해야한다.

    using System.Security.Cryptography;
    • System.Security.Cryptography의 객체들은 네이티브 리소스를 사용하여 가비지 컬렉터(GC)가 자동으로 리소스를 해제하지 않기 때문에 수동으로 리소스를 해제해줘야 한다.

예제 코드

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Moti_PhysioLicenseManager
{
    /// <summary>
    /// AES-256 대칭 암·복호화 헬퍼.
    /// <para>
    /// ⚠️ <strong>주의</strong><br/>
    /// 아래 <see cref="ExampleKey"/> / <see cref="ExampleIV"/> 는
    /// "예제"용 임시 값입니다. 실제 서비스에선 <c>RandomNumberGenerator</c> 등으로
    /// 안전하게 생성한 값을 사용하세요.
    /// </para>
    /// <example>
    /// <code>
    /// // ────────────────────────── 예제 사용법 ──────────────────────────
    /// using var cryptor = new DataCryptor();
    ///
    /// string plain = "Hello Moti-Physio!";
    ///
    /// // ① 암호화 (문자열 → 바이트)
    /// byte[] cipher = cryptor.Encrypt(
    ///     Encoding.UTF8.GetBytes(plain),   // 평문
    ///     DataCryptor.ExampleKey,          // ★ 예제 Key
    ///     DataCryptor.ExampleIV);          // ★ 예제 IV
    ///
    /// // ② (선택) 전송/저장을 위해 Base64 문자열화
    /// string cipherText = Convert.ToBase64String(cipher);
    ///
    /// // ③ 복호화
    /// byte[] cipherBytes = Convert.FromBase64String(cipherText);
    /// string decrypted = Encoding.UTF8.GetString(
    ///     cryptor.Decrypt(cipherBytes,
    ///                     DataCryptor.ExampleKey,
    ///                     DataCryptor.ExampleIV));
    ///
    /// Console.WriteLine(decrypted); // Hello Moti-Physio!
    /// // ────────────────────────────────────────────────────────────────
    /// </code>
    /// </example>
    /// </summary>
    public sealed class DataCryptor : IDisposable
    {
        #region 🔑  예제 Key / IV (절대로 실서비스에 쓰지 마세요!)

        /// <summary>32 바이트 AES-256 키 (0x00~0x1F 순차값)</summary>
        public static readonly byte[] ExampleKey =
        {
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
            0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
            0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
            0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
        };

        /// <summary>16 바이트 IV (0xA0~0xAF 순차값)</summary>
        public static readonly byte[] ExampleIV =
        {
            0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
            0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF
        };

        #endregion

        private readonly Aes _aes = Aes.Create();
        private bool _disposed;

        #region Encrypt

        /// <summary>
        /// UTF-8 문자열을 받아 암호 바이트 배열을 반환합니다.
        /// </summary>
        public byte[] Encrypt(string plainText, byte[] key, byte[] iv) =>
            Encrypt(Encoding.UTF8.GetBytes(plainText), key, iv);

        /// <summary>
        /// 바이트 배열을 암호화합니다.
        /// </summary>
        public byte[] Encrypt(byte[] plain, byte[] key, byte[] iv)
        {
            if (plain is null || plain.Length == 0)
                throw new ArgumentException(nameof(plain));

            InitKeyIv(key, iv);

            using MemoryStream ms = new();
            using CryptoStream cs = new(ms, _aes.CreateEncryptor(), CryptoStreamMode.Write);
            cs.Write(plain, 0, plain.Length);
            cs.FlushFinalBlock();
            return ms.ToArray();
        }

        #endregion
        #region Decrypt

        /// <summary>
        /// Base64 문자열을 복호화해 UTF-8 문자열로 돌려줍니다.
        /// </summary>
        public string Decrypt(string base64CipherText, byte[] key, byte[] iv)
        {
            byte[] cipher = Convert.FromBase64String(base64CipherText);
            return Encoding.UTF8.GetString(Decrypt(cipher, key, iv));
        }

        /// <summary>
        /// 암호 바이트 배열을 복호화해 평문 바이트를 반환합니다.
        /// </summary>
        public byte[] Decrypt(byte[] cipher, byte[] key, byte[] iv)
        {
            if (cipher is null || cipher.Length == 0)
                throw new ArgumentException(nameof(cipher));

            InitKeyIv(key, iv);

            using MemoryStream ms = new(cipher);
            using CryptoStream cs = new(ms, _aes.CreateDecryptor(), CryptoStreamMode.Read);
            using MemoryStream plain = new();
            cs.CopyTo(plain);
            return plain.ToArray();
        }

        #endregion
        #region Helpers / Dispose

        private void InitKeyIv(byte[] key, byte[] iv)
        {
            _aes.Key = (key is { Length: > 0 }) ? key : ExampleKey;
            _aes.IV  = (iv  is { Length: > 0 }) ? iv  : ExampleIV;
        }

        /// <inheritdoc/>
        public void Dispose()
        {
            if (_disposed) return;
            _aes.Dispose();
            _disposed = true;
        }

        #endregion
    }
}

참고

0개의 댓글