【问题标题】:Online/streaming MD5 algorithm?在线/流媒体 MD5 算法?
【发布时间】:2013-09-24 00:07:28
【问题描述】:
  1. 是否可以使用 FileReader API 和 onprogress 事件访问 HTML5 中的数据?

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

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

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

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

【问题讨论】:

    标签: javascript html hash


    【解决方案1】:

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

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

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

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

    这是我认为您正在尝试完成的直播demo,减去“访问数据”部分。我不确定这是否可能。我很久以前写过这个,它使用 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} ;
    
    }
    

    【讨论】:

    • "这就是 FileReader API 的目的。" -- FileReader API 允许您在客户端读取文件,是的,但是我找不到 API 中可以访问要读取的最后一块字节的位置。 onprogress 事件几乎是 undocumented。当然,我可以不使用它,我只是认为对于大型(千兆字节)文件,它会更快地读取它,因为它不需要将整个文件加载到内存中。
    • W3 列出了它的属性,其中没有一个包含实际读取的字节。无论如何,我很欣赏你的榜样。很有帮助!
    • @Mark 对于大文件,JavaScript 本身的速度成为一个重要因素。我建议使用 JS 散列大文件。我认为您可以使用 FileReader 来读取文件而不是读取整个文件,但是是的 - 缺少文档,所以我不确定。
    • @Mark 但是如果你找到一种方法来读取/散列文件,请告诉我。
    • 我做了一些实验,看起来we can 获得了最后读取的块,至少在最新版本的Firefox 和Chrome 中,如果我们使用readAsBinaryString(不适用于@987654330 @,可能是因为它需要先阅读整个内容,然后才能对其进行 base64 编码,而不是什么)。我知道是否获得最后一块的唯一方法是将其切片;这似乎很慢,因为我们正在制作它的副本。如果我将它用于算法,可能没有必要。
    【解决方案2】:

    我做了一些实验。看起来我们可以通过在阅读器对象上利用不完整的result 来获取onprogress 事件中读取的最后一个块。如果我们使用reader.readAsArrayBuffer(仅限Chrome?)或reader.readAsBinaryString,它似乎只能访问。字符串的问题是,如果你想取一大块,你必须对它进行切片,这会产生一个副本(非常慢)。

    ArrayBuffers 有一个.subarray 方法,它在缓冲区中创建一个view,而不复制任何数据。这正是我们想要的。但是,它似乎在基类上不可用;并且从文档中不清楚当我们使用此缓冲区构造派生类(例如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 不仅仅是对文件进行哈希处理,而是对其进行密钥强化,这真的不是我想要的。稍后将进行更多调查。

    【讨论】:

    • 仅供参考,我认为您不再需要使用.subarray。默认情况下,类型化数组对象的构造函数会在 ArrayBuffer 中创建一个视图,并且可以接受一些偏移参数。
    猜你喜欢
    • 2014-02-05
    • 2011-10-20
    • 2017-06-18
    • 2014-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-15
    • 1970-01-01
    相关资源
    最近更新 更多