如何使用Java或Groovy计算目录的MD5校验和?

6

我希望使用Java或Groovy获取完整目录的MD5校验和。

我需要将源目录复制到目标目录,对源目录和目标目录进行校验,然后删除源目录。

我找到了适用于文件的脚本,但如何处理目录?

import java.security.MessageDigest

def generateMD5(final file) {
    MessageDigest digest = MessageDigest.getInstance("MD5")
    file.withInputStream(){ is ->
        byte[] buffer = new byte[8192]
        int read = 0
        while( (read = is.read(buffer)) > 0) {
            digest.update(buffer, 0, read);
        }
    }
    byte[] md5sum = digest.digest()
    BigInteger bigInt = new BigInteger(1, md5sum)

    return bigInt.toString(16).padLeft(32, '0')
}

有更好的方法吗?

你应该优先使用org.apache.commons.codec.digest.DigestUtils.md5Hex方法之一,而不是上面的代码。 - Dónal
我觉得FastMD5非常容易获取文件的MD5值:String hash = MD5.asHex(MD5.getHash(new File(filename))); 更易于使用并且更快速。 - Fabien Barbier
7个回答

9

我有同样的需求,并选择将“目录哈希”设置为目录中所有非目录文件串联流的MD5哈希。正如crozin在一个类似的问题的评论中提到的那样,您可以使用SequenceInputStream作为连接其他流的流。我正在使用Apache Commons Codec进行MD5算法。

基本上,您要通过目录树进行递归,将非目录文件的FileInputStream实例添加到Vector中。 Vector然后方便地具有elements()方法来提供Enumeration,这是SequenceInputStream需要循环的方法。对于MD5算法,这只是一个InputStream

需要注意的是,您需要按相同的顺序每次呈现文件,以使哈希在具有相同输入的情况下相同。 File中的listFiles()方法不保证排序方式,因此我按文件名排序。

我正在针对受SVN控制的文件进行此操作,并希望避免对隐藏的SVN文件进行哈希处理,因此我实现了一个标记以避免隐藏文件。

相关的基本代码如下所示。(显然,它可以“加固”。)

import org.apache.commons.codec.digest.DigestUtils;

import java.io.*;
import java.util.*;

public String calcMD5HashForDir(File dirToHash, boolean includeHiddenFiles) {

    assert (dirToHash.isDirectory());
    Vector<FileInputStream> fileStreams = new Vector<FileInputStream>();

    System.out.println("Found files for hashing:");
    collectInputStreams(dirToHash, fileStreams, includeHiddenFiles);

    SequenceInputStream seqStream = 
            new SequenceInputStream(fileStreams.elements());

    try {
        String md5Hash = DigestUtils.md5Hex(seqStream);
        seqStream.close();
        return md5Hash;
    }
    catch (IOException e) {
        throw new RuntimeException("Error reading files to hash in "
                                   + dirToHash.getAbsolutePath(), e);
    }

}

private void collectInputStreams(File dir,
                                 List<FileInputStream> foundStreams,
                                 boolean includeHiddenFiles) {

    File[] fileList = dir.listFiles();        
    Arrays.sort(fileList,               // Need in reproducible order
                new Comparator<File>() {
                    public int compare(File f1, File f2) {                       
                        return f1.getName().compareTo(f2.getName());
                    }
                });

    for (File f : fileList) {
        if (!includeHiddenFiles && f.getName().startsWith(".")) {
            // Skip it
        }
        else if (f.isDirectory()) {
            collectInputStreams(f, foundStreams, includeHiddenFiles);
        }
        else {
            try {
                System.out.println("\t" + f.getAbsolutePath());
                foundStreams.add(new FileInputStream(f));
            }
            catch (FileNotFoundException e) {
                throw new AssertionError(e.getMessage()
                            + ": file should never not be found!");
            }
        }
    }

}

1
好的答案 - 我决定使用你的代码。不过需要注意的是,它并不具备可移植性 - 检查隐藏文件应该基于 "isHidden" 而不是文件名。在某些情况下,最好同时检查两者,因为一些程序(例如 Eclipse)会将以点开头的工作文件(例如 .classpath)保存在非 Unix 操作系统上而不将它们隐藏。 - Omri Spector
@OmriSpector 是的,关于不可移植性的观点很好,我很高兴你发现了这个片段的用处。那一部分是快速而粗糙的代码;我确实说过“显然它可以被‘加固’” :-) - Stuart Rossiter
答案很棒,但如果文件名更改并保留字母顺序,它就会失效,因此我们可以使用文件的绝对路径再进行一次哈希。 - best wishes

5

我编写了一个函数,用于计算目录的MD5校验和:

首先,我使用FastMD5:http://www.twmacinta.com/myjava/fast_md5.php

以下是我的代码:

  def MD5HashDirectory(String fileDir) {
    MD5 md5 = new MD5();
    new File(fileDir).eachFileRecurse{ file ->
      if (file.isFile()) {
        String hashFile = MD5.asHex(MD5.getHash(new File(file.path)));
        md5.Update(hashFile, null);
      }

    }
    String hashFolder = md5.asHex();
    return hashFolder
  }

使用Groovy(可能也适用于Java),使用Ant(或更好的Gant)可能很有用。请参见:http://ant.apache.org/manual/dirtasks.html - Fabien Barbier
4
实际上,这是对文件内容的哈希进行哈希处理,而不仅仅是对其进行单纯的哈希处理。 - Justin Piper

4

基于Stuart Rossiter的回答,但代码更简洁,并且隐藏文件得到了正确处理:

import org.apache.commons.codec.digest.DigestUtils;

import java.io.*;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Vector;

public class Hashing {
    public static String hashDirectory(String directoryPath, boolean includeHiddenFiles) throws IOException {
        File directory = new File(directoryPath);
        
        if (!directory.isDirectory()) {
            throw new IllegalArgumentException("Not a directory");
        }

        Vector<FileInputStream> fileStreams = new Vector<>();
        collectFiles(directory, fileStreams, includeHiddenFiles);

        try (SequenceInputStream sequenceInputStream = new SequenceInputStream(fileStreams.elements())) {
            return DigestUtils.md5Hex(sequenceInputStream);
        }
    }

    private static void collectFiles(File directory, List<FileInputStream> fileInputStreams,
                                     boolean includeHiddenFiles) throws IOException {
        File[] files = directory.listFiles();

        if (files != null) {
            Arrays.sort(files, Comparator.comparing(File::getName));

            for (File file : files) {
                if (includeHiddenFiles || !Files.isHidden(file.toPath())) {
                    if (file.isDirectory()) {
                        collectFiles(file, fileInputStreams, includeHiddenFiles);
                    } else {
                        fileInputStreams.add(new FileInputStream(file));
                    }
                }
            }
        }
    }
}

3

HashCopy是一个Java应用程序。它可以在单个文件或递归的目录上生成和验证MD5和SHA。我不确定它是否有API。它可以从www.jdxsoftware.org下载。


2
不错的应用程序,但如果要将HashCopy用于商业目的,则需要许可证。 - Fabien Barbier

2
如果您需要在Gradle构建文件中执行此操作,与纯Groovy相比,这要简单得多。
以下是一个例子:
def sources = fileTree('rootDir').matching {
    include 'src/*', 'build.gradle'
}.sort { it.name }
def digest = MessageDigest.getInstance('SHA-1')
sources.each { digest.update(it.bytes) }
digest.digest().encodeHex().toString()

MessageDigest是Java标准库中的一个类:https://docs.oracle.com/javase/8/docs/api/java/security/MessageDigest.html

所有JVM支持的算法包括:

MD5
SHA-1
SHA-256

1

对于一个目录来说,计算md5sum的含义并不清晰。你可能想要文件列表的校验和;你可能想要文件列表及其内容的校验和。如果你已经在计算文件数据本身的校验和,我建议你为目录列表指定一个明确的表示方式(注意文件名中的恶意字符),然后每次计算并哈希它。你还需要考虑如何处理特殊文件(在Unix世界中是套接字、管道、设备和符号链接;NTFS有文件流和类似符号链接的东西)。


1
我计算的是sha512而不是md5(因为它更安全),但是你可以在gradle文件或原始groovy中定义它。
import java.security.MessageDigest
import java.io.File

def calcDirHash(fileDir) {
  def hash = MessageDigest.getInstance("SHA-512")
  new File(fileDir).eachFileRecurse{ file ->
    if (file.isFile()) {
      file.eachByte 4096, {bytes, size ->
        hash.update(bytes, 0, size);
      }
    }
  }
  return hash.digest().encodeHex()
}

在任何任务中调用calcDirHash(并传入要哈希的目录)。您可以使用其他编码方案,而不是SHA-512。

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