Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Софт [Сурс] C# Encryptor — FUD шифрование файлов (AES-256 + PBKDF2)

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
394
Реакции
7
Народ, кто до сих пор хранит конфиги, дллки или ресурсы своих лоадеров в открытом виде или юзает дефолтный XOR — пора переходить на нормальные рельсы. Попал в руки годный класс для реализации действительно криптостойкого шифрования на C#.

Это не просто «паста» с MSDN. Здесь реализован грамотный стек: AES-256 для контента и PBKDF2 (SHA512) для деривации ключа. Из приятных плюшек — использование Pepper (дополнительная статическая соль) и автоматическое сжатие GZip перед шифрованием. Последнее не только уменьшает вес, но и ломает структуру файла, что полезно против сигнатурного анализа.

Что внутри тех-части:
  1. Алгоритм: AES-256 (CipherMode.CBC, PaddingMode.PKCS7).
  2. Деривация: PBKDF2 с 100,000 итераций (медленно для брутфорса, надежно для нас).
  3. Безопасность: Юзается SecureString для паролей, чтобы не светить ими в открытой памяти (RAM).
  4. Очистка: Ручное затирание массивов байтов через Array.Clear после использования.
  5. Соль и Пеппер: 128-bit salt + 32-byte pepper.

Код:
Expand Collapse Copy
using System;
using System.IO;
using System.IO.Compression;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace Encryption
{
    public sealed class Encryptor : IDisposable
    {
        private const int KeySize = 256; // AES-256
        private const int Iterations = 100000; // PBKDF2 iteration count
        private const int SaltSize = 128; // 128-bit salt
        private const int PepperSize = 32; // 256-bit pepper
        private const int MinPasswordLength = 12;
        private readonly byte[] _pepper;
        private bool _disposed = false;
 
        public Encryptor()
        {
            try
            {
                // Generate application-specific pepper
                _pepper = new byte[PepperSize];
                using (var rng = RandomNumberGenerator.Create())
                {
                    rng.GetBytes(_pepper);
                }
            }
            catch (Exception ex)
            {
                throw new CryptographicException("Failed to initialize encryptor", ex);
            }
        }
 
        public void EncryptFile(string inputFile, string outputFile, SecureString password)
        {
            ValidateParameters(inputFile, outputFile, password);
 
            try
            {
                byte[] salt = GenerateSalt();
                byte[] compressedData = CompressFile(inputFile);
                byte[] passwordBytes = SecureStringToBytes(password);
 
                try
                {
                    PerformEncryption(outputFile, salt, compressedData, passwordBytes);
                }
                finally
                {
                    SecureClear(compressedData);
                    SecureClear(passwordBytes);
                }
            }
            catch (Exception ex) when (!(ex is OperationCanceledException))
            {
                throw new CryptographicException($"Encryption failed: {ex.Message}", ex);
            }
        }
 
        public void DecryptFile(string inputFile, string outputFile, SecureString password)
        {
            ValidateParameters(inputFile, outputFile, password);
 
            try
            {
                byte[] passwordBytes = SecureStringToBytes(password);
 
                try
                {
                    byte[] salt = ReadSaltFromFile(inputFile);
                    byte[] decryptedData = PerformDecryption(inputFile, passwordBytes, salt);
                    DecompressToFile(decryptedData, outputFile);
                    SecureClear(decryptedData);
                }
                finally
                {
                    SecureClear(passwordBytes);
                }
            }
            catch (CryptographicException)
            {
                throw; // Preserve original cryptographic exceptions
            }
            catch (Exception ex) when (!(ex is OperationCanceledException))
            {
                throw new CryptographicException($"Decryption failed: {ex.Message}", ex);
            }
        }
 
        #region Core Operations
 
        private byte[] GenerateSalt()
        {
            var salt = new byte[SaltSize / 8];
            using (var rng = RandomNumberGenerator.Create())
            {
                rng.GetBytes(salt);
            }
            return salt;
        }
 
        private void PerformEncryption(string outputFile, byte[] salt, byte[] data, byte[] passwordBytes)
        {
            using (var derivedKey = new Rfc2898DeriveBytes(
                password: Combine(passwordBytes, salt),
                salt: Combine(salt, _pepper),
                iterations: Iterations,
                hashAlgorithm: HashAlgorithmName.SHA512))
            {
                byte[] key = derivedKey.GetBytes(KeySize / 8);
                byte[] iv = derivedKey.GetBytes(128 / 8);
 
                try
                {
                    using (var aes = Aes.Create())
                    {
                        ConfigureAes(aes, key, iv);
                        WriteEncryptedFile(outputFile, salt, data, aes);
                    }
                }
                finally
                {
                    SecureClear(key);
                    SecureClear(iv);
                }
            }
        }
 
        private byte[] PerformDecryption(string inputFile, byte[] passwordBytes, byte[] salt)
        {
            using (var derivedKey = new Rfc2898DeriveBytes(
                password: Combine(passwordBytes, salt),
                salt: Combine(salt, _pepper),
                iterations: Iterations,
                hashAlgorithm: HashAlgorithmName.SHA512))
            {
                byte[] key = derivedKey.GetBytes(KeySize / 8);
                byte[] iv = derivedKey.GetBytes(128 / 8);
 
                try
                {
                    using (var aes = Aes.Create())
                    {
                        ConfigureAes(aes, key, iv);
                        return ReadAndDecryptFile(inputFile, salt.Length, aes);
                    }
                }
                finally
                {
                    SecureClear(key);
                    SecureClear(iv);
                }
            }
        }
 
        #endregion
 
        #region File Operations
 
        private byte[] CompressFile(string inputFile)
        {
            try
            {
                using (var memoryStream = new MemoryStream())
                {
                    using (var gzipStream = new GZipStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true))
                    using (var fileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        fileStream.CopyTo(gzipStream);
                    }
                    return memoryStream.ToArray();
                }
            }
            catch (IOException ex)
            {
                throw new InvalidOperationException($"Compression failed for {inputFile}", ex);
            }
        }
 
        private void DecompressToFile(byte[] compressedData, string outputFile)
        {
            try
            {
                using (var memoryStream = new MemoryStream(compressedData))
                using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
                using (var fileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write))
                {
                    gzipStream.CopyTo(fileStream);
                }
            }
            catch (InvalidDataException ex)
            {
                throw new InvalidOperationException("Invalid compressed data format", ex);
            }
            catch (IOException ex)
            {
                throw new InvalidOperationException($"Failed to write decompressed file to {outputFile}", ex);
            }
        }
 
        private byte[] ReadSaltFromFile(string inputFile)
        {
            try
            {
                using (var fileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
                {
                    byte[] salt = new byte[SaltSize / 8];
                    int bytesRead = fileStream.Read(salt, 0, salt.Length);
                    if (bytesRead != salt.Length)
                    {
                        throw new InvalidDataException("File is too short to contain salt");
                    }
                    return salt;
                }
            }
            catch (IOException ex)
            {
                throw new InvalidOperationException($"Failed to read salt from {inputFile}", ex);
            }
        }
 
        private void WriteEncryptedFile(string outputPath, byte[] salt, byte[] data, SymmetricAlgorithm algorithm)
        {
            try
            {
                using (var fileStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
                {
                    // Write salt
                    fileStream.Write(salt, 0, salt.Length);
 
                    // Write encrypted data
                    using (var cryptoStream = new CryptoStream(
                        fileStream,
                        algorithm.CreateEncryptor(),
                        CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(data, 0, data.Length);
                        cryptoStream.FlushFinalBlock();
                    }
                }
            }
            catch (IOException ex)
            {
                throw new InvalidOperationException($"Failed to write encrypted file to {outputPath}", ex);
            }
        }
 
        private byte[] ReadAndDecryptFile(string inputPath, int saltLength, SymmetricAlgorithm algorithm)
        {
            try
            {
                using (var fileStream = new FileStream(inputPath, FileMode.Open, FileAccess.Read))
                {
                    // Skip salt
                    fileStream.Position = saltLength;
 
                    using (var memoryStream = new MemoryStream())
                    using (var cryptoStream = new CryptoStream(
                        fileStream,
                        algorithm.CreateDecryptor(),
                        CryptoStreamMode.Read))
                    {
                        cryptoStream.CopyTo(memoryStream);
                        return memoryStream.ToArray();
                    }
                }
            }
            catch (IOException ex)
            {
                throw new InvalidOperationException($"Failed to read encrypted file from {inputPath}", ex);
            }
        }
 
        #endregion
 
        #region Security Utilities
 
        private byte[] SecureStringToBytes(SecureString secureString)
        {
            IntPtr bstr = IntPtr.Zero;
            try
            {
                bstr = Marshal.SecureStringToBSTR(secureString);
                int length = Marshal.ReadInt32(bstr, -4);
                byte[] bytes = new byte[length];
 
                for (int i = 0; i < length; i++)
                {
                    bytes[i] = Marshal.ReadByte(bstr, i);
                }
 
                return bytes;
            }
            finally
            {
                if (bstr != IntPtr.Zero)
                {
                    Marshal.ZeroFreeBSTR(bstr);
                }
            }
        }
 
        private void SecureClear(byte[] data)
        {
            if (data != null)
            {
                Array.Clear(data, 0, data.Length);
            }
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private byte[] Combine(byte[] first, byte[] second)
        {
            var combined = new byte[first.Length + second.Length];
            Buffer.BlockCopy(first, 0, combined, 0, first.Length);
            Buffer.BlockCopy(second, 0, combined, first.Length, second.Length);
            return combined;
        }
 
        private void ConfigureAes(SymmetricAlgorithm algorithm, byte[] key, byte[] iv)
        {
            algorithm.Key = key;
            algorithm.IV = iv;
            algorithm.Mode = CipherMode.CBC;
            algorithm.Padding = PaddingMode.PKCS7;
        }
 
        #endregion
 
        #region Validation
 
        private void ValidateParameters(string inputFile, string outputFile, SecureString password)
        {
            if (string.IsNullOrWhiteSpace(inputFile))
                throw new ArgumentNullException(nameof(inputFile), "Input file path cannot be empty");
            
            if (string.IsNullOrWhiteSpace(outputFile))
                throw new ArgumentNullException(nameof(outputFile), "Output file path cannot be empty");
            
            if (password == null)
                throw new ArgumentNullException(nameof(password), "Password cannot be null");
            
            if (password.Length < MinPasswordLength)
                throw new ArgumentException($"Password must be at least {MinPasswordLength} characters", nameof(password));
            
            if (!File.Exists(inputFile))
                throw new FileNotFoundException("Input file not found", inputFile);
            
            try
            {
                string outputDir = Path.GetDirectoryName(outputFile);
                if (!Directory.Exists(outputDir))
                {
                    Directory.CreateDirectory(outputDir);
                }
            }
            catch (Exception ex)
            {
                throw new ArgumentException($"Invalid output path: {ex.Message}", nameof(outputFile), ex);
            }
        }
 
        #endregion
 
        #region IDisposable Implementation
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing && _pepper != null)
                {
                    SecureClear(_pepper);
                }
                _disposed = true;
            }
        }
 
        ~Encryptor()
        {
            Dispose(false);
        }
 
        #endregion
    }
}

Почему это FUD?
Потому что здесь используются стандартные системные библиотеки .NET без кастомных и подозрительных оберток, которые любят детектить проактивки. Сжатие данных перед шифрованием полностью меняет энтропию файла, делая его «белым шумом» для любого сканера.

Совет по внедрению:
Если юзаете это в лоадере, зашивайте _pepper в константы или генерируйте его на основе HWID юзера. Это создаст дополнительный геморрой для тех, кто захочет вскрыть ваши ресурсы на другом ПК.

Кто уже пробовал связку AES + GZip для защиты ассетов в своих проектах, как полет?
 
Назад
Сверху Снизу