Microsoft .NET 中的简化加密

Paul D. Sheriff
PDSA.com

2003 年 10 月

适用于:
    Microsoft® .NET
    安全
    Microsoft® Visual Basic® .NET
    C#

摘要:学习如何利用 .NET Framework 的加密功能创建类似本文所述的包装程序来保护您的数据。

下载与本文相关的 CryptoSampleCSSample.msiCryptoSampleVBSample.msi 代码示例。(请注意,在示例文件中,程序员的注释使用的是英文,本文中将其译为中文是为了便于读者理解。)

目录

散列简介
创建示例散列项目
在散列中添加“盐”值
小结

您希望在计算机上保存一些机密信息吗?如果是,本文就为您介绍如何进行加密!加密技术就是将有意义的字符编码成无意义的字符,使不应该访问这些数据的人员无法读取它们。加密技术已经存在很多年了,甚至远在计算机诞生之前就已经存在了。随着计算机的出现,在计算机领域应用加密技术可以生成几乎牢不可破的代码。Microsoft 在 Windows 95 中开发并分发了加密 API。使用 Microsoft .NET,新创建的类可以将这些复杂的算法打包到非常易于使用的属性和方法中。

散列简介

如果您只是不想让别人窃取您的密码,那么可以为密码数据创建一个散列。散列是一种单向算法,一旦数据被转换,将无法再获得其原始值。大多数开发人员使用数据库存储密码。但是,在数据库中查找用户数据的人员也能够看到这些密码。不过,您可以使用散列算法对密码进行加密,然后再将其存储在数据库中。用户输入密码后,您可以再次使用散列算法对其进行解密,然后将其与存储在数据库中的散列进行比较。散列的缺点之一是,即使原始数据只发生一个小小的改动,数据的散列也会发生非常大的变化。Pork 和 Porky 这两个单词非常相似,但使用散列算法加密后的结果却相去甚远。您可能根本看不出二者之间有什么相似之处。

.NET 开发人员可以使用多种散列算法类。最常用的是 SHA1 和 MD5。下面我们看一下如何为“Paul”这样的普通字符串生成散列,使任何人都无法识别它。

使用 SHA1 生成散列

我们创建一个新例程,然后使用它为字符串“Paul”生成散列。在 Visual Studio® .NET 中打开一个新的 Windows 应用程序,在窗体上放置一个命令按钮。当该命令按钮上发生 Click 事件时,将调用名为 HashText() 的方法。您可以将以下代码添加到该窗体中,看一下此散列算法的实际效果。编写下列代码之前,需要导入命名空间 System.Security.Cryptography

Private Sub HashText(ByVal TextToHash As String)
  Dim SHA1 As SHA1CryptoServiceProvider
  Dim bytValue() As Byte
  Dim bytHash() As Byte

  ' 创建新的加密服务提供程序对象
  SHA1 = New SHA1CryptoServiceProvider

  ' 将原始字符串转换成字节数组
  bytValue = _
   System.Text.Encoding.UTF8.GetBytes(TextToHash)

  ' 计算散列,并返回一个字节数组
  bytHash = SHA1.ComputeHash(bytValue)

  SHA1.Clear()

  ' 返回散列值的 Base64 编码字符串
  Debug.WriteLine(Convert.ToBase64String(bytHash))
End Sub

您可以传递不同的字符串值来调用该例程,查看散列值的变化。例如,如果将字符串“Paul”传递给该例程,Debug(调试)窗口将显示以下文本:

w2h6uYgMJt/nq5ZqihcBteAXwv8=

现在,将此过程中的输入值更改为“Pauly”。您将看到以下输出结果:

proywxJ0znMpGF5sbB18+7GSAsM=

如您所见,输入字符串的一个小小变化就会产生完全不同的字符组合。这正是散列算法之所以有效的原因,它使我们很难找到输入字符串的规律,也很难根据加密后的字符弄清楚字符串原来的模样。

使用 MD5 也可以生成散列

了解一种散列类的使用方法后,基本上就了解了所有的散列类。下面的方法用于 MD5 散列算法。注意,除了 CryptoServiceProvider 类不同外,代码是完全相同的。

Private Sub HashTextMD5(ByVal TextToHash As String)
  Dim md5 As MD5CryptoServiceProvider
  Dim bytValue() As Byte
  Dim bytHash() As Byte

  ' 创建新的加密服务提供程序对象
  md5 = New MD5CryptoServiceProvider

  ' 将原始字符串转换成字节数组
  bytValue = System.Text.Encoding. _
   UTF8.GetBytes(TextToHash)

  ' 计算散列,并返回一个字节数组
  bytHash = md5.ComputeHash(bytValue)

  md5.Clear()

  ' 返回散列值的 Base64 编码字符串
  Debug.WriteLine(Convert.ToBase64String(bytHash))
End Sub

输入“Paul”之后,MD5 散列算法的输出结果如下所示:

nVWBsHh1MKNctPioSyqyTQ==

同样,加密后的字符串看起来也与原始输入相去甚远。这些散列算法对于创建没有任何意义的密码来说非常有用,也使黑客很难猜出这些密码。之所以使用散列算法,是因为可以用这种算法对密码进行加密并将其存储在数据库中。然后,当用户输入真实密码时,您先对密码进行解密,然后通过网络发送到数据库中,比较它与数据库中的密码是否匹配。请记住,散列是单向操作。使用散列算法对原始密码加密后将无法再恢复。

如何选择算法

本文介绍的两种散列算法都执行同一种操作。不同之处只在于生成散列的密钥大小以及使用的算法。使用的密钥越大,加密就越安全。例如,MD5 使用的加密密钥比 SHA1 使用的密钥大,因此 MD5 散列较难破解。

对于散列算法要考虑的另外一点是,从实践或理论的角度上看是否存在冲突的可能性。冲突是我们所不希望的,因为两个不同的单词可能会生成相同的散列。例如,SHA1 从实践或理论上来讲没有发生冲突的可能性。MD5 从理论上讲有发生冲突的可能性,但从实践上讲没有发生冲突的可能性。因此,选择哪种算法归根结底取决于您所需要的安全级别。

创建示例散列项目

本文包含两个示例散列项目,以更普通的方式说明如何使用不同的散列算法加密任意字符串。这两个示例项目的名称分别为 CryptoSampleVB.sln 和 CryptoSampleCS.sln。前者是 Visual Basic .NET 解决方案,后者是 C# 解决方案。两个解决方案都包括一个类似图 1 的窗体,该窗体允许您输入要通过散列算法为其加密的原始字符串,还提供一个用来选择散列算法的选项按钮和一个显示散列结果的文本框。

图 1:创建一个通用散列屏幕来尝试两种散列算法。

单击此屏幕上的 Hash(散列)按钮时,将运行该按钮的 Click 事件过程。此事件过程将调用一个名为 HashString() 的例程。

' Visual Basic .NET
Private Sub btnHash_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles btnHash.Click
  txtHashed.Text = HashString(txtOriginal.Text)
End Sub
// C#
private void cmdHash_Click(object sender, 
 System.EventArgs e)
{
   txtHashed.Text = HashString(txtOriginal.Text);
}

HashString() 方法接受输入的值并调用 SetHash() 方法。此方法将根据窗体上选项按钮的设置来决定使用哪个加密服务提供程序创建该方法的实例并返回该方法。将为该窗体创建一个名为 mHash 的 HashAlgorithm 类型的成员变量。HashAlgorithm 类型是创建所有散列加密服务提供程序的基类。

' Visual Basic .NET
Private mhash As HashAlgorithm

// C#
private HashAlgorithm mhash;

SetHash() 方法如下所示:

' Visual Basic .NET
Private Function SetHash() As HashAlgorithm
  If optSHA1.Checked Then
    Return New SHA1CryptoServiceProvider
  Else
    If optMD5.Checked Then
      Return New MD5CryptoServiceProvider
    End If
  End If
End Function

// C#
private HashAlgorithm SetHash()
{
   if(this.optSHA1.Checked)
      return new SHA1CryptoServiceProvider();
   else
      return new MD5CryptoServiceProvider();
}

根据您在窗体上选择的选项按钮,此方法将创建并返回一个不同的 HashAlgorithm 类型。HashString() 方法在该窗体上执行实际的数据加密:

' Visual Basic .NET
Private Function HashString(ByVal Value As String) _
 As String
  Dim bytValue() As Byte
  Dim bytHash() As Byte

  ' 创建新的加密服务提供程序对象
  mhash = SetHash()

  ' 将原始字符串转换成字节数组
  bytValue = System.Text.Encoding.UTF8.GetBytes(Value)

  ' 计算散列,并返回一个字节数组
  bytHash = mhash.ComputeHash(bytValue)

  mhash.Clear()

  ' 返回散列值的 Base64 编码字符串
  Return Convert.ToBase64String(bytHash)
End Function


// C#
private string HashString(string Value)
{
   mhash = SetHash();

   // 将原始字符串转换成字节数组
   byte[] bytValue = System.Text.Encoding.UTF8.GetBytes(Value);

   // 计算散列,并返回一个字节数组
   byte[] bytHash = mhash.ComputeHash(bytValue);

   mhash.Clear();

   // 返回散列值的 Base64 编码字符串
   return Convert.ToBase64String(bytHash);
}

HashString 方法中,我们创建了两个字节数组。第一个数组用来保存用户的原始字符串输入。我们使用 System.Text.Encoding.UTF8.GetBytes() 方法将该字符串转换成字节数组。将原始字符串转换成字节数组后,现在使用服务提供程序的 ComputeHash() 方法计算该字符串的散列值。此方法接受字节数组作为输入,然后返回该字符串加密格式的字节数组。

注意:完成之后清除散列变量是一个好的做法。因此,您看到计算该字符串的散列后,我们调用了 Clear 方法。

现在我们已经获得了加密的字节数组,这正是要从该方法返回的数组。因为我们要将原始值和加密值都作为字符串数据类型而不是字节数组进行处理,所以要通过使用 Convert.ToBase64String 方法返回加密的字节。此方法负责将字节数组转换成 Base64 编码的字符串。Base64 编码的使用非常重要,因为有可能需要将此字符串推到 Web 页上或将其存储到数据库中。如果不进行转换,加密字符串中的某些高阶 ASCII 字符将无法正确显示或存储。

在散列中添加一些“盐”值

到目前为止,散列算法暴露出来的问题之一是,如果两个用户碰巧使用相同的密码,那么散列值将完全相同。如果黑客看到您存储密码的表格,会从中找到规律并明白您很可能使用了常见的词语,然后黑客会开始词典攻击以确定这些密码。要确保任何两个用户密码的散列值都不相同,一种方法是在加密密码之前,在每个用户的密码中添加一个唯一的值。这个唯一值称为“盐”值。在进行此操作时,需要确保将使用的盐值存储为用户记录的一部分。如果您使用表格存储用户 ID 和密码,我建议您使用不同的表格来存储盐值。这样,即使数据库泄漏,盐值也可以为您提供一层额外的安全保护。

在用户密码中添加盐值的方法有多种。最简单的方法是摘取用户的某些信息(例如姓、名、电子邮件地址或员工 ID)并将其添加到用户密码中,然后再进行加密。这种方法的缺点是,因为您需要存储盐值,所以如果黑客找到该值,将会对您所做的一切操作了如指掌。当然,黑客需要花费额外的时间来破解盐值,但这对黑客来说简直是易如反掌。

另外一种方法是使用 .NET Framework 类 RNGCryptoServiceProvider 创建一个随机的数字字符串。RNG 表示随机数生成器。该类可以创建一个任意长度的随机字节数组,长度由您指定。您可以使用此随机字节数组作为散列算法的盐值。要采用这种方法,必须安全地存储该盐值。

在图 2 所示的示例中,您需要在文本框中输入一个字符串,选择特定的散列类型,然后生成盐值以及包含该盐值和原始字符串的散列值。

图 2:在散列值中添加盐值以创建更安全的密码散列
(需要存储盐值以便再次创建相同的散列。)

该示例与本文中的上一个示例基本相同,不同之处在于创建盐值的例程。在此屏幕上的按钮的 Click 事件下,首先调用一个名为 CreateSalt() 的方法来生成一个唯一的盐值,然后将该值存储到 txtSalt 文本框中。获得唯一的盐值后,再调用 HashString() 方法,将这两个值结合起来。

' Visual Basic .NET
Private Sub btnHash_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles btnHash.Click
  txtSalt.Text = CreateSalt()
  txtHashed.Text = HashString(txtSalt.Text & _
   txtOriginal.Text)
End Sub

// C#
private void cmdHash_Click(object sender, System.EventArgs e)
{
    txtSalt.Text = CreateSalt();
   txtHashed.Text = HashString(txtOriginal.Text);
}

CreateSalt() 方法的代码非常简单。它首先创建一个长度为 8 个字节的字节数组,然后您创建一个新的 RNGCryptoServiceProvider 类实例。使用该对象的 GetBytes() 方法,将生成的随机字符集填充到字节数组中。然后将此字节数组转换成 Base64 编码字符串并从该函数返回。

' Visual Basic .NET
Private Function CreateSalt() As String
  Dim bytSalt(8) As Byte
  Dim rng As New RNGCryptoServiceProvider

  rng.GetBytes(bytSalt)

  Return Convert.ToBase64String(bytSalt)
End Function

// C#
private string CreateSalt()
{
  byte[] bytSalt = new byte[8];
  RNGCryptoServiceProvider rng;

  rng = new RNGCryptoServiceProvider();

  rng.GetBytes(bytSalt);

  return Convert.ToBase64String(bytSalt);
}

数据加密是一个双行道

如果需要在两个或多个人员或计算机之间来回发送信息,并希望对方能够读取数据,而其他人不可以读取,那么加密则是最好的方式!加密算法使您可以将数据掩盖起来,除了特定人员能够对其解密外,其他人员不大可能通过数学方法读取该数据。但如果您希望某个人能够读取该数据,您可以为其提供一个特定的“密钥”,使其能够解密并读取数据。.NET Framework 中有多种可用的加密/解密算法。本文主要介绍对称算法,包括以下几种:

  • DES
  • RC2
  • Rijndael
  • TripleDES

对称算法(或密钥算法)使用一个密钥和一个初始化向量 (IV) 来保证数据的安全。使用该数据的双方都必须知道这个密钥和初始化向量才能够加密和解密数据。必须确保该密钥的安全,否则其他人将有可能解密该数据并读取该消息。初始化向量只是一个随机生成的字符集,使用它可以确保任何两个文本都不会生成相同的加密数据。使用 .NET 中不同的加密类的内置方法可以导出密钥,至于如何导出密钥,则不属于本文要讨论的内容。

其他类型的加密算法称为不对称算法。不对称算法使用公钥/私钥对来创建加密数据。不对称算法将在下文进行讨论。

如何在不同的情况下选择不同的加密方法

对称算法(或密钥算法)的速度非常快,非常适于加密大型的数据流。这些算法可以加密数据,也可以解密数据。它们都相当安全,但如果有足够的时间,也可能会被破密,因为有人可能会搜索每个已知的密钥值组合。由于每种算法都使用固定的密钥长度或 ASCII 字符,因此计算机程序可以尝试每个可能的密钥组合并最终找到正确的那个组合。这些类型的算法一般用于存储和检索数据库的连接字符串。

不对称算法(或公钥算法)没有对称算法快,但其代码较难破密。这些算法取决于两个密钥,一个是私钥,另一个是公钥。公钥用来加密消息,私钥是可以解密该消息的唯一密钥。公钥和私钥通过数学方法链接在一起,因此要成功进行加密交换,必须获得这两个密钥。由于可能会影响到计算机性能,因此不对称算法不太适用于加密大量数据。不对称算法的常见用法是将对称密钥和初始化向量加密并传输给对方。然后在双方之间来回发送的消息中使用对称算法加密和解密数据。

如果您不打算再恢复原始值,尤其不希望别人发现原始值,那么请使用散列值。散列可以将任意长度的字符串加密为固定的字节集。此操作是单向的,因此通常用于密码这样的少量数据。当用户在安全的输入屏幕上输入用户密码后,程序将对此密码进行加密并将散列值存储到数据库中。即使数据库泄漏,也没有人能够读取密码,因为密码已被加密。当用户登录到该系统进行输入时,将使用相同的算法解密用户键入的密码,如果两个散列值相匹配,系统则可以确定用户输入的值与以前存储的值相同。

加密练习

示例应用程序中包括一个窗体,允许您使用 DES 和 TripleDES 加密服务提供程序进行加密练习。该窗体名为 frmEncrypt,如图 3 所示。

图 3:加密算法允许您加密并解密值。

在该屏幕上,首先需要单击 Gen Key(生成密钥)按钮,然后单击 Gen IV(生成 IV)按钮。然后在 Original String(原始字符串)文本框中输入一些数据并单击 Encrypt(加密)按钮。单击 Encrypt(加密)按钮后,加密后的文本将显示在 Encrypted String(加密字符串)文本框中。如果您希望在自己的应用程序中使用这个加密的字符串,则需要记下生成的密钥和 IV,因为要解密连接字符串以便再次使用,您需要提供这两个值。如果丢失了密钥和 IV,将再也无法恢复连接字符串。

现在看一下其源代码,了解如何实现加密和解密例程。首先看一下该类的成员变量,它用于保存相应加密服务提供程序的引用。该成员变量的类型为 SymmetricAlgorithm。所有的对称算法类都是从这个基类继承而来的。

' Visual Basic .NET
Private mCSP As SymmetricAlgorithm
// C#
private SymmetricAlgorithm mCSP;

根据您在此窗体上选择的选项按钮,此 mCSP 变量将被指定给特定的对称算法类。SetEnc() 方法将负责创建适当的类型并将其返回到不同的方法。

' Visual Basic .NET
Private Function SetEnc() As SymmetricAlgorithm
  If optDES.Checked Then
    Return New DESCryptoServiceProvider
  Else
    If optTripleDES.Checked Then
      Return New TripleDESCryptoServiceProvider
    End If
  End If
End Function

// C#
private SymmetricAlgorithm SetEnc()
{
   if(optDES.Checked)
      return new DESCryptoServiceProvider();
   else
      return new TripleDESCryptoServiceProvider();
}

如您所见,根据您在该窗体上选择的选项按钮,将创建 DESCryptoServiceProvider 对象或 TripleDESCryptoServiceProvider 对象。

实现加密和解密的密钥

要使用对称算法,必须提供要使用的密钥。每个 CryptoSymmetricAlgorithm 实现都提供一种 GenerateKey 方法。它们实际上使用的是公共语言运行时 (CLR) 类中内置的随机数生成器类。我们来看一下 Gen Key(生成密钥)按钮的 Click 事件处理程序,看它如何生成要使用的随机密钥值。

' Visual Basic .NET
Private Sub btnKeyGen_Click(ByVal sender As _
 System.Object, ByVal e As System.EventArgs) _
 Handles btnKeyGen.Click
  mCSP = SetEnc()

  mCSP.GenerateKey()

  txtKey.Text = Convert.ToBase64String(mCSP.Key)
End Sub
// C#
private void btnKeyGen_Click(object sender, 
 System.EventArgs e)
{
   mCSP = SetEnc();

   mCSP.GenerateKey();

   txtKey.Text = Convert.ToBase64String(mCSP.Key);
}

获取服务提供程序的特定实现后,只需调用 GenerateKey 方法来创建一个用于加密的新的随机密钥。密钥的大小取决于用来加密的特定提供程序。例如,DES 密钥的大小为 64 位,而 TripleDES 密钥的大小为 192 位。每个 SymmetricAlgorithm 类上都有一个 KeySize 属性,它将返回用于生成密钥的密钥大小。

我们还需要生成初始化向量 (IV)。IV 将帮助算法生成最终加密字符串的数据块。IV 用于开始第一个块的加密。如果不提供 IV,那么只要密钥相同,在字符串之间传递的通用数据将保持同一种模式。因此,需要使用 IV 作为加密数据的“随机”组件。通过这种方式,只要使用的 IV 不同,即使密钥相同,同一个数据也会被加密成完全不同的值。下面是生成新的 IV 的 Gen IV(生成 IV)按钮的源代码。

' Visual Basic .NET
Private Sub btnIVGen_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 
Handles btnIVGen.Click mCSP.GenerateIV() txtIV.Text = Convert.ToBase64String(mCSP.IV) End Sub // C# private void btnIVGen_Click(object sender, System.EventArgs e) { mCSP.GenerateIV(); txtIV.Text = Convert.ToBase64String(mCSP.IV); }

此代码看起来与生成密钥的代码非常相似。每个加密服务提供程序类上都有一个 GenerateIV() 方法。如果未提供 IV,该方法将生成一个 IV。

加密数据

获得密钥和初始化向量后,现在可以使用 KeyIVOriginal String 值来创建原始字符串值的加密版本。单击 Encrypt(加密)按钮将运行以下代码。

' Visual Basic .NET
Private Sub btnEncrypt_Click( _
 ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdEncrypt.Click
  txtEncrypted.Text = EncryptString(txtOriginal.Text)
End Sub

// C#
private void cmdEncrypt_Click(object sender, System.EventArgs e)
{
   txtEncrypted.Text = EncryptString(txtOriginal.Text);
}

Click 事件过程将调用名为 EncryptString() 的方法,从 Original String(原始字符串)文本框中接受值并对其进行加密。然后返回该值并将其放到 Encrypted String(加密字符串)文本框中。下面是 EncryptString() 方法的代码。

' Visual Basic .NET
Private Function EncryptString(ByVal Value As String) _
 As String
  Dim ct As ICryptoTransform
  Dim ms As MemoryStream
  Dim cs As CryptoStream
  Dim byt() As Byte

  ct = mCSP.CreateEncryptor(mCSP.Key, mCSP.IV)

  byt = Encoding.UTF8.GetBytes(Value)

  ms = New MemoryStream
  cs = New CryptoStream(ms, ct, CryptoStreamMode.Write)
  cs.Write(byt, 0, byt.Length)
  cs.FlushFinalBlock()

  cs.Close()

  Return Convert.ToBase64String(ms.ToArray())
End Function

// C#
private string EncryptString(string Value)
{
  ICryptoTransform ct;
  MemoryStream ms;
  CryptoStream cs;
  byte[] byt;

  ct = mCSP.CreateEncryptor(mCSP.Key, mCSP.IV);

  byt = Encoding.UTF8.GetBytes(Value);

  ms = new MemoryStream();
  cs = new CryptoStream(ms, ct, CryptoStreamMode.Write);
  cs.Write(byt, 0, byt.Length);
  cs.FlushFinalBlock();

  cs.Close();

  return Convert.ToBase64String(ms.ToArray());
}

现在我们分开看一下各行代码并了解这些代码的作用。首先是加密进程的几个变量。

Dim ct As ICryptoTransform
Dim ms As MemoryStream
Dim cs As CryptoStream
Dim byt() As Byte

ICryptoTransform 是一个接口。需要此接口才能在任何服务提供程序上调用 CreateEncryptor 方法,服务提供程序将返回定义该接口的实际 encryptor 对象。

然后需要将原始字符串转换成字节数组。大多数 .NET 加密算法处理的是字节数组而不是字符串。

byt = Encoding.UTF8.GetBytes(Value)

现在可以执行实际的加密了。此进程需要创建一个数据流,用于将加密的字节写入到其中。要使用名为 msMemoryStream 对象、ICryptoTransform 对象(提供给 CryptoStream 类的构造函数)以及说明您希望在何种模式(读、写等)下创建该类的枚举常数。创建 CryptoStream 对象 cs 后,现在使用 CryptoStream 对象的 Write 方法将数据写入到内存数据流。这就是进行实际加密的方法,加密每个数据块时,数据将被写入 MemoryStream 对象。

ms = New MemoryStream
cs = New CryptoStream(ms, ct, CryptoStreamMode.Write)
cs.Write(byt, 0, byt.Length)
cs.FlushFinalBlock()

cs.Close()

创建 MemoryStream 后,该代码将在 CryptoStream 对象上执行 FlushFinalBlock 方法,以确保所有数据均被写入 MemoryStream 对象。该过程将关闭 CryptoStream 对象。

最后,该过程将内存数据流从字节数组转换回字符串,这样才可以在窗体上的文本框内显示该字符串。可以使用 MemoryStream ToArray() 方法从数据流中获取字节数组,然后调用 Convert.ToBase64String() 方法,该方法接受字节数组输入并使用 Base64 编码方法将该字符串编码为可读内容。

解密数据

加密数据后,有时还需要解密数据。解密数据的过程非常简单,与加密过程相似。您需要提供加密过程中使用的密钥和初始化向量。SymmetricAlgorithm 类的 KeyIV 属性被定义为字节数组。因此,设置这些属性之前需要提供您创建的字符串并将其转换成字节数组。下面我们看一下窗体内用于解密字符串的 DecryptString 方法。该方法是从窗体上 Decrypt(解密)按钮的 Click 事件处理程序中调用的。

' Visual Basic .NET
Private Function DecryptString(ByVal Value As String) _
 As String
  Dim ct As ICryptoTransform
  Dim ms As MemoryStream
  Dim cs As CryptoStream
  Dim byt() As Byte

  ct = mCSP.CreateDecryptor(mCSP.Key, mCSP.IV)

  byt = Convert.FromBase64String(Value)

  ms = New MemoryStream
  cs = New CryptoStream(ms, ct, CryptoStreamMode.Write)
  cs.Write(byt, 0, byt.Length)
  cs.FlushFinalBlock()

  cs.Close()

  Return Encoding.UTF8.GetString(ms.ToArray())
End Function

// C#
private string DecryptString(string Value)
{
   ICryptoTransform ct;
   MemoryStream ms;
   CryptoStream cs;
   byte[] byt;

   ct = mCSP.CreateDecryptor(mCSP.Key, mCSP.IV);

   byt = Convert.FromBase64String(Value);

   ms = new MemoryStream();
   cs = new CryptoStream(ms, ct, CryptoStreamMode.Write);
   cs.Write(byt, 0, byt.Length);
   cs.FlushFinalBlock();

   cs.Close();

   return Encoding.UTF8.GetString(ms.ToArray());
}

Encrypt 函数和 Decrypt 函数只有三个不同之处。

  1. 需要使用 CryptoServiceProvider 类的 CreateDecryptor 方法来创建相应的 ICtryptoTransform 对象。
  2. 需要将 Base64 编码字符串转换成字节数组。需要使用 Convert.FromBase64String 方法来实现此转换。
  3. 通过对原始字节数组进行转换,将字节数组转换成相应的内存数据流。需要将内存数据流从字节数组转换回可以在窗体上再次显示的普通字符串。需要使用 Encoding.UTF8.GetString() 方法来实现此转换。
    注意:Encoding.UTF8 类来自于 System.Text 命名空间。

是否可以使其更简单?!

尽管到目前为止显示的代码并不难,但有很多不同的类和接口您可能还不习惯于使用。此外,还要记住很多代码。下面我们学习如何将 Cryptography 类包装成易于使用的类。

名为 PDSACryptography 的程序集中有两个类,分别为 PDSAHash 和 PDSAEncryption。这两个类用于封装创建散列字符串或加密字符串的实际机制。此外,它们还允许您使用枚举常数来决定要使用哪种散列或加密算法。不必记住每个不同的加密服务提供程序的所有不同的名称,即可获得不错的 Intellisense® 提供程序列表。

使用 PDSAHash 包装散列

PDSAHash 类包含属性 HashTypeHashObjectOriginalStringHashStringSaltValueUseSaltSaltLength。与该类相关的方法包括 SetEncryptorCreateSaltResetCreateHash。该类中创建了一个称为 PDSAHashType 的枚举,您可以从中选择要使用的相应散列类。该类的作用是将上文所示的代码简化为以下代码。

Private Sub UsePDSAHash()
  Dim ph As New PDSAHash(PDSAHash.PDSAHashType.MD5)

  MessageBox.Show(ph.CreateHash("Paul"))
End Sub

我宁愿键入以上代码也不愿意记住上文显示的所有代码。如您所见,这段代码相当简单,与上文所述的代码完全相同,只是被包装到一个易于使用的接口中。您可以从本文包含的示例中找到完整的类。下面列举了可以使用该类创建的不同散列算法。

Public Enum PDSAHashType As Byte
  MD5
  SHA1
  SHA256
  SHA384
  SHA512
End Enum

其中的每个算法都为最终的散列提供了一个不同的安全级别。.NET 中完整的散列类列表如下所示:

  • MD5CryptoServiceProvider
  • SHA1CryptoServiceProvider
  • SHA256Managed
  • SHA384Managed
  • SHA512Managed

有关这些不同的散列类型的详细信息,请参阅 Visual Studio .NET 联机文档。

使用 PDSAEncryption 包装加密

就像 PDSAHash 类可以包装 .NET Framework 中的所有散列功能一样,PDSAEncryption 类可用于包装 .NET Framework 中的各种对称算法类。PDSAEncryption 类包括以下枚举类型,允许您创建各种加密/解密对象。

Public Enum PDSAEncryptionType As Byte
  DES
  RC2
  Rijndael
  TripleDES
End Enum

同样,每个服务提供程序都为加密字符串提供了不同的安全级别。这在 Visual Studio .NET 联机文档中都有详细的介绍,这里不再赘述。

该类包含属性 EncryptionTypeOriginalStringEncryptedStringKeyKeyStringIVIVStringCryptoProvider。其中的大多数属性都是不言自明的,但 KeyIV 属于字节数组,而 KeyStringIVString 是这些字节数组的字符串表示。

该类还包含一些方法。例如 EncryptDecrypt。还有 GenerateKeyGenerateIV 方法,如果没有现成的密钥和 IV,可以使用这两个方法创建一个密钥和 IV。另外还有 SetEncryptor 方法,它用于创建将在各种方法中使用的新的 CryptoProvider 对象。

该类的作用是使加密和解密字符串更容易实现。例如,下面的代码片断显示了使用该类加密字符串是多么容易。

Private Sub btnHardCoded_Click( _
 ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles btnHardCoded.Click
  Dim ps As New  PDSASymmetric( _ 
   PDSASymmetric.PDSAEncryptionType.TripleDES)

  MessageBox.Show(ps.Encrypt( _
   "Server=localhost;Database=Northwind;uid=sa;pwd=sa"))
End Sub

小结

使用 Microsoft .NET Framework 中的类可以很容易地在计算机中保存机密信息。您会发现 Cryptography 命名空间中的多个类都可以很好完成这一任务。为这些类创建包装可以帮助您大大减少需要编写的代码量。强烈建议您按照本文所述,创建类似的包装。

作者简介

Paul Sheriff 是 PDSA, Inc. 的总裁,该公司提供有关 .NET 的咨询、产品和服务,包括 SDLC 文档和体系结构框架 (www.pdsa.com)。Paul 是 Microsoft 在南加利福尼亚的区域负责人(英文)。他编写的 .NET 方面的书籍包括《ASP.NET Developer's Jumpstart》(Addison-Wesley) 以及在 PDSA Web 站点(英文)上列出的一些电子图书。

posted on 2007-01-29 11:25  Achilles.NET  阅读(284)  评论(0编辑  收藏  举报