2012-04-16 8 views
230

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

string encryptedString = SomeStaticClass.Encrypt(sourceString); 

string decryptedString = SomeStaticClass.Decrypt(encryptedString); 

НО с минимумом возни с участием солей, ключи, отводом о с Byte [] и т.д.

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

+18

Крипто не прост. Прочтите http://blogs.msdn.com/b/ericlippert/archive/2011/09/27/keep-it-secret-keep-it-safe.aspx – SLaks

+0

Все криптографии работают на байт-массивах. Используйте 'Encoding.UTF8'. – SLaks

+7

Любой ответ на этот вопрос, который просто предлагает некоторый криптоалгоритм и не обсуждает личность, управление ключами, целостность ... совершенно бесполезен. – dtb

ответ

533

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

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

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

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

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

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

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

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

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

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

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

Вот код:

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 образца (который включает в себя несколько модульных тестов) here).

UPDATE 23/Декабрь/2015: Перечень конкретных улучшений в коде:

  • Исправлена ​​глупая ошибка, когда кодировка отличается от шифрования и дешифрования . Поскольку механизм, по которому генерируется соль &, меняются значения IV, кодирование больше не требуется.
  • Из-за изменения соли/IV предыдущий комментарий кода, который неправильно указал, что UTF8, кодирующий 16-значную строку, создает 32 байта, больше не применим (поскольку кодирование больше не требуется).
  • Использование замененного алгоритма PBKDF1 заменено использованием более современного алгоритма PBKDF2.
  • Вывод пароля теперь должным образом солен, тогда как ранее он не был солен вообще (другая глупая ошибка свалилась).
+0

Если я конвертирую в long или int вместо строки, как я могу убедиться, что зашифрованное число не переполняется? –

+0

@IanWarburton Я не слишком уверен, что вы имеете в виду. Вы не сможете преобразовать зашифрованный вывод в число, поскольку выход [Base64 encoded] (http://en.wikipedia.org/wiki/Base64) (то есть он будет выглядеть примерно так: iBqFa0LvBIsXiyMGf0oMKw ==) , Вы можете преобразовать числовой ввод в его строковое представление перед шифрованием, но вам нужно знать, является ли эта строка ввода int32/int64 и т. Д. После того, как вы расшифруете, поскольку процедура шифрования касается только строк. – CraigTP

+0

Я имею в виду, что я отменяю создание байтового массива, а вместо него кодирует его base64, я конвертирую его в int или long. –

8

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

+3

Я читал документацию MSDN, но он не утверждает, что произойдет, если мы переместим эти зашифрованные данные на другую машину с разными учетными данными. Таким образом, мы все еще можем расшифровать его обратно? – Tarik

+1

@Braveyard Просто расшифруйте его при экспорте и зашифруйте его снова на новой машине. –

+1

@ RichardHein: Я тоже так думал, но не кажется мудрым советом. Когда я перемещаю информацию, я не люблю делать это в простом человекообразном формате, иначе в чем смысл всех этих вещей шифрования. – Tarik

-1

Самый простой способ, который я видел, чтобы сделать шифрование с помощью RSA

Ознакомьтесь с MSDN на нем: http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.aspx

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

+6

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

16

Если вам нужно сохранить пароль в памяти, и хотел бы, чтобы он зашифрован, вы должны использовать SecureString:

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

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

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

+0

SecureString не подходит, я думаю, но это хорошо, когда вы работаете в защищенной среде, например, в приложениях для банков ... – Tarik

+0

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

22

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

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); 
+3

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

+1

Этот ответ порядочный. TDES много используется для обеспечения транзакций ATM, когда они путешествуют по телефонной линии. Я сделал это, я не знаю, почему кто-то еще это сделал. – psyklopz

+1

Разнообразие не является тем, что поддерживает алгоритмы шифрования. Математика делает это: DES (даже тройной DES) - это старый алгоритм, который уже не является достаточно сильным для большинства приложений. –

42
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; 
    } 
} 
+0

Возможно, вы не должны жестко закодировать ключ шифрования в методах. – user1914368

+8

Это очень полезно и просто для многочисленных случаев, когда нам не нужны сложности соли. Я просто сделал ключ шифрования параметром и смог использовать код успешно, как есть. – shev72

+0

@ user1914368 - где он должен класть ключ шифрования вместо жесткого кодирования его в метод? – FrenkyB

5

Если вы ориентируетесь ASP.NET ядро, которое не поддерживает 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); 
    } 
} 

this article См для более подробно.

Смежные вопросы