.NET提供了一组类型来实现对称加密和解密。这些类型拥有共同的基类SymmetricAlgorithm,如图所示。
可以看到上面类型也分为后缀为"CryptoServiceProvider"和"Managed"两种,作用相同,区别是Managed后缀的类是由托管代码写的,CryptoServiceProvider后缀的类调用的是Windows Crypto API,相当于一个包装类。
现在假设选择TripleDES作为算法,加密的流程如下:
1. 先创建一个TripleDESCryptoServiceProvider的实例,比如provider。
2. 在provider上指定密钥和IV,也就是它的Key属性和IV属性。
这里简单解释一下IV(Initialization vector,初始化向量),如果一个字符串(或者数据)在加密之前很多部分是重复的,比如ABCABCABC,那么加密之后尽管字符串是乱码,但相关部分也是重复的。为了解决这个问题,就引入了IV,在使用它以后,加密之后即使是重复的也被打乱了。
对于特定算法,密钥和IV的值可以随意指定,但长度是固定的,通常密钥为128位或196位,IV为64位。密钥和IV都是byte[]类型,因此,如果使用Encoding类来将字符串转换为byte[],那么编码方式就很重要,因为UTF8是变长编码,所以对于中文和英文,需要特别注意byte[]的长度问题。
3.1 如果是加密,在provider上调用CreateEncryptor()方法,创建一个ICryptoTransform类型的加密器对象;
3.2 如果是解密,在provider上调用CreateDecryptor()方法,同样创建一个ICryptoTransform类型的解密器对象。
3.3 ICryptoTransform定义了加密转换的运算,.NET将在底层调用这个接口。
4.1 因为流和byte[]是数据类型无关的一种数据结构,可以保存和传输任何形式的数据,区别只是byte[]是一个静态的概念而流是一个动态的概念。
4.2 因此,.NET采用了流的方式进行加密和解密,运算过程会涉及两个流,一个是明文流,含有加密前的数据;一个是密文流,含有加密后的数据。
4.3 那么就必然有一个中介者,将明文流转换为密文流;或者将密文流转换为明文流。.NET中执行这个操作的中介者也是一个流类型,叫做CryptoStream。
4.4 CryptoStream的构造函数如下,共有三个参数:
public CryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
5. 当加密时,构造函数签名中的stream参数为密文流(注意此时密文流还没有包含数据,仅仅是一个空流);ICryptoTransform是步骤3.1创建的加密器,负责进行加密运算;CryptoStreamMode枚举为Write,意思是将流经CryptoStream的明文流写入到密文流中。
最后,从密文流中获得加密后的数据。
6. 当解密时,stream为密文流(此时密文流含有数据);ICryptoTransform是步骤3.2创建的解密器,负责进行解密运算;CryptoStreamMode枚举为Read,意思是将密文流中的数据读出到明文流,进而再转换为明文的、原本的格式。
可见,CryptoStream总是接受密文流,并且根据CryptoStreamMode枚举的值来决定是将明文流写入到密文流(加密),还是将密文流读入到明文流中(解密)。
下面是一个用于对称加密和解密的SymmetricCryptoHelper帮助类:
// 对称加密帮助类
public class SymmetricCryptoHelper
{
private ICryptoTransform encryptor; // 加密器对象
private ICryptoTransform decryptor; // 解密器对象
private const int BufferSize = 1024;
public SymmetricCryptoHelper(string algorithmName, byte[] key)
{
//1和2 创建对称加密算法提供器provider,并指定密钥和IV
using (SymmetricAlgorithm provider = SymmetricAlgorithm.Create(algorithmName))
{
provider.Key = key;
provider.IV = new byte[] { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
//创建加密器对象
encryptor = provider.CreateEncryptor();
//创建解密器对象
decryptor = provider.CreateDecryptor();
}
}
/// <summary>
/// 默认使用TripleDES对称算法
/// </summary>
/// <param name="key"></param>
public SymmetricCryptoHelper(byte[] key) : this(SymmetricCryptoTypes.TripleDES, key)
{
}
// 加密算法
public string Encrypt(string clearText)
{
// 根据明文文本创建明文流
byte[] clearBuffer = Encoding.UTF8.GetBytes(clearText);
using (MemoryStream clearStream = new MemoryStream(clearBuffer))
{
// 创建空的密文流
using (MemoryStream encryptedStream = new MemoryStream())
{
// 创建控制加密的流对象cryptoStream
using (CryptoStream cryptoStream = new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write))
{
// 将明文流写入到buffer中
// 将buffer中的数据写入到cryptoStream中
int bytesRead = 0;
byte[] buffer = new byte[BufferSize];
do
{
//使用buffer字节数组,批量从明文数据流中获取数据,然后写入cryptoStream,并进行了加密
bytesRead = clearStream.Read(buffer, 0, BufferSize);
cryptoStream.Write(buffer, 0, bytesRead);
}
while (bytesRead > 0);
//从缓冲区写入encryptedStream对象中
cryptoStream.FlushFinalBlock();
// 获取加密后的文本
buffer = encryptedStream.ToArray();
// 对加密后的字节进行base64编码处理
string encryptedText = Convert.ToBase64String(buffer);
return encryptedText;
}
}
}
}
// 解密算法
public string Decrypt(string encryptedText)
{
// 对加密的数据进行base64解码
byte[] encryptedBuffer = Convert.FromBase64String(encryptedText);
// 得到加密的数据流
using (Stream encryptedStream = new MemoryStream(encryptedBuffer))
{
// 创建空的明文流
using (MemoryStream clearStream = new MemoryStream())
{
// 创建控制解密的流对象cryptoStream
using (CryptoStream cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))
{
int bytesRead = 0;
byte[] buffer = new byte[BufferSize];
do
{
//通过buffer字节数组,批量从密文数据流中读取明文流数据
bytesRead = cryptoStream.Read(buffer, 0, BufferSize);
//得到解密后的文明流然后写入clearStream
clearStream.Write(buffer, 0, bytesRead);
}
while (bytesRead > 0);
//获取明文流中的字节数据
buffer = clearStream.GetBuffer();
//通过UTF8编码获得文本字符串
string clearText = Encoding.UTF8.GetString(buffer, 0, (int)clearStream.Length);
return clearText;
}
}
}
}
public static string Encrypt(string clearText, string key)
{
byte[] keyData = new byte[16];
byte[] sourceData = Encoding.UTF8.GetBytes(key);
//密钥长度固定为16字节,那么当传入的字符串key转换成数组后太短了的时候,
//需要将keyData补齐为16位,数组后面空余的位数补0;
//当传入的字符串key转换成数组后太长了的时候,则只取前面的16位。
//正确的密钥长度可以通过在算法对象上调用LegalKeySizes获得(对本例来说,例如provider.LegalKeySizes)。
int copyBytes = 16;
if (sourceData.Length < 16)
{
copyBytes = sourceData.Length;
}
Array.Copy(sourceData, keyData, copyBytes);
//准备好了定长的keyData,然后初始化对称加密算法提供器
SymmetricCryptoHelper helper = new SymmetricCryptoHelper(keyData);
return helper.Encrypt(clearText);
}
public static string Decrypt(string encryptedText, string key)
{
byte[] keyData = new byte[16];
byte[] sourceData = Encoding.UTF8.GetBytes(key);
//密钥长度固定为16字节,那么当传入的字符串key转换成数组后太短了的时候,
//需要将keyData补齐为16位,数组后面空余的位数补0;
//当传入的字符串key转换成数组后太长了的时候,则只取前面的16位。
//正确的密钥长度可以通过在算法对象上调用LegalKeySizes获得(对本例来说,例如provider.LegalKeySizes)。
int copyBytes = 16;
if (sourceData.Length < 16)
{
copyBytes = sourceData.Length;
}
Array.Copy(sourceData, keyData, copyBytes);
//准备好了定长的keyData,然后初始化对称加密算法提供器
SymmetricCryptoHelper helper = new SymmetricCryptoHelper(keyData);
return helper.Decrypt(encryptedText);
}
}
public class SymmetricCryptoTypes
{
public const string DES = "DES";
public const string TripleDES = "TripleDES";
public const string Rijndael = "Rijndael";
public const string RC2 = "RC2";
}
对于这个帮助类,我添加了详细的注释,请结合代码进行阅读。
可以对上面的CryptoHelper类进行以下测试:
string key = "secret key";
string plainText = "Hello, world!";
string encryptedText = SymmetricCryptoHelper.Encrypt(plainText, key);
Console.WriteLine(encryptedText);
string clearText = SymmetricCryptoHelper.Decrypt(encryptedText, key);
Console.WriteLine(clearText);
可以看到下面的输出结果:
+l2YvR8Sm9l54pG3atJYxw==
Hello, world!