10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Node.jsAdvent Calendar 2016

Day 3

Node.js での MD5値の求め方

Posted at

環境

  • Ubuntu 16.04 LTS
  • Node.js 4.x, 6.x

動機

aws-sdk の AWS.S3#putObject()1 でファイルをS3にUploadする時、 ContentMD5 で Base64 でデコードしたMD5値を指定すると、アップロード後のMD5と比較してチェックしてくれる。

この機能を使いたくて、Node.jsでMD5値を求める必要があったので備忘録も兼ねてまとめみた。

シンプル版

まずは簡単に同期的に書いてみる

var crypto = require("crypto");
var fs = require("fs");

function md5(filePath) {
    let b = fs.readFileSync(filePath);
    let md5hash = crypto.createHash('md5');
    md5hash.update(b);
    return md5hash.digest("base64");
}

問題点

この実装だと、読み込むファイルのサイズが大きいとクラッシュしてしまう。
試しに 8GBファイルのMD5を求めると↓のようになる。

$ node md5pf.js
buffer.js:25
    const ui8 = new Uint8Array(size);
                ^

RangeError: Invalid typed array length
    at new Uint8Array (native)
    at createBuffer (buffer.js:25:17)
    at allocate (buffer.js:158:12)
    at new Buffer (buffer.js:56:12)
    at Object.fs.readFileSync (fs.js:419:16)
    at md5F (/home/yasuhiroki/md5pf.js:33:16)
    at Object.<anonymous> (/home/yasuhiroki/md5pf.js:41:13)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)

MD5というより、 readFileSync() で大きなファイルを読み込もうとしているのが問題。

Streamを使った版

大きなデータを扱うならば Stream を使えば良い。
となると非同期処理になるので、Promiseを返す関数にする。

var crypto = require("crypto");
var fs = require("fs");

function md5(filePath) {
    return new Promise((resolve, reject) => {
        var readStream = fs.createReadStream(filePath);
        var md5hash = crypto.createHash('md5');
        md5hash.setEncoding('base64');
        readStream.pipe(md5hash);
        readStream.on('end', () => {
            resolve(md5hash.read());
        });
        readStream.on('error', (e) => {
            reject(e);
        });
    });
}

crypto は pipe に渡せるので、シンプルに readStream.pipe(md5hash) と書くことができる。
あとは、 stream の end のタイミングで計算結果を読み込めば良い。

先程クラッシュした8GBファイルのMD5値を計算すると↓のようにうまくいく。

$ node md5pf.js
N11F4wTV+6JFRuh89jqaqQ==

おまけ

そもそもの動機だった、 AWS.S3#putObject() で ContentMD5を指定してチェックする方法は、サイズの大きいファイルで使うと The Content-MD5 you specified is invalid for multi-part uploads というエラーが発生する。

巨大なファイルは multipart upload が使用されるので、ファイルが分割されて少しずつアップロードされる。そのため、MD5値は分割された単位で求めなければならず、せっかく↑で求めたMD5値は使えなかった。

https://github.com/aws/aws-sdk-js/blob/5691296bd1c981a4fed04ee6eb42179c277ac079/lib/s3/managed_upload.js#L47 を読むと、代わりに computeChecksums を使うべしとある。
このフラグを使うと、aws-sdkがいい感じにMD5値のチェックをしてくれるので、自分で計算しなくていい。

も、もっと早くに知っていれば....

  1. http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property

10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?