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.
- Вывод пароля теперь должным образом солен, тогда как ранее он не был солен вообще (другая глупая ошибка свалилась).
Крипто не прост. Прочтите http://blogs.msdn.com/b/ericlippert/archive/2011/09/27/keep-it-secret-keep-it-safe.aspx – SLaks
Все криптографии работают на байт-массивах. Используйте 'Encoding.UTF8'. – SLaks
Любой ответ на этот вопрос, который просто предлагает некоторый криптоалгоритм и не обсуждает личность, управление ключами, целостность ... совершенно бесполезен. – dtb