封装自己的对称加密模块
本文技术含量不高,基本只是抽取 Enterprise Library 5.0的几个类,对代码稍作修改,并且给出使用演示示例!
一,对称加密解密算法的了解,System.Security.Cryptography;
查看.net framework 可知很多对称加密解密算法都被封装到了Cryptography空间下,
包括有:Aes、DES、 RC2、RSA、Rijndael、TripleDES、SHA等等等。。。。
SymmetricAlgorithm;
表示所有对称算法的实现都必须从中继承的抽象基类。
如:SymmetricAlgorithm symmetricAlgorithm = Activator.CreateInstance(algorithmType) as SymmetricAlgorithm;
二,DPAPI的了解,ProtectedData
ProtectedData类实现了DPAPI,提供保护数据和取消数据保护的方法。无法继承此类。
三,封装我们的加密解密库
直接从微软企业库提取出以下3个类:
CryptographyUtility,此类包含一些通用的处理方法,如byte[]比较,byte[]与十六进制的转换,数据流到加密解密流的转换等
SymmetricCryptographer,此类就是实现我们对称加密解密的服务类,通过SymmetricAlgorithm基类获取一个对称加密解密算法的实例对象,进行加密解密
ProtectedKey,此封装了使用DPAPI对对称算法的key进行保护
下面帖上完整代码+注释,如有不对的地方欢迎批评指正!
using System.IO;
using System.Security.Cryptography;
namespace CryptBlock
{
public class SymmetricCryptographer : IDisposable
{
private SymmetricAlgorithm algorithm; //表示一个对称加密算法的对象,在Cryptography空间下所有对称加密算法的基类
private ProtectedKey key; //key=ProtectedKey.CreateFromPlaintextKey() key对象通过CreateFromPlaintextKey方法或CreateFromEncryptedKey方法返回
public byte[] EnKey
{
get
{
return key.EncryptedKey; //由DPAPI加密过的密钥
}
}
public byte[] Key
{
get
{
return key.DecryptedKey; //返回由DPAPI解密出来 ,对称加密算法的key
}
}
/// <summary>
/// [构造函数] 创建一个对称加密实例
/// </summary>
/// <param name="algorithmType"></param>
/// <param name="key"></param>
public SymmetricCryptographer(Type algorithmType, ProtectedKey key)
{
if (algorithmType == null) throw new ArgumentNullException("algorithmType Null");
if (!typeof(SymmetricAlgorithm).IsAssignableFrom(algorithmType)) throw new ArgumentException("algorithmType IsAssignable Error");
if (key == null) throw new ArgumentNullException("key Null");
this.key = key;
this.algorithm = GetSymmetricAlgorithm(algorithmType);
}
/// <summary>
/// Finalizer for <see cref="SymmetricCryptographer"/>
/// </summary>
~SymmetricCryptographer()
{
Dispose(false);
}
/// <summary>
/// Releases all resources for this instance.
/// </summary>
public void Dispose()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
/// <summary>
/// Override to customize resources to be disposed.
/// </summary>
/// <param name="disposing">Unused.</param>
protected virtual void Dispose(bool disposing)
{
if (algorithm != null)
{
algorithm.Clear();
algorithm = null;
}
}
/// <summary>
/// 加密
/// </summary>
/// <param name="plaintext"></param>
/// <returns></returns>
public byte[] Encrypt(byte[] plaintext)
{
byte[] output = null;
byte[] cipherText = null;
this.algorithm.Key = Key; //加密算法对象的key值,此Key由DPAPI的解密返回
using (ICryptoTransform transform = this.algorithm.CreateEncryptor()) //[CreateEncryptor]:用当前的 Key 属性和初始化向量 (IV) 创建对称加密器对象
{
cipherText = Transform(transform, plaintext);
}
output = new byte[IVLength + cipherText.Length];
Buffer.BlockCopy(this.algorithm.IV, 0, output, 0, IVLength);//[BlockCopy]:将指定数目的字节从起始于特定偏移量的源数组复制到起始于特定偏移量的目标数组。
Buffer.BlockCopy(cipherText, 0, output, IVLength, cipherText.Length);
CryptographyUtility.ZeroOutBytes(this.algorithm.Key); //把密钥内存清0 防止被恶意读取
return output;
}
/// <summary>
/// <para>Decrypts bytes with the initialized algorithm and key.</para>
/// </summary>
/// <param name="encryptedText"><para>The text which you wish to decrypt.</para></param>
/// <returns><para>The resulting plaintext.</para></returns>
public byte[] Decrypt(byte[] encryptedText)
{
byte[] output = null;
byte[] data = ExtractIV(encryptedText); //先去除IV数据
this.algorithm.Key = Key;
using (ICryptoTransform transform = this.algorithm.CreateDecryptor()) //创建解密对象
{
output = Transform(transform, data); //用解密对象将数据解密为解密流读出的bytes
}
CryptographyUtility.ZeroOutBytes(this.algorithm.Key);
return output;
}
private static byte[] Transform(ICryptoTransform transform, byte[] buffer)
{
return CryptographyUtility.Transform(transform, buffer);
}
private int IVLength
{
get
{
if (this.algorithm.IV == null)
{
this.algorithm.GenerateIV(); //生成用于该算法的随机初始化向量 (IV)
}
return this.algorithm.IV.Length;
}
}
/// <summary>
/// 返回除去IV之后的数据
/// </summary>
/// <param name="encryptedText"></param>
/// <returns></returns>
private byte[] ExtractIV(byte[] encryptedText)
{
byte[] initVector = new byte[IVLength];
if (encryptedText.Length < IVLength + 1)
{
throw new CryptographicException("ExceptionDecrypting");
}
byte[] data = new byte[encryptedText.Length - IVLength];
Buffer.BlockCopy(encryptedText, 0, initVector, 0, IVLength); //从encryptedText中将 IVLength长度的数据复制到 initVector
Buffer.BlockCopy(encryptedText, IVLength, data, 0, data.Length);
this.algorithm.IV = initVector;
return data;
}
/// <summary>
/// 根据加密类型获取一个SymmetricAlgorithm对象
/// </summary>
/// <param name="algorithmType"></param>
/// <returns></returns>
private static SymmetricAlgorithm GetSymmetricAlgorithm(Type algorithmType)
{
SymmetricAlgorithm symmetricAlgorithm = Activator.CreateInstance(algorithmType) as SymmetricAlgorithm;
return symmetricAlgorithm;
}
//[SymmetricAlgorithm]:表示所有对称算法的实现都必须从中继承的抽象基类。
}
}
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Security.Cryptography;
namespace CryptBlock
{
/// <summary>
/// Represents an encrypted cryptographic key and the DPAPI <see cref="DataProtectionScope"/> used to encrypt it.
/// </summary>
public class ProtectedKey
{
private byte[] protectedKey;
private DataProtectionScope protectionScope;
/// <summary>
/// Constructor method use to create a new <see cref="ProtectedKey"></see> from a plaintext symmetric key. The caller of this method
/// is responsible for clearing the byte array containing the plaintext key after calling this method.
/// </summary>
/// <param name="plaintextKey">Plaintext key to protect. The caller of this method is responsible for
/// clearing the byte array containing the plaintext key after calling this method.</param>
/// <param name="dataProtectionScope"><see cref="DataProtectionScope"></see></param> used to protect this key.
/// <returns>Initialized <see cref="ProtectedKey"></see> containing the plaintext key protected with DPAPI.</returns>
public static ProtectedKey CreateFromPlaintextKey(byte[] plaintextKey, DataProtectionScope dataProtectionScope)
{
//[ProtectedData]:提供保护数据和取消数据保护的方法。无法继承此类。 DPAPI
byte[] encryptedKey = ProtectedData.Protect(plaintextKey, null, dataProtectionScope);
return new ProtectedKey(encryptedKey, dataProtectionScope);
}
/// <summary>
/// Constructor method used to create a new <see cref="ProtectedKey"/> from an already encrypted symmetric key.
/// </summary>
/// <param name="encryptedKey">Encrypted key to protect.</param>
/// <param name="dataProtectionScope"><see cref="DataProtectionScope"></see></param> used to protect this key.
/// <returns>Initialized <see cref="ProtectedKey"></see> containing the plaintext key protected with DPAPI.</returns>
public static ProtectedKey CreateFromEncryptedKey(byte[] encryptedKey, DataProtectionScope dataProtectionScope)
{
return new ProtectedKey(encryptedKey, dataProtectionScope);
}
/// <summary>
/// Gets the encrypted key contained by this object.
/// </summary>
public byte[] EncryptedKey
{
get
{
return (byte[])protectedKey.Clone();
}
}
/// <summary>
/// Gets the decrypted key protected by this object. It is the responsibility of the caller of this method
/// to clear the returned byte array.
/// </summary>
public byte[] DecryptedKey
{
get { return Unprotect(); }
}
/// <summary>
/// Returns the decrypted symmetric key.
/// </summary>
/// <returns>Unencrypted symmetric key. It is the responsibility of the caller of this method to
/// clear the returned byte array.</returns>
public virtual byte[] Unprotect()
{
return ProtectedData.Unprotect(protectedKey, null, protectionScope);
}
private ProtectedKey(byte[] protectedKey, DataProtectionScope protectionScope)
{
this.protectionScope = protectionScope;
this.protectedKey = protectedKey;
}
}
}
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.IO;
namespace CryptBlock
{
/// <summary>
/// <para>Common Cryptography methods.</para>
/// </summary>
public static class CryptographyUtility
{
/// <summary>
/// <para>Determine if two byte arrays are equal.</para>
/// </summary>
/// <param name="byte1">
/// <para>The first byte array to compare.</para>
/// </param>
/// <param name="byte2">
/// <para>The byte array to compare to the first.</para>
/// </param>
/// <returns>
/// <para><see langword="true"/> if the two byte arrays are equal; otherwise <see langword="false"/>.</para>
/// </returns>
public static bool CompareBytes(byte[] byte1, byte[] byte2)
{
if (byte1 == null || byte2 == null)
{
return false;
}
if (byte1.Length != byte2.Length)
{
return false;
}
bool result = true;
for (int i = 0; i < byte1.Length; i++)
{
if (byte1[i] != byte2[i])
{
result = false;
break;
}
}
return result;
}
/// <summary>
/// <para>Returns a byte array from a string representing a hexidecimal number.</para>
/// </summary>
/// <param name="hexidecimalNumber">
/// <para>The string containing a valid hexidecimal number.</para>
/// </param>
/// <returns><para>The byte array representing the hexidecimal.</para></returns>
public static byte[] GetBytesFromHexString(string hexidecimalNumber)
{
if (hexidecimalNumber == null) throw new ArgumentNullException("hexidecimalNumber");
StringBuilder sb = new StringBuilder(hexidecimalNumber.ToUpperInvariant());
if (sb[0].Equals('0') && sb[1].Equals('X'))
{
sb.Remove(0, 2);
}
if (sb.Length % 2 != 0)
{
throw new ArgumentException("InvalidHexString");
}
byte[] hexBytes = new byte[sb.Length / 2];
try
{
for (int i = 0; i < hexBytes.Length; i++)
{
int stringIndex = i * 2;
hexBytes[i] = Convert.ToByte(sb.ToString(stringIndex, 2), 16);
}
}
catch (FormatException ex)
{
throw ex;
}
return hexBytes;
}
/// <summary>
/// <para>Returns a string from a byte array represented as a hexidecimal number (eg: 0F351A).</para>
/// </summary>
/// <param name="bytes">
/// <para>The byte array to convert to forat as a hexidecimal number.</para>
/// </param>
/// <returns>
/// <para>The formatted representation of the bytes as a hexidcimal number.</para>
/// </returns>
public static string GetHexStringFromBytes(byte[] bytes)
{
if (bytes == null) throw new ArgumentNullException("bytes Null");
if (bytes.Length == 0) throw new ArgumentException("bytes.Length=0");
StringBuilder sb = new StringBuilder(bytes.Length * 2);
for (int i = 0; i < bytes.Length; i++)
{
sb.Append(bytes[i].ToString("X2", CultureInfo.InvariantCulture));
}
return sb.ToString();
}
/// <summary>
/// <para>Combines two byte arrays into one.</para>
/// </summary>
/// <param name="buffer1"><para>The prefixed bytes.</para></param>
/// <param name="buffer2"><para>The suffixed bytes.</para></param>
/// <returns><para>The combined byte arrays.</para></returns>
public static byte[] CombineBytes(byte[] buffer1, byte[] buffer2)
{
if (buffer1 == null) throw new ArgumentNullException("buffer1");
if (buffer2 == null) throw new ArgumentNullException("buffer2");
byte[] combinedBytes = new byte[buffer1.Length + buffer2.Length];
Buffer.BlockCopy(buffer1, 0, combinedBytes, 0, buffer1.Length);
Buffer.BlockCopy(buffer2, 0, combinedBytes, buffer1.Length, buffer2.Length);
return combinedBytes;
}
/// <summary>
/// Creates a cryptographically strong random set of bytes.
/// </summary>
/// <param name="size">The size of the byte array to generate.</param>
/// <returns>The computed bytes.</returns>
public static byte[] GetRandomBytes(int size)
{
byte[] randomBytes = new byte[size];
GetRandomBytes(randomBytes);
return randomBytes;
}
/// <summary>
/// <para>Fills a byte array with a cryptographically strong random set of bytes.</para>
/// </summary>
/// <param name="bytes"><para>The byte array to fill.</para></param>
public static void GetRandomBytes(byte[] bytes)
{
RNGCryptoServiceProvider.Create().GetBytes(bytes);
}
/// <summary>
/// <para>Fills <paramref name="bytes"/> zeros.</para>
/// </summary>
/// <param name="bytes">
/// <para>The byte array to fill.</para>
/// </param>
public static void ZeroOutBytes(byte[] bytes)
{
if (bytes == null)
{
return;
}
Array.Clear(bytes, 0, bytes.Length);
}
/// <summary>
/// Transforms an array of bytes according to the given cryptographic transform.
/// </summary>
/// <param name="transform"><see cref="ICryptoTransform" /> used to transform the given <paramref name="buffer" />.</param>
/// <param name="buffer">Buffer to transform. It is the responsibility of the caller to clear this array when finished.</param>
/// <returns>Transformed array of bytes. It is the responsibility of the caller to clear this byte array
/// if necessary.</returns>
public static byte[] Transform(ICryptoTransform transform, byte[] buffer)
{
if (buffer == null) throw new ArgumentNullException("buffer null");
byte[] transformBuffer = null;
using (MemoryStream ms = new MemoryStream())
{
CryptoStream cs = null; //定义将数据流链接到加密转换的流
try
{
cs = new CryptoStream(ms, transform, CryptoStreamMode.Write);
cs.Write(buffer, 0, buffer.Length);
cs.FlushFinalBlock();
transformBuffer = ms.ToArray();
}
finally
{
if (cs != null)
{
cs.Close();
((IDisposable)cs).Dispose();
} // Close is not called by Dispose
}
}
return transformBuffer;
}
}
}
四,实例演示
可能微软的企业库封装的东西太多,代码也比较多,一下看起来就比较没耐心了,所以可能很多同学还不能真正了解怎么用,以及怎么一个原理!
希望通过下面实例演示,用我的理解来为没有耐心的同学解惑,高手们就不要拍我了!
看代码,写了自己认为比较详细的注释:
static void Main(string[] args)
{
ProtectedKey key;
Rijndael rj = Rijndael.Create();//创建Rijndael 加密算法对象
if(File.Exists("key2.dat"))
{
//解密由DPAPI加密过的Key数据, DPAPI模式为机器模式,只有对此Key进行加密的计算机才可以使用
using (FileStream fs = new FileStream("key2.dat", FileMode.Open, FileAccess.Read))
{
byte[] keybytes = new byte[fs.Length];
fs.Read(keybytes, 0, (int)fs.Length);
key = ProtectedKey.CreateFromEncryptedKey(keybytes, DataProtectionScope.LocalMachine);
try{
Console.WriteLine("Decode Key2:{0}", Encoding.Default.GetString(key.DecryptedKey));
}
catch(Exception e)
{
//捕捉由不是创建加密key的机器来解密key时产生的异常
Console.WriteLine("Error DPAPI.DecryptedKey:{0}", e.Message);
}
}
}
else
{
rj.GenerateKey();//生成随机的rj算法的key
key = ProtectedKey.CreateFromPlaintextKey(rj.Key, DataProtectionScope.LocalMachine);
using (FileStream fs = new FileStream("key.dat", FileMode.CreateNew, FileAccess.Write))
{
fs.Write(rj.Key, 0, rj.Key.Length); //把key保存出来在key.dat文件
}
Console.WriteLine("Key:{0}", Encoding.Default.GetString(rj.Key));
}
SymmetricCryptographer crypt = new SymmetricCryptographer(rj.GetType(), key); //实例化一个对称加密的对象
string str = "加密演示!";
byte[] encode=crypt.Encrypt(Encoding.Default.GetBytes(str));
//加密解密的key来自 DPAPI.DecryptedKey 即crypt对象的Key属性
Console.WriteLine("Encode:{0}",Encoding.Default.GetString(encode) );
Console.WriteLine("Decode:{0}", Encoding.Default.GetString(crypt.Decrypt(encode)));
Console.WriteLine("press ESC to continue...");
while (Console.ReadKey().Key!=ConsoleKey.Escape)
{
//...
}
}
需要说明一下的是,为了演示,所以SymmetricCryptographer类中的 Key属性在上面代码了改成了Public级别的,实际使用中还是需要隐藏此key!
个人理解及能力都有限,自己认为需要注意的地方都添加了注释,如有错误之处还望大家指正!
另外这么封装是为了我要在软件中添加远程认证时对传递的数据进行保护,具体怎么使用还要看自己的设计!