2
  1. 是否可以使用 FileReader API 和 onprogress 事件访问 HTML5 中的数据?

  2. 如果是这样,是否有 MD5 或其他快速散列算法的“在线”版本,以便我可以在文件被完全读取之前开始计算散列?

我想在客户端计算哈希并在发送整个文件之前仅将哈希发送到服务器以在启动文件上传之前检查重复项。

目前我不关心对旧浏览器的支持。

编辑:我认识到哈希冲突并不能保证文件重复,唯一可以确定的方法是逐字节检查,这意味着无论如何我都必须上传文件。可能性很低,我愿意承担这个风险;最坏的情况我提示用户并说“这个文件似乎已经在服务器上;你确定要上传它吗?”

4

2 回答 2

2

是否有 MD5 或其他快速散列算法的“在线”版本,以便我可以在文件被完全读取之前开始计算散列?

是的,如果你想使用 SHA ,你可以使用sjcl 。sjcl 没有对 MD5 的本机支持,因此您必须自己编写(尽管我确信其他人已经完成了)。CryptoJS具有原生 MD5 支持,但速度明显较慢。

我认识到哈希冲突并不能保证重复文件[...]概率足够低,我愿意承担这个风险;

与自然发生碰撞相比,流星撞击地球并结束人类生命的可能性足够低(因此完全不需要散列)。当然,除非用户故意制造碰撞,因为 MD5 的抗碰撞性被破坏了。

这是我认为您正在尝试完成的工作的现场演示,减去“访问数据”部分。我不确定这是否可能。我很久以前写过这个,它使用 CryptoJS,所以性能不是很好,但它完成了工作。重要的块是:

function handleFileSelect(evt) 
{
    evt.stopPropagation();
    evt.preventDefault();

    var files = evt.target.files || evt.dataTransfer.files; // FileList object.

    for (var i=0, file; file = files[i]; ++i)
    {
        // this creates the FileReader and reads stuff as text
        var fr = new FileReader();

        fr.onload = (function(theFile) {
            return function (e) {
                var hashes = parsePseudoBuffer(e.target.result);

                document.getElementById('output').innerHTML += '<br />' + theFile.name + '<br />' 
                + 'MD5: ' + hashes.md5 + '<br />' + 'SHA1: ' + hashes.sha1 + '<br />' ;

            };
        }) (file);

        fr.readAsArrayBuffer(file); // ArrayBuffer
    }

}

function parsePseudoBuffer(result)
{

    var buffs = new Uint8Array(result); // buffer thingie       
    var md5 = CryptoJS.algo.MD5.create();
    var sha1 = CryptoJS.algo.SHA1.create();     
    var bufsize = 8 * 1024; // 8K buffer

    for (var bstart=0, bend=bufsize; bstart < buffs.length; bstart+=bufsize, bend+= bufsize)
    {
        var data = CryptoJS.lib.WordArray.create(buffs.subarray(bstart, bend)); 
        md5.update(data);
        sha1.update(data);          
    }

    md5 = md5.finalize(); 
    sha1 = sha1.finalize();         

    return {'md5': md5, 'sha1': sha1} ;

}
于 2013-09-19T03:44:13.880 回答
2

我做了一些实验。看起来我们可以onprogress通过利用resultreader 对象上的不完整来获取事件中读取的最后一个块。只有当我们使用reader.readAsArrayBuffer(Chrome only?) 或reader.readAsBinaryString. 字符串的问题在于,如果您想获取其中的一部分,则必须对其进行切片,从而生成副本(非常慢)。

ArrayBuffers 有一种.subarray方法可以在缓冲区中创建视图,而无需复制任何数据。这正是我们想要的。但是,它似乎在基类上不可用;并且从文档中不清楚当我们Uint8Array使用此缓冲区构造派生类(例如)时会发生什么,但考虑到原始缓冲区可通过只读属性访问,我假设它不是复制。

sjcl 和 CryptoJS 都有很方便的.update方法来接收这个 ArrayBufferView,这样你就可以即时更新你的哈希值。因此,我提出了以下解决方案(使用 jQuery、下划线和 sjcl):

$(document).on('drop', function(dropEvent) {
    dropEvent.preventDefault();

    _.each(dropEvent.originalEvent.dataTransfer.files, function(file) {
        var reader = new FileReader();
        var pos = 0;

        var hash = new sjcl.hash.sha256();

        reader.onprogress = function(progress) {
            var chunk = new Uint8Array(reader.result, pos, progress.loaded - pos);
            pos = progress.loaded;
            hash.update(chunk);
            if(progress.lengthComputable) {
                console.log((progress.loaded/progress.total*100).toFixed(1)+'%');
            }
        };

        reader.onload = function() {
            var chunk = new Uint8Array(reader.result, pos);
            if(chunk.length > 0) hash.update(chunk);
            console.log(sjcl.codec.hex.fromBits(hash.finalize()));
        };

        reader.readAsArrayBuffer(file);
    });
});

请注意,此解决方案目前仅适用于 Chrome,而且速度相当慢。我认为 sjcl 不仅仅是对文件进行哈希处理,而是对其进行密钥强化,这真的不是我想要的。稍后将进行更多调查。

于 2013-09-19T06:45:45.613 回答