在c#中满足下列条件的最现代(最好)的方法是什么?
string encryptedString = SomeStaticClass.Encrypt(sourceString);
string decryptedString = SomeStaticClass.Decrypt(encryptedString);
但是最少的麻烦涉及盐,键,字节[],等等。
我一直在谷歌上搜索,对我的发现感到困惑(你可以看到类似的SO q列表,看看这是一个欺骗性的问题)。
在c#中满足下列条件的最现代(最好)的方法是什么?
string encryptedString = SomeStaticClass.Encrypt(sourceString);
string decryptedString = SomeStaticClass.Decrypt(encryptedString);
但是最少的麻烦涉及盐,键,字节[],等等。
我一直在谷歌上搜索,对我的发现感到困惑(你可以看到类似的SO q列表,看看这是一个欺骗性的问题)。
我所见过的最简单的加密方法是RSA
看看它的MSDN: http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.aspx
它确实涉及到使用字节,但当它归结到这一点时,你确实希望加密和解密很难弄清楚,否则就很容易被黑客攻击。
如果你需要在内存中存储密码,并且想要加密它,你应该使用SecureString:
http://msdn.microsoft.com/en-us/library/system.security.securestring.aspx
对于更一般的用途,我会使用FIPS批准的算法,如高级加密标准,以前称为Rijndael。请参阅本页的实现示例:
http://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndael.aspx
试试这门课:
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);
2015年12月23日更新:由于这个答案似乎得到了很多赞,我已经更新了它,以修复愚蠢的错误,并根据评论和反馈普遍改进代码。有关具体改进的列表,请参见文章的末尾。
正如其他人所说,密码学并不简单,所以最好避免“滚动您自己的”加密算法。
但是,您可以围绕内置的RijndaelManaged加密类之类的东西“滚出自己的”包装器类。
Rijndael是当前高级加密标准的算法名称,所以您使用的算法可以被认为是“最佳实践”。
RijndaelManaged类通常确实需要您“摆弄”字节数组、盐、键、初始化向量等,但这恰恰是可以在“包装器”类中抽象的那种细节。
下面的类是我不久前编写的,目的正是执行您所需要的那种操作,它是一个简单的方法调用,允许使用基于字符串的密码对一些基于字符串的明文进行加密,生成的加密字符串也表示为字符串。当然,有一种等效的方法可以用相同的密码解密加密的字符串。
Unlike the first version of this code, which used the exact same salt and IV values every time, this newer version will generate random salt and IV values each time. Since salt and IV must be the same between the encryption and decryption of a given string, the salt and IV is prepended to the cipher text upon encryption and extracted from it again in order to perform the decryption. The result of this is that encrypting the exact same plaintext with the exact same password gives and entirely different ciphertext result each time.
使用这种方法的“优点”在于使用RijndaelManaged类为您执行加密,同时使用System.Security.Cryptography名称空间的Rfc2898DeriveBytes函数,该函数将使用基于您提供的基于字符串的密码的标准安全算法(特别是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))
using (var streamReader = new StreamReader(cryptoStream, Encoding.UTF8))
{
return streamReader.ReadToEnd();
}
}
}
}
}
}
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样例解决方案(其中包括一些单元测试)。
2015年/ 12月23日更新: 对代码的具体改进如下:
Fixed a silly bug where encoding was different between encrypting and decrypting. As the mechanism by which salt & IV values are generated has changed, encoding is no longer necessary. Due to the salt/IV change, the previous code comment that incorrectly indicated that UTF8 encoding a 16 character string produces 32 bytes is no longer applicable (as encoding is no longer necessary). Usage of the superseded PBKDF1 algorithm has been replaced with usage of the more modern PBKDF2 algorithm. The password derivation is now properly salted whereas previously it wasn't salted at all (another silly bug squished).
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;
}
}
如果你的目标是ASP。NET Core还不支持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);
}
}
有关详细信息,请参阅本文。