.NET安全系列之四:.NET中加密算法及证书

对称算法

    首先阐述一下对称算法的原理,假设有A与B两个人,现需要使用对称加密算法来保证交换信息的机密性。对称算法以一个密钥对系统作为基础。在两人加密消息M之前,他们选择了一种对称算法并构造了与对密钥(S,P)。假设,P(M)表示使用密钥P加密的消息,而S(M)表示使用密钥S加密的消息。对称算法具有如下属性:

  • S(P(M)) = P(S(M)) = M
  • 没有密钥S无法从P(M)中获得M。
  • 没有密钥P无法从S(M)中获得M。

由上面属性可以看出,密钥S与P扮演了一个对称的角色,因此我们将其命名为对称算法。当A向B发送消息M时,为了保证消息的机密,需要在生成的密钥对中选择其一来加密信息M。当B收到A发来的加过密的信息后,使用另一个密钥来解密信息。在这个过程中,两个需要实现在使用的密钥对上达成一致。如果第三方截获了信息,由于他没有密钥因而无法解密信息。

下图展示了以上描述的这个过程:

为了保证信息的安全性,密钥是需要保密的。

对称算法存在以下两个缺陷:

  • 希望交换消息的双方必须都知道同一个密钥对。而在某些情况下,这个密钥对需要通过存在安全隐患的传输信道发送到对方。
  • 一个密钥对仅仅对交换消息的双方有效。如上中,如果A希望与B与C交换经过加密的消息,他必须同时维护两个密钥对,一个用来与B交换信息的,另一个用来与C交换信息。对密钥对的管理将是一个潜在的问题。

这些问题可以使用非对称密钥很好的解决。

 

.NET中的对称算法

.NET Framework提供了一些常见的对称算法的实现,包括DES、RC2等。下面列出了与对称算法有关的类的结构:

System.Security.Cryptography.SymmetricAlgorithm

    System.Security.Cryptography.DES

        System.Security.Cryptography.DESCryptoServiceProvider

System.Security.Cryptography.RC2

    System.Security.Cryptography.RC2CryptoServiceProvider

System.Security.Cryptography.Rijndael

    System.Security.Cryptography.RijndaelManaged

System.Security.Cryptography.TripleDES

    System.Security.Cryptography.TripleDESCryptoServiceProvider

这些类中黑体表示的类为实现算法的具体类,其它的都是抽象类;也可以根据需要实现自己的对称算法。

 

  1. DES(Digital Encryption Standard, 数字机密标准)

    DES算法是最常用的对称算法了,下面的示例就演示了如何使用DESCryptoServiceProvider类加密一个字符串。除了一个密钥,我们还需要为该算法提供一个初始向量或称IV。该初始向量可以视为一个随机数,它将用于算法的初始化。

插一句,CryptoServiceProvider表示此加密组件不是托管的实现。

首先我们列一下加密与解密过程各自需要的步骤,然后给出示例代码:

解密步骤:

  1. 将源字符串转换为一个字节数组。
  2. 初始化一个加密算法类(此处即DESCryptoServiceProvider,得到des对象,见代码)。
  3. 使用加密算法类对象(des这个)来生成一个加密者对象(下文代码中的encryptor对象)。这里需要密钥与IV值
  4. 使用加密者(encryptor)对象来初始化一个密文数据流(CryptoStream类的encryptionStream对象)
  5. 通过向步骤4中得到的加密流写入要加密的数据进行加密,加密后的数据放在CryptoStream创建所使用的MemoryStream中。
  6. 由CryptoStream对象中取出加密过的byte[]即可。
  7. 如需其它使用可以将byte[]转换为string类型。

以上描述针对代码中方法一,也可直接使用方法二,后者比较简单不再赘述。

解密过程类似但相反:

  1. 将密文字符串转成byte[],并放入一个MemoryStream中。
  2. 初始化一个加密算法类对象
  3. 使用加密算法类生成一个实现ICryptoTransform接口的解密者对象,这一步需要提供密钥与IV值。
  4. 使用解密者对象初始化一个CryptoStream对象 - decryptionStream。
  5. 由上一步得到的流中读取的数据即解密后的数据。

同样这里的步骤描述的是代码中方法1所示,更为方便的方法2不赘述。

static void Main()
{
    string sMsg = "The message to encrypt!";
    string sEnc, sDec;
    DESCryptoServiceProvider des = new DESCryptoServiceProvider();
    Encoding utf = new UTF8Encoding();
    byte[] key = utf.GetBytes("12345678");
    byte[] iv = { 1, 2, 3, 4, 5, 6, 7, 8 };
    ICryptoTransform encryptor = des.CreateEncryptor(key, iv);
    ICryptoTransform decryptor = des.CreateDecryptor(key, iv);
    //加密
    {
        byte[] bMsg = utf.GetBytes(sMsg);
        byte[] bEnc;
        //方法1
        MemoryStream tempStream = new MemoryStream();
        CryptoStream encryptionStream = new CryptoStream(tempStream, encryptor, CryptoStreamMode.Write);
        encryptionStream.Write(bMsg, 0, bMsg.Length);//加密
        encryptionStream.FlushFinalBlock();
        bEnc = tempStream.GetBuffer();
        //方法2
        bEnc = encryptor.TransformFinalBlock(bMsg, 0, bMsg.Length);
        sEnc = Convert.ToBase64String(bEnc);
    }
    //解密
    {
        byte[] bEnc = Convert.FromBase64String(sEnc);
        //方法1
        MemoryStream tempStream = new MemoryStream(bEnc,0,bEnc.Length);
        CryptoStream decryptionStream = new CryptoStream(tempStream,decryptor,CryptoStreamMode.Read);//解密了
        StreamReader sw = new StreamReader(decryptionStream);
        sDec = sw.ReadToEnd();
        //方法2
        byte[] bDec = decryptor.TransformFinalBlock(bEnc, 0, bEnc.Length);
        sDec = utf.GetString(bDec);
    }
    Console.WriteLine("Message : " + sMsg);
    Console.WriteLine("Encrypted: " + sEnc);
    Console.WriteLine("Decrypted: " + sDec);
} 

程序输出结果如下:

这个例子中,encryptor与decryptor对象都实现了ICryptoTransform接口。这也正是该算法具有对称型的一个体现。

也可以使用DESCryptoServiceProvider类来构建一个密钥与初始向量。即如下写法:

​…… 
des.GenerateKey(); 
des.GenerateIV(); 
ICryptoTransform encryptor = des.CreateEncryptor(); 
ICryptoTransform decryptor = des.CreateDecryptor(); 
…… 

 

 

非对称算法

前文提到了对称算法的存在的问题,非对称算法何以很好的解决。一个非对称算法拥有以下5个属性,其中前三种属性与对称算法相同,后两种属性是其特有的:

  • S(P(M)) = P(S(M)) = M
  • 没有密钥S无法从P(M)中获得M。
  • 没有密钥P无法从S(M)中获得M。
  • 当我们知道密钥P时,能够方便的计算出密钥S。
  • 当我们知道密钥S时,很难就算出密钥P。

这样A与B使用该类型的算法就可以不必交换密钥同时不必管理大量的密钥。他们只需要分别计算出名为(Sa,Pa)和(Sb,Pb)的密钥对,然后A使用密钥Pa作为私钥,B使用Pb作为私钥即可。任何人想要发送一个消息M给A,并希望保证该消息的机密性,他只需使用A的公钥Sa加密该消息即可,由于只有A拥有自己的私钥Pa,所以他是唯一一个可以通过计算Pa(Sa(M))解密别人发送给他的Sa(M)消息的人。

上述过程的图示如下:

我们将S称为公钥或共享密钥,而称P为私钥。所以我们也称此种算法为公钥/私钥算法。

使用非对称密钥可以通过以下技巧避免冒充身份发送信息的问题:由于任何人都知道A的公钥Sa,所以任何人都可以向其发送信息,假设C要冒充B给A发送信息,B怎样避免被冒充,而A又怎样识别发信人。

    解决:B可以向A发送一个记过加密的消息Sa(Pb(M)),由于A持有密钥Pa与Sb,根据Sb(Pa(Sa(Pb(M)))) = Sb(Pb(M)) = M,这样A可以将Sb(Pj(M))消息解密而获得原始消息M。即,B是唯一知道密钥Pb的人,因此A可以确定B是消息的发送者。这也说明了非对称算法除加密数据外另一个重要的用途 - 对数据做数字签名。对数据做数字签名意味着使用该数据的一方可以100%确定数据的生产者就是密钥的拥有者。我们把数据的使用方确认数据的生产者就是密钥的拥有者的过程成为数据认证。

非对称算法算法的一个问题在于,其计算代价是对称算法的1000倍,所以实际应用中,非对称算法用于交换对称算法的一个密钥对,然后使用对称算法的密钥用对称算法加密进行数据交换。此时对称算法使用的密钥仅在一次消息交换的会话中有效。

如果对文件的安全性要求不是很严格,只是控制文件在传输中的完整性,可以用散列算法算出一个文件的散列,然后用非对称算法加密并把加密后的信息附加于文件中,接收方使用收到的文件中的散列验证文件的完整性,同时也能确认文件的发送者,即实现了简单的文件签名的功能。

 

RSA算法

RSA是当今最常用的非对称算法,其被应用于很多领域。RSA算法的理论依据来自于一个大素数所具有的特性:对于给定的两个大素数A与B,很容易计算出它们的乘积;但是,仅知道AB的成绩却很难计算原来的A与B各自的值。

.NET与RSA算法

.NET Frameork提供了两个类供我们使用RSA算法:用于加密数据的RSACryptoServiceProvider类以及用于对数据做数字签名的DSACryptoServiceProvider类(DSA: Digital Signature Algorithm,数字签名算法),其类的层次结构如下:

System.Security.Cryptography.AsymmetricAlgorithm

    System.Security.Cryptography.DSA

        System.Security.Cryptography.DSACryptoServiceProvider

    System.Security.Cryptography.RSA

        System.Security.Cryptography.RSACryptoServiceProvider

 

下面示例演示了如何使用RSACryptoServiceProvider类加密一个字符串。其中,ExpertParameter(bool)方法根据参数的真假允许获得公钥/私钥对(true)或只有公钥(false)。

using System; 
using System.Text; 
using System.Security.Cryptography; 
class Program { 
    static void Main() { 
        string sMsg = "The message to encrypt!"; 
        string sEnc, sDec; 
        Encoding utf = new UTF8Encoding(); 
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); 
        RSAParameters publicKey = rsa.ExportParameters( false ); 
        RSAParameters publicAndPrivateKey = rsa.ExportParameters( true ); 
        { 
            RSACryptoServiceProvider rsaEncryptor = new 
            RSACryptoServiceProvider(); 
            rsaEncryptor.ImportParameters( publicKey ); 
            byte[] bMsg = utf.GetBytes(sMsg); 
            byte[] bEnc = rsaEncryptor.Encrypt( bMsg, false ); 
            sEnc = Convert.ToBase64String( bEnc ); 
        } 
        { 
            RSACryptoServiceProvider rsaDecryptor = new 
            RSACryptoServiceProvider(); 
            rsaDecryptor.ImportParameters( publicAndPrivateKey ); 
            byte[] bEnc = Convert.FromBase64String(sEnc); 
            byte[] bDec = rsaDecryptor.Decrypt( bEnc, false ); 
            sDec = utf.GetString( bDec ); 
        } 
        Console.WriteLine("Message : " + sMsg); 
        Console.WriteLine("Encrypted: " + sEnc); 
        Console.WriteLine("Decrypted: " + sDec); 
    } 
} 

散列算法

散列算法是把长长的一列数据变成较短的代码,这是最流行的64位散列密钥。两个最流行的散列算法是SHA(Secured Hash Algorithm)和MD5(Message Digest version5)。这些散列密钥用于标记数字文档。

散列值是根据一个数据集合计算出来的数字。如果数据集合不同,那么计算出来的数字基本上也是不同的。.NET Framework在其System.Security.Cryptography命名空间下提供了一些主要的散列算法,包括:SHAx、RIPEMD160、与MD5。

下面在举一个例子描述一下对文件数字签名的过程,虽然前文已经有了大致的介绍。假设A希望给B发送文件FileA并证明他就是文件的作者,首先A计算出文件的散列值Hfa,然后利用自己的私钥计算出Pa(Hfa),最后将Pa(Hfa)及他的公钥Sa集成到FileA文件中(如放在文件开始处)。B事先已经知道A的公钥Sa,当B得到文件FileA后,从其中抽取Pa(Hfa)与Sa。然后依次进行以下步骤进行验证。

  1. 首先验证抽取的公钥Sa是否为其已知的A的公钥。
  2. 直接计算文件FileA的散列(去除Pa(Hfa)与Sa后的文件)
  3. 根据Sb(Pb(x)) = x,利用Pb(x)与公钥Sb还原出x。

如果2、3步中两个x值相等,就可以确定FileA的作者就是私钥Pa的拥有者(当A可以证明其是Pa的唯一拥有者时,A就是FileA的作者)。Pa(x)就成为FileA的数字签名。

下面的示例程序给出了使用HMACSHA1进行散列处理的一个例子,结果为二进制散列结果值的Base64编码,如下:

using System; 
using System.IO; 
using System.Security.Cryptography; 
using System.Text; 
using System.Runtime.Serialization.Formatters; 
 
class Program 
{ 
    static void Main() 
    { 
        Console.WriteLine("输入要散列处理的文件名"); 
        string fp = Console.ReadLine(); 
         
        Console.WriteLine(Hash(fp)); 
    } 
     
    private static string Hash(string filepath) 
    { 
        //定义一个混淆偷窥者的随机秘密 
        Byte[] key = Encoding.ASCII.GetBytes("Secret Key 01231631".ToCharArray()); 
        //使用随机秘密创建.NET类库的散列算法的对象 
        HMACSHA1 hmac = new HMACSHA1(key); 
        //打开要处理的文件流 
        FileStream fs = File.OpenRead(filepath); 
         
        //计算散列 
        Byte[] hash = hmac.ComputeHash(fs); 
        string b64 = Convert.ToBase64String(hash); 
        fs.Close(); 
         
        return b64; 
    } 
} 

SHA

SHA(安全散列算法)是一个块密码,在64位的数据块上执行,这个算法的改进版本采用了更大的密钥值,当然,密钥值越大所需的计算时间越长。而且,对于相对较小的文件,散列值越小就越安全,就是说,散列算法的块大小应小于或等于数据块本身的大小。

SHA1算法的散列范围是160位,其用法如下代码所示,只列出了与HMACSHA1不同之处:

SHA1 sha = new SHA1CryptoServiceProvider(); 
…… 
Byte[] hash = sha.ComputeHash(fs); 

​.NET Framework也提供了更大的密钥值算法,分别为SHA256、SHA384和SHA512。名称最后的数字表示其块大小。

ASP.NET安全性中的成员资格提供程序使用SHA1加密用户密码。

 

MD5

MD5表示Message Digest版本5。它是一个加密的单向散列算法。MD5有抗伪造,计算成本低且执行简单等特点。现在MD5已成为散列算法的事实标准。

.NET Framework提供了MD5CryptoServiceProvider这个类实现MD5算法。该类与SHA1共享同一个基类,前面的示例也只需要做简单的改动即可成为使用MD5进行处理的例子。

MD5 md5 = new MD5CryptoServiceProvider(); 
…… 
Byte[] hash = md5.ComputeHash(fs); 

RIPEMD-160

基于MD5的RIPEMD-160最早出现于欧洲,是一个使用160位的散列算法。.NET也引入了对这种算法的支持。见如下代码段:

RIPEMD160Managed myRIPEMD = new RIPEMD160Managed(); 
…… 
byte[] hash = myRIPEMD.ComputeHash(fs); 

证书

一份证书包含了一个公钥以及与证书发布者相关的信息,其用于确保其中的公钥是由证书中提到的发布者产生的。证书是一个包含了私有密钥拥有者生成的数字签名的语句。私有密钥拥有者首先获取验证机构(即证书认证中心CA)的信任,然后验证者负责保证其公共密钥的有效性。这种技术可以在两个未知实体之间创建一个信任关系。证书还包括证书有效期等信息。

Internet上有很多提供验证服务的机构(CA),比较流行的有VeriSign。在Intranet中可以使用Windows Server中的证书服务运行自己的Certificate Authority(CA)服务。

X.509

X.509是一种较常用的证书标准,如Windows的Authenticode技术与SSL技术都是使用的X.509证书标准。

.NET Framework SDK中提供了makecert这个生成证书的工具用于制作测试证书。

如下命令:mskecert -n CN=Test test.cer会产生一个名为test.cer的证书。双击此证书可以看到如下所示:

在.NET Framework中System.Security.Cryptography.X509Certificates下提供了三个处理X509证书的类。

下面的示例展示了使用这些类处理刚创建的证书的方法。

using System; 
using System.Security.Cryptography.X509Certificates; 
 
class Program 
{ 
    static void Main() 
    { 
        X509Certificate cer = X509Certificate.CreateFromCertFile(@"T:\test.cer"); 
        Console.WriteLine("hash = {0}", cer.GetCertHashString()); 
        Console.WriteLine("effective Date = {0}", cer.GetEffectiveDateString()); 
        Console.WriteLine("expire Date = {0}", cer.GetExpirationDateString()); 
        Console.WriteLine("Issued By = {0}", cer.Issuer); 
        Console.WriteLine("Issued To = {0}", cer.Subject); 
        Console.WriteLine("algo = {0}", cer.GetKeyAlgorithm()); 
        Console.WriteLine("Pub Key = {0}", cer.GetPublicKeyString()); 
    } 
} 

根证书

Windows系统维护了一个证书的列表(称为证书存储区,Certificate Store),包含在该列表中的证书被称为根证书,它是由Windows系统默认提供的。例如:当一个软件向某个CA请求发布它的证书时,CA会使用自己的证书为将要发布的证书签名。如果CA的证书是一个根证书Windows就不再需要通过网络访问CA以验证证书的有效性,因为本机上已存有这类CA证书。通过各CA的层级验证可以形成一个证书链,从而最大程度避免机器在安装期间与CA联系。

可以使用certmgr.exe查看并管理证书。

 

最后总结一下证书管理工具

.NET Framework SDK提供的证书管理工具

程序名称

功能

Makecert.exe

创建一个X.509证书,以进行测试

Certmgr.exe

给程序集授予CTL(Certificate Trust List),也可以用于撤销

Chktrust.exe

验证包含数据的标记文件、其PKCS#7散列和X.509证书

Cert2spc.exe

从X.509证书中创建一个SPC(Software Publisher Certificate)

posted @ 2009-01-23 15:00  hystar  阅读(3581)  评论(2编辑  收藏  举报