封装自己的对称加密模块

说明

通过对 Enterprise Library 5.0的学习,提取并修改一个适合自己使用的加密解密库;

需要封装的库能支持大部分对称加密解密算法,使用DPAPI保证key的安全性,并且简单易懂方便使用; 


本文技术含量不高,基本只是抽取 Enterprise Library 5.0的几个类,对代码稍作修改,并且给出使用演示示例!

一,对称加密解密算法的了解,System.Security.Cryptography;


查看.net framework 可知很多对称加密解密算法都被封装到了Cryptography空间下,

包括有:AesDES RC2RSARijndaelTripleDESSHA等等等。。。。

SymmetricAlgorithm;

表示所有对称算法的实现都必须从中继承的抽象基类。 

如:SymmetricAlgorithm symmetricAlgorithm = Activator.CreateInstance(algorithmType) as SymmetricAlgorithm;

二,DPAPI的了解,ProtectedData

DPAPI

您必须了解的背景知识

在开始学习本章之前,您应该知道:

Windows 2000 操作系统和更高版本的操作系统提供了用于加密和解密数据的 Win32® 数据保护 API (DPAPI)。

DPAPI 是加密 API (Crypto API) 的一部分并且是在 crypt32.dll 中实现的。它包含两个方法:CryptProtectData 和 CryptUnprotectData

DPAPI 特别有用,因为它能够消除使用密码的应用程序所带来的密钥管理问题。虽然加密能确保数据安全,但您必须采取额外的步骤来确保密钥的安全。DPAPI 使用与 DPAPI 函数的调用代码关联的用户帐户的密码,以便派生加密密钥。因此,是操作系统(而非应用程序)管理着密钥。

DPAPI 能够与计算机存储或用户存储(需要一个已加载的用户配置文件)配合使用。DPAPI 默认情况下用于用户存储,但您可以通过将 CRYPTPROTECT_LOCAL_MACHINE 标志传递给 DPAPI 函数来指定使用计算机存储。

这种用户配置文件方式提供了一个额外的安全层,因为它限制了哪些用户能访问机密内容。只有加密该数据的用户才能解密该数据。但是,当通过 ASP.NET Web 应用程序使用 DPAPI 时,使用用户配置文件需要您执行额外的开发工作,因为您需要采取明确的步骤来加载和卸载用户配置文件(ASP.NET 不会自动加载用户配置文件)。

计算机存储方式更容易开发,因为它不需要管理用户配置文件。但是,除非使用一个附加的熵参数,否则并不安全,因为该计算机的任何用户都可以解密数据。(熵是一个设计用来使解密机密内容更为困难的随机值)。使用附加的熵参数出现的问题在于它必须由应用程序安全地存储起来,这带来了另一个密钥管理问题。

注意:如果您将 DPAPI 和计算机存储一起使用,那么加密字符串仅适用于给定的计算机,因此您必须在每台计算机上生成加密数据。不要在场或群集中将加密数据从一台计算机复制到另一台计算机。

如果将 DPAPI 和用户存储一起使用,则可以用一个漫游的用户配置文件在任何一台计算机上解密数据。

 

ProtectedData类实现了DPAPI,提供保护数据和取消数据保护的方法。无法继承此类。 

三,封装我们的加密解密库

直接从微软企业库提取出以下3个类:

CryptographyUtility,此类包含一些通用的处理方法,如byte[]比较,byte[]与十六进制的转换,数据流到加密解密流的转换等

SymmetricCryptographer,此类就是实现我们对称加密解密的服务类,通过SymmetricAlgorithm基类获取一个对称加密解密算法的实例对象,进行加密解密

ProtectedKey,此封装了使用DPAPI对对称算法的key进行保护


下面帖上完整代码+注释,如有不对的地方欢迎批评指正!

SymmetricCryptographer
using System;
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 == nullthrow new ArgumentNullException("algorithmType Null");
            
if (!typeof(SymmetricAlgorithm).IsAssignableFrom(algorithmType)) throw new ArgumentException("algorithmType IsAssignable Error");
            
if (key == nullthrow 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]:表示所有对称算法的实现都必须从中继承的抽象基类。  
    }
}
ProtectedKey
using System;
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;
        }
    }
}
CryptographyUtility
using System;
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 == nullthrow new ArgumentNullException("hexidecimalNumber");

            StringBuilder sb 
= new StringBuilder(hexidecimalNumber.ToUpperInvariant());

            
if (sb[0].Equals('0'&& sb[1].Equals('X'))
            {
                sb.Remove(
02);
            }

            
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 == nullthrow new ArgumentNullException("bytes Null");
            
if (bytes.Length == 0throw 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 == nullthrow new ArgumentNullException("buffer1");
            
if (buffer2 == nullthrow 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 == nullthrow 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;
        }


    }
}

四,实例演示 

可能微软的企业库封装的东西太多,代码也比较多,一下看起来就比较没耐心了,所以可能很多同学还不能真正了解怎么用,以及怎么一个原理!

希望通过下面实例演示,用我的理解来为没有耐心的同学解惑,高手们就不要拍我了!

看代码,写了自己认为比较详细的注释:

Tester
 [STAThread]
        
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!

个人理解及能力都有限,自己认为需要注意的地方都添加了注释,如有错误之处还望大家指正!

另外这么封装是为了我要在软件中添加远程认证时对传递的数据进行保护,具体怎么使用还要看自己的设计! 

原文地址:https://www.cnblogs.com/cxwx/p/1770354.html