代码改变世界

net加密基础3-数据完整性(散列)

2012-02-15 22:04  海不是蓝  阅读(1555)  评论(3编辑  收藏  举报

数据完整性概念

为什么需要数据完整性?

之前的数据加密技术可以保护信息不被第三方获取,但是不能阻止恶意用户对信息的篡改。

假如:用户A发送一个加密信息给解密用户B,很长的时间都是正常运行,但是突然有一天用户A的电脑被用户C获取了,但是用户C不知道密钥,所以无法和用户B进行沟通,但是用户C可以修改包含恶意数据的信息发给用户B。

用户B无法解密这些信息,这些恶意的信息还可能对用户B的电脑进行攻击。

所以验证数据的完整性和正确性就很重要了,特别是一些涉及到金融和对安全性要求高的项目,对数据的完整性和正确性要求就更加严格了!


 

散列算法

什么是散列算法?

散列算法可以使用任意数量的数据,并且用它生成该信息的唯一较小的散列码。

散列算法的特点

1.单向

如果你想要根据散列码来还原文档是完全不可能的(除非你具有逆天的能力!),因为散列不包含文档的所有信息。

 

2.最小的冲突

几乎是不可能创建出2个散列码一样的文档(如果你无意成功了,请去把你所有家当拿去买彩票吧,记得支援我点!)。

以上这些原因使散列成为验证数据完整性的最佳算法。


 

NET中的散列类

NET中的散列算法类使用的3层继承模型与NET中的加密算法模型相同。

NET中所有的散列算法都继承HashAlgorithm抽象类。

算法

抽象散列算法类

实现类

散列长度(位)

MD5

MD5

MD5CryptoServiceProvider

128

SHA-1

SHA1

SHA1CryptoServiceProvider

160

SHA-256

SHA256

SHA256Managed

256

SHA-384

SHA384

SHA384Managed

384

SHA512

SHA512

SHA512Managed

512

 

散列长度和安全性

对于大部分散列算法来说,散列的长度越长,散列码抵抗蛮力攻击的能力就越强,发现相同散列文档也就越难。

如果一个散列有256位,那么它就有2^256次方这么多种不同的散列码,貌似宇宙也只有10^66种原子!现在知道你是在逆天了吧!

下面我们使用MD5实现个简单的数据散列


 

View Code
        static void Main(string[] args)
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
Console.WriteLine("MD5 Hash size:{0} bits", md5.HashSize);
Console.WriteLine("MD5 Hash size:{0} bytes", md5.HashSize / 8);
Console.WriteLine();
byte[] data = { 0x13, 0x45, 0x5A, 0xB6 };
//计算data的散列码
byte[] HashBytes = md5.ComputeHash(data);
Console.WriteLine("HashBytes size:{0} bytes", HashBytes.Length);
Console.WriteLine("Hash:{0}", BitConverter.ToString(HashBytes));
Console.Read();
}


多次运行这个程序,如果不修改byte数组的数据,那么生成的散列码都是相同的。

对于不同的MD5对象去计算相同的数据散列,其结果是相同的。


 

View Code
        static void Main(string[] args)
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
MD5CryptoServiceProvider md51 = new MD5CryptoServiceProvider();
byte[] data = { 0x13, 0x45, 0x5A, 0xB6 };
//计算data的散列码
byte[] HashBytes = md5.ComputeHash(data);
Console.WriteLine("HashBytes size:{0} bytes", HashBytes.Length);
Console.WriteLine("Hash:{0}", BitConverter.ToString(HashBytes));
byte[] HashBytes1 = md5.ComputeHash(data);
Console.WriteLine("HashBytes1 size:{0} bytes", HashBytes1.Length);
Console.WriteLine("Hash:{0}", BitConverter.ToString(HashBytes1));
Console.Read();
}


 

散列对数据流的支持

ComputeHash有个重载的方法,接受一个流对象,然后从流的当前位置读取完整的流数据并且对其计算散列。


View Code
        static void Main(string[] args)
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] data = { 0x13, 0x45, 0x5A, 0xB6 };
MemoryStream ms = new MemoryStream();
ms.Write(data, 0, data.Length);
//计算data的散列码
byte[] HashBytes = md5.ComputeHash(ms);
Console.WriteLine("HashBytes size:{0} bytes", HashBytes.Length);
Console.WriteLine("Hash:{0}", BitConverter.ToString(HashBytes));
Console.Read();
}


请注意散列以后的流的内部指针位置,如果需要重新使用该流,那么请重新设置该流的位置。

 

散列CryptoStream中的数据

回忆之前我们对称加密算法中对CryptoStream的使用,

DES des = DESCryptoServiceProvider.Create();

MemoryStream ms = new MemoryStream();

CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);

 

这里des.CreateEncryptor()方法返回的是ICryptoTransform,我们再来看看MD5的基类实现。

public abstract class MD5 : HashAlgorithm

public abstract class HashAlgorithm : ICryptoTransformIDisposable

所以我们可以把散列对象传入CryptoStream中(注意CryptoStream必须是在写的模式下)。


 

View Code
        static void Main(string[] args)
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] data = { 0x13, 0x45, 0x5A, 0xB6 };
MemoryStream ms = new MemoryStream();
ms.Write(data, 0, data.Length);
MemoryStream ms1 = new MemoryStream();
CryptoStream cry = new CryptoStream(ms1, md5, CryptoStreamMode.Write);
cry.Write(data, 0, data.Length);
cry.FlushFinalBlock();
ms1.Close();
byte[] HashBytes = md5.Hash;
Console.WriteLine("HashBytes size:{0} bytes", HashBytes.Length);
Console.WriteLine("Hash:{0}", BitConverter.ToString(HashBytes));
Console.Read();
}


介绍完了散列的基本用法,下面介绍下散列的应用

 

加密散列码

使用散列就是为了验证数据的完整性,那么如果非法用户向服务器发送了一条错误的信息,并且对其生成了正确的散列码,那么服务器验证发现散列码是正确的,但是却无法对数据正确的解密,而且那些数据很有可能是包含了恶意的攻击代码。

 

所以在实际的应用中一般是把明文信息和散列码一起加密发送到解密方,但是这样的方法还是有个缺点,就是如果非法用户获取了公钥,那么还是可以伪造密文和加密散列。

散列加密文件

这里我们使用前面介绍到的net中的散列知识对一个文件进行散列然后对散列码和原文进行加密,然后再解密验证散列码。

(具体看代码吧,啰嗦太累了)


 

       static void Main(string[] args)
{
//只是图方便,实际项目别这样不规范
Encryptor();

Decryptor();

Console.Read();
}
public static void Encryptor()
{
CryptoStream cry = null; ;
FileStream FileInput = null;
FileStream FileOut = null;

//判断是否应该关闭CryptoStream
bool cryb = true;

try
{
FileInput = new FileStream(@"c:\123.xml", FileMode.Open);
FileOut = new FileStream(@"c:\456.xml", FileMode.Create);

byte[] key = Encoding.ASCII.GetBytes("ujd87yr4");

DESCryptoServiceProvider des = new DESCryptoServiceProvider();
cry = new CryptoStream(FileOut, des.CreateEncryptor(key, key),
CryptoStreamMode.Write);

//考虑到加密xml文件可能很大
byte[] bytes = new byte[1024];
int read = 0;
do
{
//读取到缓冲区
read = FileInput.Read(bytes, 0, 1024);
//写入加密流
cry.Write(bytes, 0, read);
}
while (read > 0);
//注意重新设置流的位置
FileInput.Position = 0;

MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
md5.ComputeHash(FileInput);

cry.Write(md5.Hash, 0, md5.Hash.Length);
cry.FlushFinalBlock();

Console.WriteLine("完成...");
}

catch (IOException ex)
{
Console.WriteLine("IOException:{0}", ex.Message);
}
catch (CryptographicException ex1)
{
cryb = false;
Console.WriteLine("CryptographicException:{0}", ex1.Message);
}

finally
{
if (FileInput != null)
FileInput.Close();

if (FileOut != null)
FileOut.Close();

if (cryb && cry != null)
cry.Close();
}
}
public static void Decryptor()
{
CryptoStream cry = null; ;
FileStream FileOut = null;
MemoryStream ms = null;

//判断是否应该关闭CryptoStream
bool cryb = true;

try
{
FileOut = new FileStream(@"c:\456.xml", FileMode.Open);

DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] key = Encoding.ASCII.GetBytes("ujd87yr4");
des.Key = des.IV = key;

cry = new CryptoStream(FileOut, des.CreateDecryptor(), CryptoStreamMode.Read);
ms = new MemoryStream();

//考虑到解密xml文件可能很大
byte[] bytes = new byte[1024];

Int32 read = 0;
do
{

read = cry.Read(bytes, 0, 1024);

ms.Write(bytes, 0, read);
} while (read > 0);
//设置内存流位置
ms.Position = 0;

MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
Int32 HashSize = md5.HashSize / 8;

//获取xml解密数据
byte[] FileBytes = new byte[ms.Length - HashSize];
ms.Read(FileBytes, 0, FileBytes.Length);

//获取md5散列码
byte[] Md5Bytes = new byte[HashSize];
//不出问题那么内存流当前位置就是我们期待的散列码了
ms.Read(Md5Bytes, 0, Md5Bytes.Length);

md5.ComputeHash(FileBytes);

//验证数据完整性
bool b = true;
for (Int32 i = 0; i < md5.Hash.Length; i++)
{
if (md5.Hash[i] != Md5Bytes[i])
{
b = false;
break;
}
}

if (b)
{
Console.WriteLine("数据完整");
Console.WriteLine(Encoding.UTF8.GetString(FileBytes));
}
else
Console.WriteLine("数据不完整");
}

catch (IOException ex)
{
Console.WriteLine("IOException:{0}", ex.Message);
}

catch (CryptographicException ex1)
{
cryb = false;
Console.WriteLine("CryptographicException:{0}", ex1.Message);
}

finally
{
if (ms != null)
ms.Close();

if (FileOut != null)
FileOut.Close();

if (cryb && cry != null)
cry.Close();
}
}


 




作者:海不是蓝

博客:http://www.cnblogs.com/hailan2012/

邮箱:hailan2012@sina.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。