计算文件的MD5校验和


334

我正在使用iTextSharp从PDF文件读取文本。但是,有时我无法提取文本,因为PDF文件仅包含图像。我每天都下载相同的PDF文件,我想看看PDF是否已被修改。如果无法获得文本和修改日期,则MD5校验和是判断文件是否已更改的最可靠方法吗?

如果是这样,将不胜感激一些代码示例,因为我在密码学方面没有太多经验。


Answers:


773

使用System.Security.Cryptography.MD5非常简单:

using (var md5 = MD5.Create())
{
    using (var stream = File.OpenRead(filename))
    {
        return md5.ComputeHash(stream);
    }
}

(我相信实际上不需要处置使用的MD5实现,但无论如何我还是会这样做。)

之后如何比较结果由您决定;例如,您可以将字节数组转换为base64,或者直接比较字节。(请注意,数组不会覆盖Equals。使用base64更容易解决问题,但如果您仅对比较哈希值感兴趣,则使用效率稍低。)

如果您需要将散列表示为字符串,则可以使用BitConverter以下命令将其转换为十六进制:

static string CalculateMD5(string filename)
{
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(filename))
        {
            var hash = md5.ComputeHash(stream);
            return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        }
    }
}

251
如果您想要看起来“标准”的md5,则可以执行以下操作:returnBitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower();
aquinas 2012年

78
MD5位于System.Security.Cryptography中-只是为了进一步展示信息。
汉斯(Hans)

6
@KalaJ:如果您试图发现故意篡改,则CRC32完全不合适。如果您只是在谈论发现数据传输失败,那很好。就个人而言,我可能只是出于习惯使用SHA-256 :)我不了解.NET对CRC32的支持,但您可能会尽快搜索它:)
Jon Skeet 2014年

12
我认为@aquinas .Replace("-", String.Empty)是更好的方法。我经历了一个小时的调试会话,因为在将用户输入与文件哈希进行比较时,我得到了错误的结果。
fabwu

7
@ wuethrich44,我认为您遇到的问题是,如果您逐字复制/粘贴代码到aquinas注释中;我碰巧注意到了同一件事。原始HTML中的“空”引号之间有两个不可见的字符-“零宽度非连接符”和Unicode“零宽度空格”。我不知道它是否在原始评论中,还是SO应该归咎于此。
克里斯·西蒙斯

66

这是我的方法:

using System.IO;
using System.Security.Cryptography;

public string checkMD5(string filename)
{
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(filename))
        {
            return Encoding.Default.GetString(md5.ComputeHash(stream));
        }
    }
}

2
我赞成您,因为更多的人需要做这样的事情。
Krythic '16

6
我认为交换using块将很有用,因为打开文件很可能会失败。在这种情况下,早期失败/快速失败方法可以节省您创建(和销毁)MD5实例所需的资源。另外,您可以省略第一个大括号using并保存一定程度的缩进,而不会损失可读性。
Palec

10
这会将16个字节长的结果转换为16个字符的字符串,而不是预期的32个字符的十六进制值。
NiKiZe

3
此代码不会产生预期的结果(假定的预期)。同意@NiKiZe
Nick

1
@Quibblesome,我只是想推广一种普遍的观念,即using语句的嵌套顺序很重要。在其他地方,差异可能很大。为什么不养成及早发现故障的习惯呢?不过,我同意在此特定代码段中,这种习惯几乎没有好处。
Palec

7

我知道已经回答了这个问题,但这是我使用的:

using (FileStream fStream = File.OpenRead(filename)) {
    return GetHash<MD5>(fStream)
}

哪里GetHash

public static String GetHash<T>(Stream stream) where T : HashAlgorithm {
    StringBuilder sb = new StringBuilder();

    MethodInfo create = typeof(T).GetMethod("Create", new Type[] {});
    using (T crypt = (T) create.Invoke(null, null)) {
        byte[] hashBytes = crypt.ComputeHash(stream);
        foreach (byte bt in hashBytes) {
            sb.Append(bt.ToString("x2"));
        }
    }
    return sb.ToString();
}

可能不是最好的方法,但是它很方便。


我对您的GetHash函数做了一些小的更改。我已将其转换为扩展方法并删除了反射代码。
Leslie Marshall,

3
public static String GetHash<T>(this Stream stream) where T : HashAlgorithm, new() { StringBuilder sb = new StringBuilder(); using (T crypt = new T()) { byte[] hashBytes = crypt.ComputeHash(stream); foreach (byte bt in hashBytes) { sb.Append(bt.ToString("x2")); } } return sb.ToString(); }
莱斯利·马歇尔

这实际上有效。。谢谢!。我花了很长时间在网上寻找可以产生正常32 char md5字符串的结果,其结果超出了我的预期。我希望它稍微复杂一些,但肯定可以。
麻烦

1
@LeslieMarshall如果要使用它作为扩展方法,则应重置流位置,而不是将其保留在结束位置
MikeT

3

这是我发现的一个稍微简单的版本。它可以一次性读取整个文件,只需要一个using指令即可。

byte[] ComputeHash(string filePath)
{
    using (var md5 = MD5.Create())
    {
        return md5.ComputeHash(File.ReadAllBytes(filePath));
    }
}

50
使用的缺点ReadAllBytes是它将整个文件加载到单个数组中。这对于大于2 GiB的文件根本不起作用,即使对于中型文件,也给GC带来了很大压力。乔恩的答案只是稍微复杂一点,但不会遭受这些问题的困扰。所以我更喜欢他的回答,而不是你的回答。
CodesInChaos

1
依次插入usings和第一个大括号using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename)),这样您就可以每行使用一次,而没有不必要的缩进。
NiKiZe

3
@NiKiZe您可以将整个程序放在一行上,并消除所有缩进。您甚至可以将XYZ用作变量名!对他人有什么好处?
德里克·约翰逊

@DerekJohnson我要提出的观点可能是“只需要一个using指令”。将所有内容读入内存并不是一个很好的理由。更为有效的方法是将数据流式传输到中ComputeHash,如果可能的话using,仅应使用,但是我完全理解是否要避免额外的缩进级别。
NiKiZe

3

我知道我迟到了,但是在实际实施解决方案之前进行了测试。

我确实针对内置的MD5类以及md5sum.exe进行了测试。在我的情况下,内置类花费了13秒,而md5sum.exe在每次运行中也花费了大约16-18秒。

    DateTime current = DateTime.Now;
    string file = @"C:\text.iso";//It's 2.5 Gb file
    string output;
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(file))
        {
            byte[] checksum = md5.ComputeHash(stream);
            output = BitConverter.ToString(checksum).Replace("-", String.Empty).ToLower();
            Console.WriteLine("Total seconds : " + (DateTime.Now - current).TotalSeconds.ToString() + " " + output);
        }
    }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.