在Java中获取文件的MD5校验和

570

我想使用Java获取文件的MD5校验和。但是我很惊讶,我找不到任何展示如何获取文件的MD5校验和的资料。

请问该怎么做?


1
也许这个链接会有所帮助。你也可以查阅规范,但因为它很复杂,可能需要更多的工作。 - waynecolvin
4
请记住,根据最近的研究,“MD5 应被视为密码学上已被破解且不适合继续使用的算法”。http://en.wikipedia.org/wiki/MD5 - Zakharia Stanley
98
MD5现在不再被认为是安全的密码学算法,但它仍足以用于验证文件完整性,并且比SHA更快。 - jiggy
4
这是关于校验和的问题。 - iPherian
2
MD5校验和在文件中的规范用途是为了避免分发文件被恶意替换。这就是它不安全的地方。但在没有恶意攻击的情况下,它是完全合适的。 - Keith Tyler
22个回答

611

有一个输入流装饰器java.security.DigestInputStream,可以在正常使用输入流的同时计算摘要,而无需对数据进行额外的读取。

MessageDigest md = MessageDigest.getInstance("MD5");
try (InputStream is = Files.newInputStream(Paths.get("file.txt"));
     DigestInputStream dis = new DigestInputStream(is, md)) 
{
  /* Read decorated stream (dis) to EOF as normal... */
}
byte[] digest = md.digest();

5
我同意,如果你已经在处理字节(例如从HTTP连接中读取字节),那么按照这种方法动态计算校验和是非常优雅的。 - Marc Novakowski
3
@AlPhaba,你将is声明为了InputStream还是FileInputStream?听起来像是你使用了FileInputStream,导致了这个错误。 - erickson
4
@barwnikk,这是您本地配置的问题。这是有效的Java 7和Java 8代码。如果您使用的是2006年的工具且遇到困难,您需要进行适应。 - erickson
5
你没有使用文件内容更新 MessageDigest 对象,对吗?这段代码会始终打印相同的摘要。 - sunil
2
@WayneWei 不是的。MD5哈希是一个常量空间算法。它仅保留16字节的状态进行计算。实现可能在内部使用64字节块缓冲区。 - erickson
显示剩余21条评论

340

使用Apache Commons Codec库中的DigestUtils

try (InputStream is = Files.newInputStream(Paths.get("file.zip"))) {
    String md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(is);
}

1
在我的Android代码中,这对我不起作用,我得到了这个错误...java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString at org.apache.commons.codec.digest.DigestUtils.md5Hex(DigestUtils.java:215) - JPM
1
我曾经遇到过同样的问题,但是通过以下代码解决了它:FileInputStream fis = new FileInputStream(new File(filePath)); byte data[] = org.apache.commons.codec.digest.DigestUtils.md5(fis); char md5Chars[] = Hex.encodeHex(data); String md5 = String.valueOf(md5Chars); - Dmitry_L
3
好的!对于新项目,我总是会仔细考虑是否需要添加新的依赖项,但对于现有项目,我只需检查库是否已经存在以便于使用。+1 - OscarRyz
非常好!在MATLAB中也可以使用...只需读取文件“fis”,并使用“md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(fis);” - Tom Anderson
对我来说可以工作,但似乎比被接受的答案慢一点。 - Thomas P
显示剩余3条评论

176

Real's Java-How-to网站上有一个例子,使用MessageDigest类。

请查看该页面以获取使用CRC32和SHA-1的示例。

import java.io.*;
import java.security.MessageDigest;

public class MD5Checksum {

   public static byte[] createChecksum(String filename) throws Exception {
       InputStream fis =  new FileInputStream(filename);

       byte[] buffer = new byte[1024];
       MessageDigest complete = MessageDigest.getInstance("MD5");
       int numRead;

       do {
           numRead = fis.read(buffer);
           if (numRead > 0) {
               complete.update(buffer, 0, numRead);
           }
       } while (numRead != -1);

       fis.close();
       return complete.digest();
   }

   // see this How-to for a faster way to convert
   // a byte array to a HEX string
   public static String getMD5Checksum(String filename) throws Exception {
       byte[] b = createChecksum(filename);
       String result = "";

       for (int i=0; i < b.length; i++) {
           result += Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
       }
       return result;
   }

   public static void main(String args[]) {
       try {
           System.out.println(getMD5Checksum("apache-tomcat-5.5.17.exe"));
           // output :
           //  0bb2827c5eacf570b6064e24e0e6653b
           // ref :
           //  http://www.apache.org/dist/
           //          tomcat/tomcat-5/v5.5.17/bin
           //              /apache-tomcat-5.5.17.exe.MD5
           //  0bb2827c5eacf570b6064e24e0e6653b *apache-tomcat-5.5.17.exe
       }
       catch (Exception e) {
           e.printStackTrace();
       }
   }
}

75
是的... 11年后仍然在线! :-) - RealHowTo
Real's Java-How-To的示例运行完美,实现起来也很简单。 - bakoyaro
10
谢谢您的及时反馈。 - Bill the Lizard
byte[] buffer = new byte[1024]; 我们可以将大小从1024更改为更优化的值吗? - Jalpesh
为什么要使用文件名而不是 Blob?如果您不基于 Blob 计算校验和,如何确保图像相同? - Dimitri Kopriwa
显示剩余4条评论

93

com.google.common.hash API 提供:

  • 统一易用的散列函数 API
  • 可设置种子的 murmur3 的 32 位和 128 位实现
  • 适配器 md5()、sha1()、sha256()、sha512(),只需更改一行代码即可在它们之间切换,还可以选择使用 murmur。
  • goodFastHash(int bits) 函数,用于不关心采用哪种算法的场景
  • HashCode 实例的通用工具,例如 combineOrdered / combineUnordered

阅读用户指南(IO ExplainedHashing Explained)。

对于您的用例,Files.hash() 可以计算并返回文件的摘要值。

例如,进行 摘要计算(将 SHA-1 更改为 MD5 即可获取 MD5 摘要)。

HashCode hc = Files.asByteSource(file).hash(Hashing.sha1());
"SHA-1: " + hc.toString();
请注意,快得多,因此如果您不需要加密安全校验和,请使用。请注意,不应用于存储密码等敏感信息,因为很容易被暴力破解,对于密码,请使用。对于哈希的长期保护,默克尔签名方案可以增强安全性。欧盟委员会赞助的后量子密码研究小组建议使用这种密码技术来长期保护免受量子计算机的攻击(参考)。请注意,的碰撞率比其他算法高。

上述的 Files.hash 的哪一部分不包含 Files.hash? - oluies
2
Files.hash() 已标记为过时的方法,推荐使用:Files.asByteSource(file).hash(Hashing.sha1()) - erkfel
1
截至2018年1月,Hashing.sha1()已被标记为过时。建议使用Hashing.sha256()函数代替。来源 - MagicLegend

84

使用nio2(Java 7+)和无外部库:

byte[] b = Files.readAllBytes(Paths.get("/path/to/file"));
byte[] hash = MessageDigest.getInstance("MD5").digest(b);

为了将结果与预期校验和进行比较:

String expected = "2252290BC44BEAD16AA1BF89948472E8";
String actual = DatatypeConverter.printHexBinary(hash);
System.out.println(expected.equalsIgnoreCase(actual) ? "MATCH" : "NO MATCH");

@Arash 是的,绝对没错 - 谢谢。我把JDK的Files类和Guava的搞混了。 - assylias
我更喜欢这个解决方案,因为它可以使用可选项进行包装,以使用纯函数式编程风格。 - Gabriel Hernandez
14
对于一个大文件,如果整个文件被读取并输入给摘要算法,这将消耗大量内存,而不是读取数据块并在读取时进行加密。 - bernie

45

Guava 现在提供了一个新的、一致的哈希 API,比 JDK 提供的各种哈希 API 更加用户友好。请参阅 哈希解释。对于文件,你可以轻松获取 MD5 摘要、CRC32(使用14.0+版本)或其他许多哈希值:

HashCode md5 = Files.hash(file, Hashing.md5());
byte[] md5Bytes = md5.asBytes();
String md5Hex = md5.toString();

HashCode crc32 = Files.hash(file, Hashing.crc32());
int crc32Int = crc32.asInt();

// the Checksum API returns a long, but it's padded with 0s for 32-bit CRC
// this is the value you would get if using that API directly
long checksumResult = crc32.padToLong();

1
Files.hash已被弃用,请使用Files.asByteSource(new File("")).hash(Hashing.md5()) - RedShift

34

好的。我必须补充一点。对于那些已经具有Spring和Apache Commons依赖项或计划添加它们的人来说,以下是一行实现:

Ok. 我需要补充一句话。对于那些已经有Spring和Apache Commons依赖项或者打算添加这些依赖项的人来说,可以使用以下一行代码实现:

DigestUtils.md5DigestAsHex(FileUtils.readFileToByteArray(file))

只针对Apache commons的选项(感谢@duleshi):

DigestUtils.md5Hex(FileUtils.readFileToByteArray(file))

希望这能帮助到某个人。


1
它是DigestUtils.md5Hex(FileUtils.readFileToByteArray(file)) - duleshi
David Onter的基于公共库的解决方案更好,因为它不会将整个文件读入内存。 - Fran Marzoa
至少在 Spring 5 中,您可以使用 DigestUtils.md5Digest(InputStream inputStream) 计算 MD5 摘要,以及使用 DigestUtils.md5DigestAsHex(InputStream inputStream) 获取十六进制字符串表示的 MD5 摘要方法,而无需将整个文件读入内存。 - Mike Shauneu

27

使用Java 7,不依赖于第三方库的简单方法

String path = "your complete file path";
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(Files.readAllBytes(Paths.get(path)));
byte[] digest = md.digest();
如果您需要打印此字节数组,请按如下方式使用。
System.out.println(Arrays.toString(digest));
如果您需要从此摘要中获取十六进制字符串,请按以下方式使用。
String digestInHex = DatatypeConverter.printHexBinary(digest).toUpperCase();
System.out.println(digestInHex);

其中DatatypeConverter是javax.xml.bind.DatatypeConverter


为什么使用 toUpperCase - EdgeCaseBerg
@edgecaseberg 只是为了让十六进制字符串在打印到控制台时看起来更好。 - sunil
我发现我需要使用 toLowerCase() 而不是 toUpperCase()。 - Splendor

14

最近我不得不为一个动态字符串做这个,MessageDigest可以用多种方式表示哈希值。要像使用md5sum命令一样获得文件的签名,我必须像这样做:

try {
   String s = "TEST STRING";
   MessageDigest md5 = MessageDigest.getInstance("MD5");
   md5.update(s.getBytes(),0,s.length());
   String signature = new BigInteger(1,md5.digest()).toString(16);
   System.out.println("Signature: "+signature);

} catch (final NoSuchAlgorithmException e) {
   e.printStackTrace();
}

显然,这并没有回答你如何针对文件具体实现的问题,上面的答案已经很好地解决了这个问题。我刚刚花费了很多时间让总和看起来像大多数应用程序的显示方式,并且我想你可能会遇到同样的麻烦。


签名是十六进制格式的摘要。我也发现十六进制表示法可以奏效,正如你所说,其他表示法无法奏效。感谢您的分享。 - amit kumar
1
这很好,但是.toString(16)会丢弃前导零。String.format("%032x", ...)可能更好。 - Harold

14
public static void main(String[] args) throws Exception {
    MessageDigest md = MessageDigest.getInstance("MD5");
    FileInputStream fis = new FileInputStream("c:\\apache\\cxf.jar");

    byte[] dataBytes = new byte[1024];

    int nread = 0;
    while ((nread = fis.read(dataBytes)) != -1) {
        md.update(dataBytes, 0, nread);
    };
    byte[] mdbytes = md.digest();
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < mdbytes.length; i++) {
        sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
    }
    System.out.println("Digest(in hex format):: " + sb.toString());
}

或者你可以获得更多信息 http://www.asjava.com/core-java/java-md5-example/


这个答案很好,我在使用这个答案之前花了12个小时才达到我想要的结果。非常感谢!:) - Bhavin Patel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接