使用部分文件生成JavaScript文件哈希值

Don*_*Den 3 javascript php hash md5 cryptojs

我正在使用JavaScript来生成唯一文件值的文件哈希值。请检查下面的代码,以了解效果良好的哈希生成机制。

<script type="text/javascript">
// Reference: https://code.google.com/p/crypto-js/#MD5
function handleFileSelect(evt) 
{   
    var files = evt.target.files; // FileList object
    // Loop through the FileList and render image files as thumbnails.
    for (var i = 0, f; f = files[i]; i++) 
    {
        var reader = new FileReader();
        // Closure to capture the file information.
        reader.onload = (function(theFile) 
        {
            return function(e) 
            {
                var span = document.createElement('span');
                var test = e.target.result;                 
                //var hash = hex_md5(test);
                var hash = CryptoJS.MD5(test);
                var elem = document.getElementById("hashValue");
                elem.value = hash;
            };
        })(f);
        // Read in the image file as a data URL.
        reader.readAsBinaryString(f);
    }
}
document.getElementById('videoupload').addEventListener('change', handleFileSelect, false);
</script>
Run Code Online (Sandbox Code Playgroud)

但是,当在客户端浏览器崩溃时,为大型文件生成哈希值时,我遇到了问题。

最多30MB的HASHING效果很好,但如果我尝试上传的大小超过该值,则系统崩溃。

我的问题是:

  1. 我可以为文件的一部分生成哈希值,而不是读取大文件并崩溃吗?如果是的话,我能知道该宽度为'FileReader'吗?

  2. 我可以指定文件的任何字节数(例如2000个字符)以生成HASH值,然后为大型文件生成。

我希望以上两种解决方案适用于较大和较小的文件。还有其他选择吗?

我的小提琴演示

Art*_* B. 5

  1. 我可以为文件的一部分生成哈希值,而不是读取大文件并崩溃吗?如果是的话,我能知道该宽度为'FileReader'吗?

是的,您可以做到这一点,这称为渐进式哈希

var md5 = CryptoJS.algo.MD5.create();

md5.update("file part 1");
md5.update("file part 2");
md5.update("file part 3");

var hash = md5.finalize();
Run Code Online (Sandbox Code Playgroud)
  1. 我可以指定文件的任何字节数(例如2000个字符)以生成HASH值,然后为大型文件生成。

有一篇HTML5Rocks文章,内容涉及如何File.slice将切片的文件传递给FileReader

var blob = file.slice(startingByte, endindByte);
reader.readAsArrayBuffer(blob);
Run Code Online (Sandbox Code Playgroud)

完整解决方案

我结合了两者。棘手的部分是同步文件读取,因为它FileReader.readAsArrayBuffer()是异步的。我编写了一个小series函数,该series函数以async.js函数为模型。它必须一个接一个地完成,因为无法获得CryptoJS哈希函数的内部状态。

另外,CryptoJS不了解什么ArrayBuffer是什么,因此必须将其转换为其本机数据表示形式,即所谓的WordArray:

function arrayBufferToWordArray(ab) {
  var i8a = new Uint8Array(ab);
  var a = [];
  for (var i = 0; i < i8a.length; i += 4) {
    a.push(i8a[i] << 24 | i8a[i + 1] << 16 | i8a[i + 2] << 8 | i8a[i + 3]);
  }
  return CryptoJS.lib.WordArray.create(a, i8a.length);
}
Run Code Online (Sandbox Code Playgroud)

另一件事是散列是一个同步操作,没有yield其他地方可以继续执行。因此,由于JavaScript是单线程的,因此浏览器将冻结。解决方案是使用Web Workers将散列卸载到另一个线程,以便UI线程保持响应。
Web工作者期望脚本文件在其构造函数中,因此我使用Rob W的此解决方案来创建内联脚本。

function series(tasks, done){
    if(!tasks || tasks.length === 0) {
        done();
    } else {
        tasks[0](function(){
            series(tasks.slice(1), done);
        });
    }
}

function webWorkerOnMessage(e){
    if (e.data.type === "create") {
        md5 = CryptoJS.algo.MD5.create();
        postMessage({type: "create"});
    } else if (e.data.type === "update") {
        function arrayBufferToWordArray(ab) {
            var i8a = new Uint8Array(ab);
            var a = [];
            for (var i = 0; i < i8a.length; i += 4) {
                a.push(i8a[i] << 24 | i8a[i + 1] << 16 | i8a[i + 2] << 8 | i8a[i + 3]);
            }
            return CryptoJS.lib.WordArray.create(a, i8a.length);
        }
        md5.update(arrayBufferToWordArray(e.data.chunk));
        postMessage({type: "update"});
    } else if (e.data.type === "finish") {
        postMessage({type: "finish", hash: ""+md5.finalize()});
    }
}

// URL.createObjectURL
window.URL = window.URL || window.webkitURL;

// "Server response", used in all examples
var response = 
    "importScripts('https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/md5.js');"+
    "var md5;"+
    "self.onmessage = "+webWorkerOnMessage.toString();

var blob;
try {
    blob = new Blob([response], {type: 'application/javascript'});
} catch (e) { // Backwards-compatibility
    window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
    blob = new BlobBuilder();
    blob.append(response);
    blob = blob.getBlob();
}
var worker = new Worker(URL.createObjectURL(blob));


var files = evt.target.files; // FileList object    
var chunksize = 1000000; // the chunk size doesn't make a difference
var i = 0, 
    f = files[i],
    chunks = Math.ceil(f.size / chunksize),
    chunkTasks = [],
    startTime = (new Date()).getTime();
worker.onmessage = function(e) {
    // create callback

    for(var j = 0; j < chunks; j++){
        (function(j, f){
            chunkTasks.push(function(next){
                var blob = f.slice(j * chunksize, Math.min((j+1) * chunksize, f.size));
                var reader = new FileReader();

                reader.onload = function(e) {
                    var chunk = e.target.result;
                    worker.onmessage = function(e) {
                        // update callback
                        document.getElementById('num').innerHTML = ""+(j+1)+"/"+chunks;
                        next();
                    };
                    worker.postMessage({type: "update", chunk: chunk});
                };
                reader.readAsArrayBuffer(blob);
            });
        })(j, f);
    }
    series(chunkTasks, function(){
        var elem = document.getElementById("hashValueSplit");
        var telem = document.getElementById("time");
        worker.onmessage = function(e) {
            // finish callback
            elem.value = e.data.hash;
            telem.innerHTML = "in " + Math.ceil(((new Date()).getTime() - startTime) / 1000) + " seconds";
        };
        worker.postMessage({type: "finish"});
    });

    // blocking way ahead...
    if (document.getElementById("singleHash").checked) {
        var reader = new FileReader();

        // Closure to capture the file information.
        reader.onloadend = (function(theFile) {
            function arrayBufferToWordArray(ab) {
                var i8a = new Uint8Array(ab);
                var a = [];
                for (var i = 0; i < i8a.length; i += 4) {
                    a.push(i8a[i] << 24 | i8a[i + 1] << 16 | i8a[i + 2] << 8 | i8a[i + 3]);
                }
                return CryptoJS.lib.WordArray.create(a, i8a.length);
            }
            return function(e) {
                var test = e.target.result;
                var hash = CryptoJS.MD5(arrayBufferToWordArray(test));
                //var hash = "none";
                var elem = document.getElementById("hashValue");
                elem.value = hash;
            };
        })(f);

        // Read in the image file as a data URL.
        reader.readAsArrayBuffer(f);
    }
};
worker.postMessage({type: "create"});
Run Code Online (Sandbox Code Playgroud)

DEMO似乎适用于大文件,但需要花费大量时间。也许可以使用更快的MD5实现来改善这一点。散列3 GB的文件大约需要23分钟。

我的答案显示了一个没有网络工作者使用SHA-256的示例。