Вопрос по c#, encryption – Расшифровка и расшифровка строки в C # [дубликаты]

296

На этот вопрос уже есть ответ:

Зашифровать и расшифровать строку в C #? 25 ответов

Какой самый современный (лучший) способ удовлетворить следующие требования в C #?

string encryptedString = SomeStaticClass.Encrypt(sourceString);

string decryptedString = SomeStaticClass.Decrypt(encryptedString);

НО с минимумом суеты, включающей соли, ключи, копание в байтах [] и т. Д.

Я погуглил и смутился из-за того, что я нашел (вы можете увидеть список похожих SO Q, чтобы увидеть, что это обманчивый вопрос).

Crypto это не просто. Читать Blogs.msdn.com / б / ericlippert / Архив / 2011/09/27 / ... SLaks
Все шифрование работает с байтовыми массивами. ИспользуйтеEncoding.UTF8. SLaks
Любой ответ на этот вопрос, который просто предлагает какой-то криптографический алгоритм и не обсуждает идентичность, управление ключами, целостность ... совершенно бесполезен. dtb
@ Richard: Какую цель ты имеешь в виду? Хранить пароли в базе данных, безопасно передавать сообщения на другой компьютер, ...? Правильный ответ во многом зависит от того, что вы пытаетесь сделать. dtb
@ dtb: я думаю, что вы преувеличиваете ситуацию здесь, OP нужен только простой класс, который может принимать чип, соль и против и обеспечивает симметричное шифрование. Он, видимо, не очень заботится о назначении функциональности. Tarik

Ваш Ответ

7   ответов
658

UPDATE 23 / Dec / 2015: Поскольку этот ответ, кажется, получает много голосов, я обновил его, чтобы исправить глупые ошибки и вообще улучшить код, основываясь на комментариях и отзывах. Смотрите в конце поста список конкретных улучшений.

Как уже говорили другие люди, криптография не проста, поэтому лучше избегать «раскручивать свой» алгоритм шифрования.

Вы можете, однако, «свернуть свой» класс обёртки вокруг чего-то вроде встроенногоRijndaelManaged класс криптографии.

Rijndael - это алгоритмическое имя текущего Расширенный стандарт шифрования, так что вы наверняка используете алгоритм, который можно считать «наилучшей практикой».

TheRijndaelManagedласс @ действительно обычно требует, чтобы вы «разбирались» с байтовыми массивами, солями, ключами, векторами инициализации и т. д., но это именно та деталь, которая может быть несколько абстрагирована в вашем классе «обертки».

Следующий класс, который я написал некоторое время назад для выполнения именно того, что вам нужно, - простой вызов одного метода, позволяющий зашифровать некоторый открытый текст на основе строки с помощью пароля на основе строки с полученной зашифрованной строкой. также представляется в виде строки. Конечно, есть эквивалентный метод для расшифровки зашифрованной строки с тем же паролем.

В отличие от первой версии этого кода, в которой каждый раз использовались одни и те же значения соли и IV, новая версия будет каждый раз генерировать случайные значения соли и IV. Поскольку соль и IV должны быть одинаковыми при шифровании и дешифровании заданной строки, соль и IV добавляются к зашифрованному тексту после шифрования и извлекаются из него снова для выполнения дешифрования. Результатом этого является то, что шифрование одного и того же открытого текста с одинаковым паролем дает совершенно разные результаты зашифрованного текста каждый раз.

"Сила" использования этого заключается в использованииRijndaelManaged класс для выполнения шифрования для вас, наряду с использованием Rfc2898DeriveBytes функцияSystem.Security.Cryptography namespace, которое сгенерирует ваш ключ шифрования, используя стандартный и безопасный алгоритм (в частности, PBKDF2) на основе введенного вами строкового пароля. (Обратите внимание, что это улучшение использования первой версии более старого алгоритма PBKDF1).

Наконец, важно отметить, что это все еще Неаутентифицированное шифрование. Одно только шифрование обеспечивает только конфиденциальность (т.е. сообщение неизвестно третьим сторонам), в то время как аутентифицированное шифрование имеет целью обеспечить конфиденциальность и аутентичность (то есть получатель знает, что сообщение было отправлено отправителем).

Не зная ваших точных требований, трудно сказать, является ли приведенный здесь код достаточно безопасным для ваших нужд, однако он был разработан для обеспечения хорошего баланса между относительной простотой реализации и «качеством». Например, если ваш «получатель» зашифрованной строки получает строку непосредственно от доверенного «отправителя», тогда аутентификация может даже не понадобиться.

Если вам требуется что-то более сложное, и которое предлагает аутентифицированное шифрование, проверьтеэта почт для реализации.

Вот код:

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

namespace EncryptStringSample
{
    public static class StringCipher
    {
        // This constant is used to determine the keysize of the encryption algorithm in bits.
        // We divide this by 8 within the code below to get the equivalent number of bytes.
        private const int Keysize = 256;

        // This constant determines the number of iterations for the password bytes generation function.
        private const int DerivationIterations = 1000;

        public static string Encrypt(string plainText, string passPhrase)
        {
            // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
            // so that the same Salt and IV values can be used when decrypting.  
            var saltStringBytes = Generate256BitsOfRandomEntropy();
            var ivStringBytes = Generate256BitsOfRandomEntropy();
            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream())
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                            {
                                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                                cryptoStream.FlushFinalBlock();
                                // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
                                var cipherTextBytes = saltStringBytes;
                                cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                                cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Convert.ToBase64String(cipherTextBytes);
                            }
                        }
                    }
                }
            }
        }

        public static string Decrypt(string cipherText, string passPhrase)
        {
            // Get the complete stream of bytes that represent:
            // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
            var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
            // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
            var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
            // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
            var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
            // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
            var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                var plainTextBytes = new byte[cipherTextBytes.Length];
                                var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }

        private static byte[] Generate256BitsOfRandomEntropy()
        {
            var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
            using (var rngCsp = new RNGCryptoServiceProvider())
            {
                // Fill the array with cryptographically secure random bytes.
                rngCsp.GetBytes(randomBytes);
            }
            return randomBytes;
        }
    }
}

Вышеупомянутый класс может использоваться довольно просто с кодом, подобным следующему:

using System;

namespace EncryptStringSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter a password to use:");
            string password = Console.ReadLine();
            Console.WriteLine("Please enter a string to encrypt:");
            string plaintext = Console.ReadLine();
            Console.WriteLine("");

            Console.WriteLine("Your encrypted string is:");
            string encryptedstring = StringCipher.Encrypt(plaintext, password);
            Console.WriteLine(encryptedstring);
            Console.WriteLine("");

            Console.WriteLine("Your decrypted string is:");
            string decryptedstring = StringCipher.Decrypt(encryptedstring, password);
            Console.WriteLine(decryptedstring);
            Console.WriteLine("");

            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }
    }
}

(Вы можете скачать простое примерное решение VS2013 (которое включает в себя несколько юнит-тестов)Во).

ОБНОВЛЕНИЕ 23 декабря 2015 г .: Список конкретных улучшений кода:

Исправлена глупая ошибка, при которой кодирование отличалось между шифрованием и дешифрованием. Поскольку механизм генерирования значений соли и IV изменился, кодирование больше не требуется. В связи с изменением соли / IV предыдущий комментарий к коду, в котором неверно указывалось, что кодировка UTF8 из 16-символьной строки выдает 32 байта, больше не применяется (поскольку кодирование больше не требуется). Использование замененного алгоритма PBKDF1 было заменено использованием более современного алгоритма PBKDF2. Деривация пароля теперь корректно засолена, тогда как раньше она вообще не была засолена (еще одна глупая ошибка исправлена).
обзор кода: PBKDF2 имеет больше смысла, так как вы генерируете более 160 бит ключевого материала. Однако ваш PBKDF1 даже не соленый вопреки утверждению выше. Ваши комментарии о IV неверны, 16-символьный UTF8 равен 16 байтам, он работает в любом случае, потому что размер IV фактически основан на размере блока, а не на размере ключа. Также IV декодируется ascii вместо utf8, декодированного в дешифровании. Ура ответ говорит об удалении солей и капельниц, но на самом деле вы просто удалили их - не современный или безопасный jbtule
Это шифрование без аутентификации. Злоумышленник может изменить сообщение, не имея возможности заметить. Он не можетучитьс сообщение, но он может изменить его. usr
@ CraigTP Хорошее объяснение, но 1000 итераций очень мало. Когда стандарт PBKDF2 был написан в 2000 году, рекомендуемое минимальное количество итераций составляло 1000, но этот параметр должен увеличиваться со временем по мере увеличения скорости процессора. В 2005 году стандарт Kerberos рекомендовал 4096 итераций, Apple iOS 3 использовала 2000, iOS 4 использовала 10000, а в 2011 году LastPass использовала 5000 итераций для клиентов JavaScript и 100000 итераций для хэширования на стороне сервера. Ogglas
Есть ли шанс для обновления .net Core, поскольку класс RijndaelManaged () недоступен в Core? onedevteam.com
Мне нельзя отвечать на вопрос, поэтому я просто комментирую здесь. Что касается .NET Standard 2, размер блока должен быть 128. Вот как он меняет ваш код: Github.com / nopara73 / DotNetEssentials / BLOB / Master / ... nopara73
69
using System.IO;
using System.Text;
using System.Security.Cryptography;

public static class EncryptionHelper
{
    public static string Encrypt(string clearText)
    {
        string EncryptionKey = "abc123";
        byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
        using (Aes encryptor = Aes.Create())
        {
            Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
            encryptor.Key = pdb.GetBytes(32);
            encryptor.IV = pdb.GetBytes(16);
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(clearBytes, 0, clearBytes.Length);
                    cs.Close();
                }
                clearText = Convert.ToBase64String(ms.ToArray());
            }
        }
        return clearText;
    }
    public static string Decrypt(string cipherText)
    {
        string EncryptionKey = "abc123";
        cipherText = cipherText.Replace(" ", "+");
        byte[] cipherBytes = Convert.FromBase64String(cipherText);
        using (Aes encryptor = Aes.Create())
        {
            Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
            encryptor.Key = pdb.GetBytes(32);
            encryptor.IV = pdb.GetBytes(16);
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(cipherBytes, 0, cipherBytes.Length);
                    cs.Close();
                }
                cipherText = Encoding.Unicode.GetString(ms.ToArray());
            }
        }
        return cipherText;
    }
}
Вероятно, вам не следует жестко кодировать ключ шифрования в методах. user1914368
Это очень полезно и просто для многочисленных случаев, когда нам не нужны сложности соли. Я просто сделал ключ шифрования параметром и смог успешно использовать код как есть. shev72
@ user1914368 - где он должен поместить ключ шифрования вместо жесткого кодирования его в метод? FrenkyB
@ FrenkyB, чтобы сделать метод переносимым, вы всегда можете передать ключ в качестве параметра метода. Например:public static string Encrypt(string clearText, string encryptionKey) Таким образом, вы можете иметь уникальные ключи для каждого вызова метода. sojim2
Мой анализатор кода предупредил, что переменнаяcs удаляется дважды. Нам не нужны избыточные заявлениcs.Close() в обоихEncrypt а такжеDecrypt методы, так как оба будут удалены после того, как контроль существуетusing блок. Jatin Sanghvi
26

Попробуйте этот класс:

public class DataEncryptor
{
    TripleDESCryptoServiceProvider symm;

    #region Factory
    public DataEncryptor()
    {
        this.symm = new TripleDESCryptoServiceProvider();
        this.symm.Padding = PaddingMode.PKCS7;
    }
    public DataEncryptor(TripleDESCryptoServiceProvider keys)
    {
        this.symm = keys;
    }

    public DataEncryptor(byte[] key, byte[] iv)
    {
        this.symm = new TripleDESCryptoServiceProvider();
        this.symm.Padding = PaddingMode.PKCS7;
        this.symm.Key = key;
        this.symm.IV = iv;
    }

    #endregion

    #region Properties
    public TripleDESCryptoServiceProvider Algorithm
    {
        get { return symm; }
        set { symm = value; }
    }
    public byte[] Key
    {
        get { return symm.Key; }
        set { symm.Key = value; }
    }
    public byte[] IV
    {
        get { return symm.IV; }
        set { symm.IV = value; }
    }

    #endregion

    #region Crypto

    public byte[] Encrypt(byte[] data) { return Encrypt(data, data.Length); }
    public byte[] Encrypt(byte[] data, int length)
    {
        try
        {
            // Create a MemoryStream.
            var ms = new MemoryStream();

            // Create a CryptoStream using the MemoryStream 
            // and the passed key and initialization vector (IV).
            var cs = new CryptoStream(ms,
                symm.CreateEncryptor(symm.Key, symm.IV),
                CryptoStreamMode.Write);

            // Write the byte array to the crypto stream and flush it.
            cs.Write(data, 0, length);
            cs.FlushFinalBlock();

            // Get an array of bytes from the 
            // MemoryStream that holds the 
            // encrypted data.
            byte[] ret = ms.ToArray();

            // Close the streams.
            cs.Close();
            ms.Close();

            // Return the encrypted buffer.
            return ret;
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine("A cryptographic error occured: {0}", ex.Message);
        }
        return null;
    }

    public string EncryptString(string text)
    {
        return Convert.ToBase64String(Encrypt(Encoding.UTF8.GetBytes(text)));
    }

    public byte[] Decrypt(byte[] data) { return Decrypt(data, data.Length); }
    public byte[] Decrypt(byte[] data, int length)
    {
        try
        {
            // Create a new MemoryStream using the passed 
            // array of encrypted data.
            MemoryStream ms = new MemoryStream(data);

            // Create a CryptoStream using the MemoryStream 
            // and the passed key and initialization vector (IV).
            CryptoStream cs = new CryptoStream(ms,
                symm.CreateDecryptor(symm.Key, symm.IV),
                CryptoStreamMode.Read);

            // Create buffer to hold the decrypted data.
            byte[] result = new byte[length];

            // Read the decrypted data out of the crypto stream
            // and place it into the temporary buffer.
            cs.Read(result, 0, result.Length);
            return result;
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine("A cryptographic error occured: {0}", ex.Message);
        }
        return null;
    }

    public string DecryptString(string data)
    {
        return Encoding.UTF8.GetString(Decrypt(Convert.FromBase64String(data))).TrimEnd('\0');
    }

    #endregion

}

и используйте это так:

string message="A very secret message here.";
DataEncryptor keys=new DataEncryptor();
string encr=keys.EncryptString(message);

// later
string actual=keys.DecryptString(encr);
Это не плохой ответ, потому что разнообразие может поддерживать сильные алгоритмы шифрования. Я не думаю, что предупреждение об этом конкретном подходе должно быть надежным, поскольку ни один механизм шифрования не является идеальны マルちゃん だよ
Этот ответ приличный. TDES часто используется для защиты транзакций банкоматов, когда они передаются по телефонной линии. Я добавил +1, я не уверен, почему кто-то еще имел -1. psyklopz
Diversity - это не то, что поддерживает алгоритмы шифрования. Математика делает это :) DES (даже тройной DES) - старый алгоритм, который недостаточно силен для большинства приложений. Eric W
Это было именно то, что я искал для моего заявления. Не нужно много, чтобы убедиться, что пользователь не может выбрать следующее целое число в последовательности. Таким образом, вместо использования ссылки, в которую включено 380, они получают случайную строку, поэтому они не могут использовать 381 вместо этого. Andrew MacNaughton
Это решение было основано на.NET 2.0 и недавно Microsoft обновила пространство имен криптографии. Я подозреваю, что мой ответ устарел. ja72
18

используйте SecureString:

http: //msdn.microsoft.com/en-us/library/system.security.securestring.asp

Для более общего использования я бы использовал утвержденный FIPS алгоритм, такой как Advanced Encryption Standard, ранее известный как Rijndael. Смотрите эту страницу для примера реализации:

http: //msdn.microsoft.com/en-us/library/system.security.cryptography.rijndael.asp

думаю, что @SecureString не совсем подходит, но это хорошо, когда вы работаете в безопасной среде, такой как приложения для банков ... Tarik
Это зависит от того, что тебе нужно делать. Я впервые познакомился с ним, когда мы должны были зашифровать учетные данные в памяти в приложениях для правительства. Ulises
Они также предоставляют AES (и другие), кроме Rijndael: Docs.microsoft.com / EN-US / DotNet / апи / ... Ioanna
12

RijndaelManaged еще можно использоватьIDataProtectionProvider.

Во-первых, настройте свое приложение на использование защиты данных:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDataProtection();
    }
    // ...
}

Тогда ты сможешь делать инъекцииIDataProtectionProvider экземпляр и использовать его для шифрования / дешифрования данных:

public class MyService : IService
{
    private const string Purpose = "my protection purpose";
    private readonly IDataProtectionProvider _provider;

    public MyService(IDataProtectionProvider provider)
    {
        _provider = provider;
    }

    public string Encrypt(string plainText)
    {
        var protector = _provider.CreateProtector(Purpose);
        return protector.Protect(plainText);
    }

    public string Decrypt(string cipherText)
    {
        var protector = _provider.CreateProtector(Purpose);
        return protector.Unprotect(cipherText);
    }
}

ВидетьЭта стать Больше подробностей

8

ProtectedData class, который шифрует данные с использованием учетных данных пользователя.

Я читаю документацию MSDN, но в ней не говорится, что произойдет, если мы переместим эти зашифрованные данные на другой компьютер с другими учетными данными. Значит, мы все равно сможем его расшифровать? Tarik
@ Braveyard Просто расшифруйте его при экспорте и снова зашифруйте на новом компьютере. Richard Hein
@ RichardHein: Я тоже так думал, но не кажется мудрым советом. Когда я перемещаю информацию, мне не нравится делать это в простом читабельном формате, иначе каков смысл всех этих вещей шифрования .. Tarik
@ Braveyard Затем расшифруйте на старой машине - снова зашифруйте, используя общий ключ для транспорта, на старой машине - расшифруйте на новой машине с общим ключом - зашифруйте с новым ключом машины? Richard Hein
@ RichardHein: если вы хотите перемещать данные между компьютерами,ProtectedData не подходит для работы. (Если вы не находитесь в домене, чтобы у них были одни и те же пользователи повсюду) SLaks
-1

Проверьте MSDN на нем:http: //msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.asp

Это предполагает использование байтов, но когда дело доходит до этого, вы действительно хотите, чтобы шифрование и дешифрование было трудно понять, иначе их будет легко взломать.

RSA является асимметричным, что вряд ли будет тем, что он хочет. SLaks

Похожие вопросы