【问题标题】:Create SHA-256 hash from a Blob/File in javascript在 JavaScript 中从 Blob/文件创建 SHA-256 哈希
【发布时间】:2014-03-12 18:10:18
【问题描述】:

我需要从浏览器内的文件 (~6MB) 创建一个 SHA-256 摘要。到目前为止,我设法做到的唯一方法是这样的:

var reader = new FileReader();
reader.onload = function() {
    // this gets read of the mime-type data header
    var actual_contents = reader.result.slice(reader.result.indexOf(',') + 1);
    var what_i_need = new jsSHA(actual_contents, "B64").getHash("SHA-256", "HEX");
}
reader.readAsDataURL(some_file);

虽然这可以正常工作,但问题是它非常慢。一个 6MB 的文件大约需要 2-3 秒。我该如何改进?

【问题讨论】:

  • 我不太熟悉 SHA256 算法,但如果你将文件转换为 ArrayBuffer 然后转换为 Uint8Array 这可能比通过 Base64 更快,您可以直接使用 bytes
  • 你能给我举个例子吗?
  • var fr = new FileReader; fr.onload = function () {var hash = byteArrayToSHA256(new Uint8Array(this.result));} fr.readAsArrayBuffer(some_file); 其中byteArrayToSHA256 是散列算法。这意味着您无需担心 utf-encodings/encoding-decoding base64/strings 等。您可能需要为此输入重新编写算法。
  • 我会在 base64 数据上运行 atob(),这样 scha 脚本就不必解码,它可能使用用户级 base64 解码器,它可能比它慢数百倍atob()...
  • 有人可以对所有答案进行基准测试并发布最快的结果吗?

标签: javascript html performance hash sha


【解决方案1】:

您可能想看看斯坦福 JS 加密库

GitHub

Website with Examples

来自网站:

SJCL 是安全的。它使用 128、192 或 256 位的行业标准 AES 算法; SHA256 哈希函数; HMAC认证码; PBKDF2 密码增强器;以及 CCM 和 OCB 认证加密模式。

SJCL 有一个测试页面,显示需要多长时间。

SHA256 迭代需要 184 毫秒。来自 catameringue 的 SHA-256 需要 50 毫秒。

测试page

示例代码:

加密数据: sjcl.encrypt("password", "data")

解密数据:sjcl.decrypt("password", "encrypted-data")

【讨论】:

  • 一项快速测试表明 sjcl 比 CryptoJS 快约 10 倍(用于计算字符串“1234567890”的 SHA-256 哈希)。虽然它似乎不支持 File / Blob / ArrayBuffer 散列,但这让我回到了第一方。根据我的发现,CryptoJS 是唯一一个支持直接对 ArrayBuffer 进行哈希处理的方法,这比通过 base-64 快得多,并且与 Web Workers 结合使用,它实际上是可用的。但是如果有办法使用 sjcl 来代替,那就更好了。
【解决方案2】:

这是一个老问题,但我认为值得注意的是asmCrypto 明显快于jsSHA,并且比CryptoJSSJCL

https://github.com/vibornoff/asmcrypto.js/

还有一个由OpenPGP.js维护的精简版(上述的一个分支)

https://github.com/openpgpjs/asmcrypto-lite

仅包含 SHA256 和几个 AES 功能。

要使用asmCrypto,您只需执行以下操作:

var sha256HexValue = asmCrypto.SHA256.hex(myArraybuffer);

我能够在 Chrome 中始终如一地在 内散列 150MB+ 文件。

【讨论】:

  • 不要使用旧版本的 AsmCrypto(这里的代码指的是第一个版本)。 2021 年没有使用最新版本 AsmCrypto 的有效文档。我建议依赖其他库。
【解决方案3】:

使用加密库的 emscripten 编译版本可能会更快,

问。编译代码的速度有多快?

A. Emscripten 默认的代码生成方式是 asm.js 格式, 这是 JavaScript 的一个子集,旨在使其成为可能 JavaScript 引擎执行速度非常快。请参阅此处以获取最新信息 基准测试结果。在很多情况下,asm.js 可以非常接近原生 速度。

你可以找到一个 Emscripten 编译的 NaCl 密码库here

【讨论】:

    【解决方案4】:

    这就是您要找的东西。我从 C 版本的 SHA256 算法中推导出来。它还包括 SHA256D。我认为使用 javascript 不会比这更快。我尝试扩展循环,但由于 javascript 解释器运行的优化,它运行得更慢。

    // From: https://github.com/Hartland/GPL-CPU-Miner/blob/master/sha2.c
    
    if ("undefined" == typeof vnet) {
        vnet = new Array();
    }
    
    if ("undefined" == typeof vnet.crypt) {
        vnet.crypt = new Array();
    }
    
    vnet.crypt.sha2 = function() {
    
        var sha256_h = [
            0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
            0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
        ];
    
        var sha256_k = [
                        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
                        0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
                        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
                        0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
                        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
                        0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
                        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
                        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
                        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
                        0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
                        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
                        0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
                        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
                        0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
                        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
                        0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
        ];
    
        var sha256_init = function(s) {
            s.state = [
                       sha256_h[0],
                       sha256_h[1],
                       sha256_h[2],
                       sha256_h[3],
                       sha256_h[4],
                       sha256_h[5],
                       sha256_h[6],
                       sha256_h[7],
            ];
        }; this.sha256_init = sha256_init;
    
    /*
    * SHA256 block compression function. The 256-bit state is transformed via
    * the 512-bit input block to produce a new state.
    */
        var sha256_transform = function(s, b, swap) {
    
            var block = b.block;
            var state = s.state;
    
            var W;
            var S;
            var t0;
            var t1;
            var i;
    
            /* 1. Prepare message schedule W. */
            if (swap) {
    
    
                W = [
                     ((((block[0] ) << 24) & 0xff000000) | (((block[0] ) << 8) & 0x00ff0000) | (((block[0] ) >> 8) & 0x0000ff00) | (((block[0] ) >> 24) & 0x000000ff)),
                     ((((block[1] ) << 24) & 0xff000000) | (((block[1] ) << 8) & 0x00ff0000) | (((block[1] ) >> 8) & 0x0000ff00) | (((block[1] ) >> 24) & 0x000000ff)),
                     ((((block[2] ) << 24) & 0xff000000) | (((block[2] ) << 8) & 0x00ff0000) | (((block[2] ) >> 8) & 0x0000ff00) | (((block[2] ) >> 24) & 0x000000ff)),
                     ((((block[3] ) << 24) & 0xff000000) | (((block[3] ) << 8) & 0x00ff0000) | (((block[3] ) >> 8) & 0x0000ff00) | (((block[3] ) >> 24) & 0x000000ff)),
                     ((((block[4] ) << 24) & 0xff000000) | (((block[4] ) << 8) & 0x00ff0000) | (((block[4] ) >> 8) & 0x0000ff00) | (((block[4] ) >> 24) & 0x000000ff)),
                     ((((block[5] ) << 24) & 0xff000000) | (((block[5] ) << 8) & 0x00ff0000) | (((block[5] ) >> 8) & 0x0000ff00) | (((block[5] ) >> 24) & 0x000000ff)),
                     ((((block[6] ) << 24) & 0xff000000) | (((block[6] ) << 8) & 0x00ff0000) | (((block[6] ) >> 8) & 0x0000ff00) | (((block[6] ) >> 24) & 0x000000ff)),
                     ((((block[7] ) << 24) & 0xff000000) | (((block[7] ) << 8) & 0x00ff0000) | (((block[7] ) >> 8) & 0x0000ff00) | (((block[7] ) >> 24) & 0x000000ff)),
                     ((((block[8] ) << 24) & 0xff000000) | (((block[8] ) << 8) & 0x00ff0000) | (((block[8] ) >> 8) & 0x0000ff00) | (((block[8] ) >> 24) & 0x000000ff)),
                     ((((block[9] ) << 24) & 0xff000000) | (((block[9] ) << 8) & 0x00ff0000) | (((block[9] ) >> 8) & 0x0000ff00) | (((block[9] ) >> 24) & 0x000000ff)),
                     ((((block[10]) << 24) & 0xff000000) | (((block[10]) << 8) & 0x00ff0000) | (((block[10]) >> 8) & 0x0000ff00) | (((block[10]) >> 24) & 0x000000ff)),
                     ((((block[11]) << 24) & 0xff000000) | (((block[11]) << 8) & 0x00ff0000) | (((block[11]) >> 8) & 0x0000ff00) | (((block[11]) >> 24) & 0x000000ff)),
                     ((((block[12]) << 24) & 0xff000000) | (((block[12]) << 8) & 0x00ff0000) | (((block[12]) >> 8) & 0x0000ff00) | (((block[12]) >> 24) & 0x000000ff)),
                     ((((block[13]) << 24) & 0xff000000) | (((block[13]) << 8) & 0x00ff0000) | (((block[13]) >> 8) & 0x0000ff00) | (((block[13]) >> 24) & 0x000000ff)),
                     ((((block[14]) << 24) & 0xff000000) | (((block[14]) << 8) & 0x00ff0000) | (((block[14]) >> 8) & 0x0000ff00) | (((block[14]) >> 24) & 0x000000ff)),
                     ((((block[15]) << 24) & 0xff000000) | (((block[15]) << 8) & 0x00ff0000) | (((block[15]) >> 8) & 0x0000ff00) | (((block[15]) >> 24) & 0x000000ff))
                ];
            } else {
                W = [
                     block[0],
                     block[1],
                     block[2],
                     block[3],
                     block[4],
                     block[5],
                     block[6],
                     block[7],
                     block[8],
                     block[9],
                     block[10],
                     block[11],
                     block[12],
                     block[13],
                     block[14],
                     block[15]
                ];
            }
    
    
            for (i = 16; i < 64; i += 2) {
                W[i] = ((
                    ((((W[i-2] >>> 17) | (W[i-2] << 15)) ^ ((W[i-2] >>> 19) | ((W[i-2] << 13)>>>0) ) ^ (W[i - 2] >>> 10)) >>> 0) + //s1 (W[i - 2]) + 
                    W[i - 7] + 
                    ((((W[i - 15] >>> 7) | (W[i - 15] << 25)) ^ ((W[i - 15] >>> 18) | ((W[i - 15] << 14) >>> 0)) ^ (W[i - 15] >>> 3))  >>> 0) + //s0 (W[i - 15]) + 
                    W[i - 16]
                ) & 0xffffffff) >>> 0;
    
                W[i+1] = ((
                    ((((W[i-1] >>> 17) | (W[i-1] << 15)) ^ ((W[i-1] >>> 19) | (W[i-1] << 13)) ^ (W[i - 1] >>> 10)) >>> 0)+ //s1 (W[i - 1]) + 
                    W[i - 6] + 
                    ((((W[i - 14] >>> 7) | (W[i - 14] << 25)) ^ ((W[i - 14] >>> 18) | (W[i - 14] << 14)) ^ (W[i - 14] >>> 3)) >>> 0)  + //s0 (W[i - 14]) + 
                    W[i - 15]
                ) & 0xffffffff) >>> 0;
            }
    
    
            /* 2. Initialize working variables. */
    
            S = [
             state[0],
             state[1],
             state[2],
             state[3],
             state[4],
             state[5],
             state[6],
             state[7],
            ];
    
            /* 3. Mix. */
    
    
            i=0;
            for(;i<64;++i) {
    
                //RNDr(S,W,i)
                t0 = S[(71 - i) % 8] + 
                    ((((S[(68 - i) % 8] >>> 6) | (S[(68 - i) % 8]  << 26)) ^ ((S[(68 - i) % 8] >>> 11) | (S[(68 - i) % 8] << 21)) ^ ((S[(68 - i) % 8] >>> 25) | (S[(68 - i) % 8] << 7)))) + //S1 (S[(68 - i) % 8]) +
                    (((S[(68 - i) % 8] & (S[(69 - i) % 8] ^ S[(70 - i) % 8])) ^ S[(70 - i) % 8]) ) + // Ch
                    W[i] + 
                    sha256_k[i];
    
                t1 = ((((S[(64 - i) % 8] >>> 2) | ((S[(64 - i) % 8] & 3) << 30)) ^ ((S[(64 - i) % 8] >>> 13) | (S[(64 - i) % 8] << 19)) ^ ((S[(64 - i) % 8] >>> 22) | (S[(64 - i) % 8] << 10)))) + //S0 (S[(64 - i) % 8]) +
                    (((S[(64 - i) % 8] & (S[(65 - i) % 8] | S[(66 - i) % 8])) | (S[(65 - i) % 8] & S[(66 - i) % 8]))); // Maj
    
                S[(67 - i) % 8] = ((S[(67 - i) % 8] + t0) & 0xFFFFFFFF) >>> 0; 
                S[(71 - i) % 8] = ((t0 + t1) & 0xFFFFFFFF) >>> 0;
            }
    
            /* 4. Mix local working variables into global state */
    
            i=0;
            for(;i<8;++i) {
                s.state[i] = (0xFFFFFFFF & (state[i] + S[i])) >>> 0;
            }
    
        }; this.sha256_transform = sha256_transform;
    
        var sha256d_hash1 = [
            0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x80000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000100
        ];
    
        var sha256d_80_swap = function(hash, data) 
        {
    
            var S = new Array();
    
            var i;
    
            var b1 = new Array();
            var b2 = new Array();
            var b3 = new Array();
    
            b1.block = [
                data[0],
                data[1],
                data[2],
                data[3],
                data[4],
                data[5],
                data[6],
                data[7],
                data[8],
                data[9],
                data[10],
                data[11],
                data[12],
                data[13],
                data[14],
                data[15]
            ];
    
            b2.block = [
                data[16],
                data[17],
                data[18],
                data[19],
                data[20],
                data[21],
                data[22],
                data[23],
                data[24],
                data[25],
                data[26],
                data[27],
                data[28],
                data[29],
                data[30],
                data[31]
            ];
    
            sha256_init(S);
            sha256_transform(S, b1, 0);
            sha256_transform(S, b2, 0);
    
            b3.block = [
                S.state[0],
                S.state[1],
                S.state[2],
                S.state[3],
                S.state[4],
                S.state[5],
                S.state[6],
                S.state[7],
                sha256d_hash1[8],
                sha256d_hash1[9],
                sha256d_hash1[10],
                sha256d_hash1[11],
                sha256d_hash1[12],
                sha256d_hash1[13],
                sha256d_hash1[14],
                sha256d_hash1[15]
            ];
    
            sha256_init(hash);
            sha256_transform(hash, b3, 0);
    
            for (i = 0; i < 8; i++) {
                hash.state[i] = ((((hash.state[i] ) << 24) & 0xff000000) | (((hash.state[i] ) << 8) & 0x00ff0000) | (((hash.state[i] ) >> 8) & 0x0000ff00) | (((hash.state[i] ) >> 24) & 0x000000ff)); //swab32(hash[i]);
            }
    
        }; this.sha256d_80_swap = sha256d_80_swap;
    
        var sha256d = function(hash, data) {
            var S;
            var T;
            var block_in;
    
            S = new Array();
            T = new Array();
    
            T.block = [];
    
            var i, r;
    
            //hash.hash = new Array(32).join('0').split('').map(parseFloat);
    
            sha256_init(S);
    
            for (r = data.length; r > -9; r -= 64) {
                if (r < 64) {
                    if (r > 0) {
                        block_in = data.slice(data.length - r,data.length);
                        block_in.push.apply(block_in, new Array(64-r).join('0').split('').map(parseFloat));
                    } else {
                        block_in = new Array(64).join('0').split('').map(parseFloat);
                    }
                } else {
                    block_in = data.slice(data.length - r,data.length - r + 64);
                }
    
                //memcpy(T, data + len - r, r > 64 ? 64 : (r < 0 ? 0 : r));
    
                if (r >= 0 && r < 64) {
                    block_in[r] = 0x80;
                } 
    
                for (i = 0; i < 16; i++) {
                    T.block[i] = (((0xff & block_in[(i*4)]) << 24) | ((0xff & block_in[(i*4)+1]) << 16) | ((0xff & block_in[(i*4)+2]) << 8) | (0xff & block_in[(i*4)+3])) >>> 0;
                }
    
                if (r < 56) {
                    T.block[15] = 8 * data.length;
                }
    
                sha256_transform(S, T, 0);
            }
            //memcpy(S + 8, sha256d_hash1 + 8, 32);
            S.block = S.state;
            for(i=8;i<16;i++) {
                S.block[i] =  sha256d_hash1[i];
            }
    
            sha256_init(T);
            sha256_transform(T, S, 0);
    
            hash.hash = [ 
                      (T.state[0] >> 24) & 0xff,
                      (T.state[0] >> 16) & 0xff,
                      (T.state[0] >> 8) & 0xff,
                      T.state[0] & 0xff,
    
                      (T.state[1] >> 24) & 0xff,
                      (T.state[1] >> 16) & 0xff,
                      (T.state[1] >> 8) & 0xff,
                      T.state[1] & 0xff,
    
                      (T.state[2] >> 24) & 0xff,
                      (T.state[2] >> 16) & 0xff,
                      (T.state[2] >> 8) & 0xff,
                      T.state[2] & 0xff,
    
                      (T.state[3] >> 24) & 0xff,
                      (T.state[3] >> 16) & 0xff,
                      (T.state[3] >> 8) & 0xff,
                      T.state[3] & 0xff,
    
                      (T.state[4] >> 24) & 0xff,
                      (T.state[4] >> 16) & 0xff,
                      (T.state[4] >> 8) & 0xff,
                      T.state[4] & 0xff,
    
                      (T.state[5] >> 24) & 0xff,
                      (T.state[5] >> 16) & 0xff,
                      (T.state[5] >> 8) & 0xff,
                      T.state[5] & 0xff,
    
                      (T.state[6] >> 24) & 0xff,
                      (T.state[6] >> 16) & 0xff,
                      (T.state[6] >> 8) & 0xff,
                      T.state[6] & 0xff,
    
                      (T.state[7] >> 24) & 0xff,
                      (T.state[7] >> 16) & 0xff,
                      (T.state[7] >> 8) & 0xff,
                      T.state[7] & 0xff
             ];
    
        }; this.sha256d = sha256d;
    
    
    
        var sha256 = function(hash, data) {
            var S;
            var T;
            var block_in;
    
            S = new Array();
            T = new Array();
    
            T.block = [];
    
            var i, r;
    
            hash.hash = new Array(32).join('0').split('').map(parseFloat);
    
            sha256_init(S);
    
            for (r = data.length; r > -9; r -= 64) {
    
                if (r < 64) {
                    if (r > 0) {
                        block_in = data.slice(data.length - r,data.length);
                        block_in.push.apply(block_in, new Array(64-r).join('0').split('').map(parseFloat));
                    } else {
                        block_in = new Array(64).join('0').split('').map(parseFloat);
                    }
                } else {
                    block_in = data.slice(data.length - r,data.length - r + 64);
                }
    
                //memcpy(T, data + len - r, r > 64 ? 64 : (r < 0 ? 0 : r));
    
                if (r >= 0 && r < 64) {
                    block_in[r] = 0x80;
                } 
    
                for (i = 0; i < 16; i++) {
                    T.block[i] = (((0xff & block_in[(i*4)]) << 24) | ((0xff & block_in[(i*4)+1]) << 16) | ((0xff & block_in[(i*4)+2]) << 8) | (0xff & block_in[(i*4)+3])) >>> 0;
                }
    
                if (r < 56) {
                    T.block[15] = 8 * data.length;
                }
    
                sha256_transform(S, T, 0);
            }
    
            for (i = 0; i < 8; i++) {
                //be32enc((uint32_t *)hash + i, T[i]);
                hash.hash[(i * 4)] = (S.state[i] >> 24) & 0xff;
                hash.hash[(i * 4)+1] = (S.state[i] >> 16) & 0xff
                hash.hash[(i * 4)+2] = (S.state[i] >> 8) & 0xff
                hash.hash[(i * 4)+3] = S.state[i] & 0xff;
            }
        }; this.sha256 = sha256;
    
    
    
    };
    

    【讨论】:

      【解决方案5】:

      您可以使用Crypto.subtle API 在没有外部库的情况下做到这一点。更多详情here.

      例子:

      function b2h(buffer) {
          return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
      }
      
      const FILEREADER = new FileReader();
      FILEREADER.readAsArrayBuffer(file);
      FILEREADER.onloadend = async function(entry) {
          const FILE_HASH = b2h(await crypto.subtle.digest('SHA-256', entry.target.result)); // output: the sha256 digest hex encoded of the file
      }
      

      【讨论】:

        【解决方案6】:

        我用SubtleCrypto.digest()

        大约 85MB 的测试文件,很快就完成了。

        <input type="file" multiple/>
        <input placeholder="Press `Enter` when done."/>
        <script>
        
          /**
           * @param {"SHA-1"|"SHA-256"|"SHA-384"|"SHA-512"} algorithm https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
           * @param {string|Blob} data
           */
          async function getHash(algorithm, data) {
        
            const main = async (msgUint8) => { // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string
              const hashBuffer = await crypto.subtle.digest(algorithm, msgUint8)
              const hashArray = Array.from(new Uint8Array(hashBuffer))
              return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
            }
        
            if (data instanceof Blob) {
              const arrayBuffer = await data.arrayBuffer()
              const msgUint8 = new Uint8Array(arrayBuffer)
              return await main(msgUint8)
            }
            const encoder = new TextEncoder()
            const msgUint8 = encoder.encode(data)
            return await main(msgUint8)
          }
        
          const inputFile = document.querySelector(`input[type="file"]`)
          const inputText = document.querySelector(`input[placeholder^="Press"]`)
          inputFile.onchange = async (event) => {
            for (const file of event.target.files) {
              console.log(file.name, file.type, file.size + "bytes")
              const hashHex = await getHash("SHA-256", new Blob([file]))
              console.log(hashHex)
            }
          }
        
          inputText.onkeyup = async (keyboardEvent) => {
            if (keyboardEvent.key === "Enter") {
              const hashHex = await getHash("SHA-256", keyboardEvent.target.value)
              console.log(hashHex)
            }
          }
        </script>

        【讨论】:

          猜你喜欢
          • 2012-09-12
          • 1970-01-01
          • 2014-08-19
          • 1970-01-01
          • 2023-03-18
          • 2017-07-18
          • 2013-01-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多