为散列加入扰乱值
迄今为止,散列算法最大的问题就是如果两个用户碰巧使用相同的密码,那么经过散列运算的值将完全相同。如果一个黑客看到你用于存储密码的数据表,他将找到模式并且知道大多数人会选择普通的单词作为密码,从而有可能通过字典攻击得到密码。保证任何两个用户的密码经散列运算后均得到不同的值的一种方法是,在进行散列运算之前,为每个用户的密码加上一个唯一的值。这个唯一的值被称为扰乱值(salt)。当你采用这种方案的时候,你必须保证将扰乱值作为用户数据的一部分保存。我建议你将用户名和密码存储在一个表中,而将扰乱值存储在另一个表中。这样可以为你的数据库提供额外的安全保障。
有很多方法可以为每一个用户的密码增加扰乱值。一个简单的方式是将一些其他的信息,例如用户的姓氏、名字、电子地址或者用户编码,和用户密码连接,然后进行散列运算。这种方法的缺点是既然你需要在同一个表中存储扰乱值,如果黑客找出了这个值,他将知道你所进行的操作。当然,这将增大黑客破解的难度,但是这是一项常见的技巧。
另一种方法就是使用.net框架类RNGCryptoServiceProvider生成一个包含数字的随机字符串。RNG表示随机数字生成器(Random Number Generator)。这个类将依照你指定的长度生成一个随机byte数组。你可以将这个随机byte数组作为散列算法的扰乱值。如果你选择这种方法,你必须安全的存储这个散列值。
在图2显示的第二个例子中,你可以在文本框中输入一个字符串,选择一种散列类型,然后生成一个扰乱值,和原始字符串连接后进行散列操作。
图2 利用扰乱值生成更加安全的散列密码
(你必须保存扰乱值以便能够再次生成一样的散列)
这个例子和本文前面的例子几乎完全相同,除了生成扰乱值的例程。在按钮的Click事件中,你将首先调用一个名为CreatSalt()的方法来生成唯一的扰乱值,然后将扰乱值存储在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个byte值的数组,然后创建一个RNGCryptoServiceProvider类的新实例,通过调用这个对象的GetBytes()方法就可以使用随机生成的字符填充byte数组了。然后这个数组被转换为一个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);
}
双向加密
当你需要在两个或者更多个人或者计算机之间传递信息,且对方可以获得信息的内容,还要保证信息的内容不被其他人所窃取,加密技术就是解决的最好方案。加密技术可以将数据伪装成某种格式,理论上,只有授权的人才可以译解信息的真实内容。你可以通过给予一个特定的可解密信息的“键码”(Key)来完成授权,从而使得信息再次具有可读性。在.net框架中有几种加密/解密算法,本文侧重于其中的对称算法(symmetric algorithms),例如:
DES
RC2
Rijndael
TripleDES
对称算法(又称密码算法)通过一个键值和一个初始向量(Initialization Vector)保障数据的安全。传递消息的双方必须具有同样的键值和初始向量用以完成数据的加密和解密。初始向量是一组随机生成的字符,用来保证信息中的任何两个片断均生成不同的加密结果。键值可以通过.net中不同加密类的方法生成。密码的生成方法,超出了本文的讨论范围。
另一种加密方法叫做非对称加密算法(asymmetric algorithm),非对称加密算法采用公钥/私钥配对来完成数据的加密。非对称加密算法将在后面的章节中介绍。
如何选择加密算法
对称加密算法,或者称为密码算法,速度很快,故适用于加密较大的数据流。这些算法可同时用于加密和解密数据。虽然这些加密算法还算安全,但是如果有足够的时间,他人还是可以通过穷举所有的已知的密码组合来达到破解的目的。既然每一种加密算法均使用一个固定长度的密码,即ASCII字符,通过计算机程序穷举每一个可能的密码组合并最终得到正确的密码,是可行的。对称加密算法的一个常见用途是用以存储和取得数据库的连接字符串。
非对称加密算法,或者称为公钥加密算法的速度比对称加密算法要慢一些,但是生成的密文也更加难以破解。这些加密算法依赖于两个键值,一个称为公钥,另一个成为私钥。公钥用于对数据进行加密,私钥是唯一一个可以解密数据的键值。公钥和私钥是密不可分的,只有同时具备两个键值才能保证信息的正确传递。出于性能方面的考虑,非对称加密算法并不适于对大量的数据进行操作。非对称加密算法的常见用途之一是用以传递另一个用于对称加密操作的密码和初始向量。然后,来往于双方的信息就可以通过对称加密算法进行加密了。
散列算法用于你不希望再得到数据的原始值的情况,特别是你希望其他任何人都无法得到数据的原始值。散列算法可以将一个任意长度的字符串转换为一个固定长度的byte数组。散列操作是单向的,所以通常用于少量的数据,例如密码。如果用户再一个安全认证窗体上输入了密码,程序可以将输入的值进行散列操作,将得到的散列值存储于数据库中。即使数据库被攻破,既然密码已经被进行散列操作,任何人都无法得到密码的原始值。当用户需要登入系统的时候,用户输入的密码被用同样的算法进行散列操作,如果两个散列值吻合,系统就可以判定,用户两次输入的密码是相同的。
尝试加密
示例程序包括一个窗体,你可以选择DES或者TripleDES加密服务提供者,窗体的名称是frmEncrypt,如图3所示:
图3:加密算法允许你对数据进行加密和解密操作
你需要首先点击窗体上的Gen Key按钮,来生成加密操作需要的键值,然后点击Gen IV按钮,生成初始向量。在Original String文本框中输入原始字符串,然后点击Encrypt按钮。点击Encrypt按钮后,加密的字符串将显示在Encrypted String文本框中。如果你希望在你的应用程序中使用加密的字符串,你需要保存生成的键值和初始向量,以便在需要象连接字符串这样的信息的时候进行数据的解密操作。如果你遗失了键值或者初始向量,你将无法还原原始的连接字符串。
现在让我们看看窗体后面实现加密和解密功能的代码。首先,我们看看类中的用以保存合适的加密服务提供者的成员变量。这个成员变量的类型是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方法,这些方法实际上使用的时公共语言运行时中的随机数字生成类。让我们看看Gen Key按钮的单击事件处理程序时怎样生成一个随机的键值的:
以下内容为程序代码:
' 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),初始向量将帮助算法逐块生成最终的加密字符串。初始向量用于对第一块数据进行加密,如果你没有提供初始向量,键值相同的时候所有的待加密的字符串将遵从相同的模式进行加密。所以可以将初始向量看作一个加密数据时使用的“随机”组件。实际上,即使你使用相同的键值,如果初始向量不同。下面就是Gen 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()方法,在你没有提供初始向量的时候生成需要的初始向量。
加密数据
一旦你得到了键值和初始向量,你就可以使用Key、IV和Original 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);
}
单击事件处理程序将调用一个叫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变量,其中就包含这个接口的定义。
接下来你需要将原始的字符串转换为byte数组,大多数的.net加密算法处理的数据是byte数组,而非字符串。
以下内容为程序代码:
byt = Encoding.UTF8.GetBytes(Value)
现在你就可以进行真正的加密操作了。这个过程包括创建一个用以存储加密的byte数据的数据流(stream)。你将一个名为ms的MemoryStream和一个ICryptoTransform对象作为参数传入CryptoStream的构造函数,另一个枚举型常量参数定义了你创建类的模式(读,写,或其他的什么)。一旦创建了CryptoStream对象,你可以利用CryptoStream对象的Write方法将数据写入内存流(memory stream)中。这个方法执行了实际的加密操作,它逐块的加密了原始数据,将加密后的数据写入MemoryStream对象。
以下内容为程序代码:
ms = New MemoryStream
cs = New CryptoStream(ms, ct, CryptoStreamMode.Write)
cs.Write(byt, 0, byt.Length)
cs.FlushFinalBlock()
cs.Close()
一旦数据被写入MemoryStream对象,代码便调用了CryptoStream对象中的FlushFinalBlock方法,以保证所有的数据被写入FlushFinalBlock对象,同时也将关闭CryptoStream对象。
最后,程序将内存流中存储的byte数组转化为字符串以便在窗体中的文本框中进行显示。你可以使用MemoryStream ToArray()方法将byte数组从流中取出,然后调用Convert.ToBase64String()方法,这个方法将byte数组中的数据取出以Base64格式进行输出以得到可读的结果。
解密你的数据
对数据进行加密后,你也会需要得到数据的原始值。解密的过程十分简单,并且很类似于加密的过程。你需要提供和进行加密时一致的键值和初始向量。SymmetricAlgorithm的Key和IV属性被定义为Byte数组,所以你需要在设定这些属性之前将其转换为byte数组。让我们看看窗体中进行加密字符串的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());
}
加密和解密的函数有三点区别:
1、必须使用CryptoServiceProvider的CreateDecryptor方法创建合适的ICtryptoTransform对象。
2、需要从一个Base64编码的字符串创建一个byte数组,可以借助Convert.FromBase64String方法实现这个功能。
3、需要通过对原始byte数组进行转换创建一个合适的内存流。还需要将内存流中的数据从byte数组转换为普通的字符串,以便可以在窗体上进行显示,可以借助Encoding.UTF8.GetString()方法进行上述转换。
注意:Encoding.UTF8类包含在System.Text工作空间内
阅读全文...
返回摘要...