HashAlgorithm类支持数据块的Hash运算:对数据前面块通过TransformBlock方法计算,最后一块通过TransformFinalBlock方法计算,结果通过Hash属性获得,便可完成整个Hash过程。对于一个数据流(如文件流),可以利用这个办法,提供Hash的计算进度。下面是一个不带计算进度的使用的使用TransformBlock方式的代码。
using System;
using System.IO;
using System.Security.Cryptography;
namespace SHA1_and_MD5
{
public static class Algorithm
{
/// <summary>
/// 计算文件的Hash值
/// </summary>
/// <param name="fileName">文件名</param>
/// <param name="hashName">Hash名</param>
/// <returns>Hash的文本结果</returns>
public static string ComputeHash(string fileName, string hashName)
{
var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
var value = Hash(fs, hashName);
fs.Close();
return BitConverter.ToString(value).Replace("-", "").ToLower();
}
/// <summary>
/// 计算流的Hash值
/// </summary>
/// <param name="stream">流</param>
/// <param name="hashName">Hash名</param>
/// <returns>Hash结果</returns>
public static byte[] ComputeHash(Stream stream, string hashName)
{
const int bufferSize = 4096; //0x1000
var hash = HashAlgorithm.Create(hashName);
int num;
var buffer = new byte[bufferSize];
do
{
num = stream.Read(buffer, 0, bufferSize);
if (num == bufferSize)
{
hash.TransformBlock(buffer, 0, num, buffer, 0);
}
else if (stream.Length == stream.Position)
{
hash.TransformFinalBlock(buffer, 0, num);
}
else if (num > 0)
{
//当 0 < num < bufferSize 的情况
var buffer2 = new byte[num];
Buffer.BlockCopy(buffer, 0, buffer2, 0, num);
hash.TransformBlock(buffer2, 0, num, buffer2, 0);
}
} while (num > 0);
return hash.Hash;
}
}
}
代码中hashName参数可以为以下值:
hashName参考:http://msdn.microsoft.com/zh-cn/library/wet69s13(VS.90).aspx | |
简单名称 | 算法实现 |
SHA | SHA1CryptoServiceProvider |
SHA1 | SHA1CryptoServiceProvider |
System.Security.Cryptography.SHA1 | SHA1CryptoServiceProvider |
System.Security.Cryptography.HashAlgorithm | SHA1CryptoServiceProvider |
MD5 | MD5CryptoServiceProvider |
System.Security.Cryptography.MD5 | MD5CryptoServiceProvider |
SHA256 | SHA256Managed |
SHA-256 | SHA256Managed |
System.Security.Cryptography.SHA256 | SHA256Managed |
SHA384 | SHA384Managed |
SHA-384 | SHA384Managed |
System.Security.Cryptography.SHA384 | SHA384Managed |
SHA512 | SHA512Managed |
SHA-512 | SHA512Managed |
System.Security.Cryptography.SHA512 | SHA512Managed |
比如,想计算系统记事本的sha1和md5,可以这样调用:
接下来,对上面的代码进行修改,加入计算时回调HashingCallback和结束时回调EndCallback,在进行ComputeHash计算时,do-while中每处理完成一组数据,执行一次HashingCallback的回调函数,全部计算完成后执行EndCallback的回调函数。为了在计算过程中取消计算,增加Stop方法,它将_cancel字段设置为true,而ComputeHash方法的do-while条件增加对_cancel的判断。回调函数的参数都包含了一个可传递的object类型的State属性,HashingCallback委托的HashingState参数,目的给出计算Hash的进度,EndCallback的EndState属性,目的通知计算结束,给出是否为中途取消而结束的。而最后的Hash结果通过Hash属性获得。另外,重载ToString方法,直接输出字符串的Hash结果。
于是,我们的代码改写成可实例化的类:
using System;
using System.IO;
using System.Security.Cryptography;
namespace SHA1_and_MD5
{
public class MyHashAlgorithm
{
#region Delegates
/// <summary>
/// 完成Hash计算的回调
/// </summary>
public delegate void EndCallback(EndState endState);
/// <summary>
/// 进行Hash计算时的回调
/// </summary>
public delegate void HashingCallback(HashingState hashingState);
#endregion
private bool _cancel; //取消计算
/// <summary>
/// 获得Hash结果
/// </summary>
public byte[] Hash { get; private set; }
/// <summary>
/// 输出Hash结果的字符串版本
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Hash != null ? BitConverter.ToString(Hash).Replace("-", "").ToLower() : "";
}
/// <summary>
/// 停止计算
/// </summary>
public void Stop()
{
_cancel = true;
}
/// <summary>
/// 计算文件的Hash值
/// </summary>
/// <param name="fileName">文件名</param>
/// <param name="hashName">Hash名</param>
/// <param name="hashingCallback">进度回调函数</param>
/// <param name="endCallback">结束计算的回调函数</param>
/// <param name="state">传递给回调的参数</param>
public void ComputeHash(string fileName, string hashName, HashingCallback hashingCallback,
EndCallback endCallback, object state)
{
using(var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
ComputeHash(fs, hashName, hashingCallback, endCallback, state);
}
}
/// <summary>
/// 计算流的Hash值
/// </summary>
/// <param name="stream">流</param>
/// <param name="hashName">Hash名</param>
/// <param name="hashingCallback">进度回调函数</param>
/// <param name="endCallback">结束计算的回调函数</param>
/// <param name="state">传递给回调的参数</param>
public void ComputeHash(Stream stream, string hashName, HashingCallback hashingCallback, EndCallback endCallback,
object state)
{
const int bufferSize = 4096; //0x1000
Hash = null;
_cancel = false;
bool nullHashingCallback = hashingCallback == null; //未指定计算时的回调
long bytesRead = 0L; //已经完成的数据
long totalBytesLength = stream.Length;
HashAlgorithm hash = HashAlgorithm.Create(hashName);
int num;
var buffer = new byte[bufferSize];
do
{
num = stream.Read(buffer, 0, bufferSize);
if (num == bufferSize)
{
hash.TransformBlock(buffer, 0, num, buffer, 0);
}
else if (totalBytesLength == stream.Position)
{
hash.TransformFinalBlock(buffer, 0, num);
}
else if (num > 0)
{
var buffer2 = new byte[num];
Buffer.BlockCopy(buffer, 0, buffer2, 0, num);
hash.TransformBlock(buffer2, 0, num, buffer2, 0);
}
if (num <= 0 || nullHashingCallback) continue;
bytesRead += num;
hashingCallback(new HashingState(bytesRead, totalBytesLength, state)); //执行计算时回调
} while (num > 0 && !_cancel);
Hash = _cancel ? new byte[0] : hash.Hash; //获得计算结果
if (endCallback != null) endCallback(new EndState(_cancel, state)); //执行结束回调
}
#region Nested type: EndState
/// <summary>
/// 为EndCallback提供数据
/// </summary>
public class EndState
{
internal EndState(bool isCancel, object state)
{
IsCancel = isCancel;
State = state;
}
/// <summary>
/// 是否取消退出的
/// </summary>
public bool IsCancel { get; private set; }
/// <summary>
/// 获得传递的参数
/// </summary>
public object State { get; private set; }
}
#endregion
#region Nested type: HashingState
/// <summary>
/// 为HashingCallback提供数据
/// </summary>
public class HashingState
{
internal HashingState(long byteRead, long totalBytesLength, object state)
{
BytesRead = byteRead;
TotalBytesLength = totalBytesLength;
State = state;
}
/// <summary>
/// 获得已经计算完成的字节数
/// </summary>
public long BytesRead { get; private set; }
/// <summary>
/// 获得总字节数
/// </summary>
public long TotalBytesLength { get; private set; }
/// <summary>
/// 获得传递的参数
/// </summary>
public object State { get; private set; }
}
#endregion
}
}
为了实现多线程支持,在调用时请使用Thread类实现。
--
感谢uking09检查代码中的BUG。