Avatar ·

Шифрование и дешифрование строки в C#: простой и безопасный способ

Всем привет! Я начинающий C# разработчик, и у меня возникла проблема с шифрованием строк. Мне нужно сохранить в базе данных пароль пользователя в зашифрованном виде, а затем при входе расшифровать его и сравнить с введённым паролем. Я пробовал использовать простые алгоритмы, но код получается громоздким и, как я понимаю, небезопасным. Вот что я написал: ```csharp public static string Encrypt(string text) { byte[] data = Encoding.UTF8.GetBytes(text); for (int i = 0; i < data.Length; i++) { data[i] = (byte)(data[i] ^ 0xFF); // простой XOR } return Convert.ToBase64String(data); } ``` Но я понимаю, что это "детский сад" в плане безопасности. Я слышал про AES, но не знаю, как правильно его использовать с солью, вектором инициализации (IV) и ключом. Мне нужен рабочий пример, который: 1. Надёжно шифрует строку (желательно AES-256). 2. Позволяет расшифровать её обратно. 3. Использует случайный IV для каждого шифрования. 4. Работает в .NET 6+ и Mono (для Linux). Пожалуйста, покажите полный код с пояснениями. Заранее спасибо! <<>>

Правильное шифрование строк с помощью AES в C#

Привет! Твой XOR-метод действительно небезопасен — любой может расшифровать данные, зная только сам алгоритм. Для защиты конфиденциальных данных (например, паролей) правильнее использовать AES (Advanced Encryption Standard) с длиной ключа 256 бит. Но запомни главное правило: пароли нельзя хранить в обратимом виде — используй хеширование (например, bcrypt) для аутентификации. Если же тебе действительно нужно обратимое шифрование (например, для хранения токенов), вот безопасный пример.

Полный код шифрования/дешифрования строки через AES-256

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

public static class StringCipher
{
    // Размеры ключа и соли (в байтах)
    private const int KeySize = 256; // бит
    private const int SaltSize = 16; // 128 бит
    private const int IvSize = 16;   // 128 бит для AES

    /// <summary>
    /// Шифрует строку с использованием AES-256 и случайного IV.
    /// </summary>
    /// <param name="plainText">Исходный текст</param>
    /// <param name="passPhrase">Парольная фраза (ключ)</param>
    /// <returns>Base64-строка: соль + IV + зашифрованные данные</returns>
    public static string Encrypt(string plainText, string passPhrase)
    {
        // 1. Генерируем случайную соль и IV
        byte[] salt = new byte[SaltSize];
        byte[] iv = new byte[IvSize];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(salt);
            rng.GetBytes(iv);
        }

        // 2. Создаём ключ из парольной фразы с помощью PBKDF2
        byte[] key;
        using (var deriveBytes = new Rfc2898DeriveBytes(passPhrase, salt, 100000, HashAlgorithmName.SHA256))
        {
            key = deriveBytes.GetBytes(KeySize / 8);
        }

        // 3. Шифруем
        byte[] encrypted;
        using (var aes = Aes.Create())
        {
            aes.KeySize = KeySize;
            aes.Key = key;
            aes.IV = iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            using (var ms = new MemoryStream())
            using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
            {
                byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
                cs.Write(plainBytes, 0, plainBytes.Length);
                cs.FlushFinalBlock();
                encrypted = ms.ToArray();
            }
        }

        // 4. Объединяем: соль + IV + шифротекст
        byte[] result = new byte[salt.Length + iv.Length + encrypted.Length];
        Buffer.BlockCopy(salt, 0, result, 0, salt.Length);
        Buffer.BlockCopy(iv, 0, result, salt.Length, iv.Length);
        Buffer.BlockCopy(encrypted, 0, result, salt.Length + iv.Length, encrypted.Length);

        return Convert.ToBase64String(result);
    }

    /// <summary>
    /// Дешифрует строку, зашифрованную методом Encrypt.
    /// </summary>
    /// <param name="cipherText">Base64-строка: соль + IV + зашифрованные данные</param>
    /// <param name="passPhrase">Парольная фраза (та же, что при шифровании)</param>
    /// <returns>Исходный текст</returns>
    public static string Decrypt(string cipherText, string passPhrase)
    {
        // 1. Извлекаем соль, IV и шифротекст из Base64
        byte[] fullCipher = Convert.FromBase64String(cipherText);
        byte[] salt = new byte[SaltSize];
        byte[] iv = new byte[IvSize];
        byte[] encrypted = new byte[fullCipher.Length - salt.Length - iv.Length];

        Buffer.BlockCopy(fullCipher, 0, salt, 0, salt.Length);
        Buffer.BlockCopy(fullCipher, salt.Length, iv, 0, iv.Length);
        Buffer.BlockCopy(fullCipher, salt.Length + iv.Length, encrypted, 0, encrypted.Length);

        // 2. Восстанавливаем ключ (те же параметры PBKDF2)
        byte[] key;
        using (var deriveBytes = new Rfc2898DeriveBytes(passPhrase, salt, 100000, HashAlgorithmName.SHA256))
        {
            key = deriveBytes.GetBytes(KeySize / 8);
        }

        // 3. Дешифруем
        string plainText;
        using (var aes = Aes.Create())
        {
            aes.KeySize = KeySize;
            aes.Key = key;
            aes.IV = iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            using (var ms = new MemoryStream(encrypted))
            using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read))
            using (var sr = new StreamReader(cs, Encoding.UTF8))
            {
                plainText = sr.ReadToEnd();
            }
        }

        return plainText;
    }
}

Как это работает?

  • Соль и IV — генерируются случайно для каждого вызова. Это гарантирует, что одинаковый текст будет давать разные шифротексты.
  • PBKDF2 — функция растяжения ключа. Парольная фраза превращается в криптостойкий ключ. 100 000 итераций замедляют атаки перебором.
  • Режим CBC — стандартный режим блочного шифрования, безопасный при правильном использовании IV.
  • Результат — соль и IV хранятся вместе с шифротекстом, поэтому для дешифрования нужен только пароль.

Пример использования

string original = "Мой секретный пароль!";
string password = "SuperSecretKey123";

string encrypted = StringCipher.Encrypt(original, password);
Console.WriteLine($"Зашифровано: {encrypted}");

string decrypted = StringCipher.Decrypt(encrypted, password);
Console.WriteLine($"Расшифровано: {decrypted}");

Важно: Храни парольную фразу в безопасном месте (например, в конфигурации приложения с ограниченным доступом). Для хранения паролей пользователей используй хеширование с солью (например, BCrypt.Net), а не шифрование.

<<>>

Альтернативный подход: использование Data Protection API (DPAPI) для Mono и .NET

Если ты работаешь в среде Windows или Mono (Linux) и тебе не нужно передавать зашифрованные данные между разными машинами, можно использовать Data Protection API (DPAPI). Он автоматически управляет ключами, используя учётные данные текущего пользователя или машины. Это проще в реализации, но привязано к конкретной среде.

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

public static class DpapiCipher
{
    public static string Encrypt(string plainText)
    {
        byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
        byte[] encrypted = ProtectedData.Protect(plainBytes, null, DataProtectionScope.CurrentUser);
        return Convert.ToBase64String(encrypted);
    }

    public static string Decrypt(string cipherText)
    {
        byte[] encrypted = Convert.FromBase64String(cipherText);
        byte[] plainBytes = ProtectedData.Unprotect(encrypted, null, DataProtectionScope.CurrentUser);
        return Encoding.UTF8.GetString(plainBytes);
    }
}

Плюсы и минусы

  • Плюсы: Не нужно управлять ключами, солью и IV — всё делает ОС. Код очень короткий.
  • Минусы: Данные нельзя расшифровать на другой машине или под другим пользователем. Работает только на Windows (встроенная поддержка) и Mono (через криптографи