问题描述
string encryptedString = SomeStaticClass.Encrypt( sourceString);
string decryptptedString = SomeStaticClass.Decrypt(encryptedString);
但是最少涉及盐,钥匙,与byte []等相关的困扰。 / p>
Googling和困惑我发现(你可以看到类似的SO Q的列表,看到这是一个欺骗性的问题要问)。
UPDATE 23 / Dec / 2015:由于这个答案似乎有很多upvote,我更新了修复傻错误,并通常根据意见和反馈来改进代码。查看帖子的最后一个具体改进列表。
正如其他人所说,密码学并不简单,所以最好避免滚动你的自己的加密算法。
然而,您可以围绕内置加密类。
Rijndael是当前类确实通常需要你垃圾关于与字节数组,盐,ke ys,初始化向量等,但这正是在包装器类中可以稍微抽象出来的那种细节。
以下类是我写的一个而之前要完全执行您之前所做的那种事情,一种简单的单一方法调用以允许使用基于字符串的密码对基于字符串的明文进行加密,并将生成的加密字符串也表示为字符串。当然,使用相同的密码来解密加密的字符串也是一种等价的方法。
与第一个版本的代码不同,每个代码使用完全相同的盐和IV值时间,这个较新的版本将每次生成随机盐和IV值。由于盐和IV在给定字符串的加密和解密之间必须是相同的,所以在加密之后,将盐和IV加密为密文,然后再从其中提取出来,以便执行解密。这样做的结果是,使用完全相同的密码加密完全相同的明文,每次给出完全不同的密文结果。
使用它的强度来自于使用类为您执行加密,以及使用函数,它将使用标准生成您的加密密钥,并使用 System.Security.Cryptography
命名空间基于您提供的基于字符串的密码,安全算法(具体来说,)。 (请注意,这是对旧版PBKDF1算法的第一版本的改进)。
最后,请注意,这仍然是未经身份验证的>加密。单独的加密仅提供隐私(即,消息对于第三方来说是未知的),而认证加密旨在提供隐私和真实性(即接收者知道消息由发送者发送)。
在不了解您的具体要求的情况下,很难说这里的代码是否足够安全,满足您的需求,但是,它已经产生了在相对简单性之间实现良好的平衡的实施与质量。例如,如果加密字符串的接收方直接从受信任的发件人接收字符串,则认证。
如果您需要更复杂的内容,哪些提供验证加密,请查看用于实现。
这里是代码: / p>
using System;
使用System.Text;
使用System.Security.Cryptography;
使用System.IO;
使用System.Linq;
命名空间EncryptStringSample
{
public static class StringCipher
{
//此常量用于以位为单位确定加密算法的密钥大小。
//我们在下面的代码中将它除以8,以获得等效的字节数。
private const int Keysize = 256;
//此常量决定密码字节生成函数的迭代次数。
private const int DerivationIterations = 1000;
public static string Encrypt(string plainText,string passPhrase)
{
// Salt和IV每次随机生成,但预先加密为加密密文
//使得在解密时可以使用相同的Salt和IV值。
var saltStringBytes = Generate256BitsOfRandomEntropy();
var ivStringBytes = Generate256BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
使用(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();
//将最后的字节创建为随机盐字节的连接,随机的iv字节和密码字节。
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)
{
//获取表示
// [32字节的盐] + [32字节的IV] + [n字节的CipherText]的完整的字节流
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
//通过从提供的cipherText字节中提取前32个字节来获取saltbytes。
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
//通过从提供的cipherText字节中提取下一个32个字节,获取IV字节。
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
//通过从cipherText字符串中删除前64个字节,获取实际的密文字节。
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8)* 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8)* 2))ToArray();
使用(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;
使用(var decryptor = symmetricKey.CreateDecryptor(keyBytes,ivStringBytes))
{
使用(var memoryStream = new MemoryStream(cipherTextBytes))
{
using cryptoStream = new CryptoStream(memoryStream,decryptor,CryptoStreamMode.Read))
{
var plainTextBytes = new byte [cipherTextBytes.Length];
var decryptptedByteCount = cryptoStream.Read(plainTextBytes,0,plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes,0,decryptptedByteCount);
}
}
}
}
}
}
private static byte [] Generate256BitsOfRandomEntropy()
{
var randomBytes = new byte [32]; // 32字节将给我们256位。
使用(var rngCsp = new RNGCryptoServiceProvider())
{
//使用加密安全的随机字节填充数组。
rngCsp.GetBytes(randomBytes);
}
返回randomBytes;
}
}
}
上面的类可以使用相当简单的代码类似于以下内容:
using System;
命名空间EncryptStringSample
{
类程序
{
static void Main(string [] args)
{
控制台。 WriteLine(请输入使用密码:);
string password = Console.ReadLine();
Console.WriteLine(请输入要加密的字符串:);
string plaintext = Console.ReadLine();
Console.WriteLine();
Console.WriteLine(您的加密字符串为:);
string encryptedstring = StringCipher.Encrypt(明文,密码);
Console.WriteLine(encryptedstring);
Console.WriteLine();
Console.WriteLine(您的解密字符串为:);
string decryptptedstring = StringCipher.Decrypt(encryptedstring,password);
Console.WriteLine(decryptptedstring);
Console.WriteLine();
Console.WriteLine(按任意键退出...);
Console.ReadLine();
}
}
}
(您可以下载一个简单的VS2013样本解决方案(其中包括几个单元测试)) 。
更新23 / Dec / 2015:
代码的具体改进列表如下:
- 修复了加密和
解密之间编码不同的愚蠢错误。作为盐和盐的机理生成的IV值已更改,编码不再需要。 - 由于salt / IV更改,以前的代码注释错误地表示UTF8编码16个字符的字符串产生32个字节不再适用(因为编码不再需要)。
- 替换了更现代的PBKDF2算法的使用,取代了PBKDF1算法。
- 密码派生现在已经正确的盐化了,而以前它根本没有盐渍(另一个愚蠢的bug)。
What is the most modern (best) way of satisfying the following in C#?
string encryptedString = SomeStaticClass.Encrypt(sourceString);
string decryptedString = SomeStaticClass.Decrypt(encryptedString);
BUT with a minimum of fuss involving salts, keys, mucking about with byte[], etc.
Been Googling and confused at what I'm finding (you can see the list of similar SO Qs to see this is a deceptive question to ask).
UPDATE 23/Dec/2015: Since this answer seems to be getting a lot of upvotes, I've updated it to fix silly bugs and to generally improve the code based upon comments and feedback. See the end of the post for a list of specific improvements.
As other people have said, Cryptography is not simple so it's best to avoid "rolling your own" encryption algorithm.
You can, however, "roll your own" wrapper class around something like the built-in RijndaelManaged
cryptography class.
Rijndael is the algorithmic name of the current Advanced Encryption Standard, so you're certainly using an algorithm that could be considered "best practice".
The RijndaelManaged
class does indeed normally require you to "muck about" with byte arrays, salts, keys, initialization vectors etc. but this is precisely the kind of detail that can be somewhat abstracted away within your "wrapper" class.
The following class is one I wrote a while ago to perform exactly the kind of thing you're after, a simple single method call to allow some string-based plaintext to be encrypted with a string-based password, with the resulting encrypted string also being represented as a string. Of course, there's an equivalent method to decrypt the encrypted string with the same password.
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.
The "strength" of using this comes from using the RijndaelManaged
class to perform the encryption for you, along with using the Rfc2898DeriveBytes function of the System.Security.Cryptography
namespace which will generate your encryption key using a standard and secure algorithm (specifically, PBKDF2) based upon the string-based password you supply. (Note this is an improvement of the first version's use of the older PBKDF1 algorithm).
Finally, it's important to note that this is still unauthenticated encryption. Encryption alone provides only privacy (i.e. message is unknown to 3rd parties), whilst authenticated encryption aims to provide both privacy and authenticity (i.e. recipient knows message was sent by the sender).
Without knowing your exact requirements, it's difficult to say whether the code here is sufficiently secure for your needs, however, it has been produced to deliver a good balance between relative simplicity of implementation vs "quality". For example, if your "receiver" of an encrypted string is receiving the string directly from a trusted "sender", then authentication may not even be necessary.
If you require something more complex, and which offers authenticated encryption, check out this post for an implementation.
Here's the code:
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;
}
}
}
The above class can be used quite simply with code similar to the following:
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();
}
}
}
(You can download a simple VS2013 sample solution (which includes a few unit tests) here).
UPDATE 23/Dec/2015:The list of specific improvements to the code are:
- Fixed a silly bug where encoding was different between encrypting anddecrypting. 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).
这篇关于加密&在C#中解密一个字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!