JavaScript
AWS
S3

Amazon S3にアップロードしたファイルの完全性を確認する

はじめに

タイトル通りです。
aws-sdk を使ってでs3にファイルをアップロードする際に、アップロードがきちんとできたかどうかを確認したいことがあると思います。
やり方としては、アップロードの際に Content-MD5 にアップロードするmd5を指定する、というものです。
そのための方法を解説している記事はあったのですが(これとか)、実際にmd5が異なっていたらアップロードが失敗することを確認しているものが少ないようだったので試してみました。

今回はjavascriptを使ってみます。

アップロード時の完全性担保

s3でアップロードしたファイルの完全性を担保する場合、ContentMD5というパラメータを使います。
こちらはアップロードした際にアップロードしたファイル(マルチパートアップロードの場合、アップロードしたパート)のmd5をリクエストに付与すると、s3側で受け取ったファイルのmd5と照合を行なってくれるというものです。

aws-sdk for javascript で putObject によりファイルのアップロードをしたい場合、以下のようになります。

upload.js
var AWS = require('aws-sdk');

var credentials = new AWS.SharedIniFileCredentials({
  profile: "profile_name"
});
AWS.config.update({
  region: 'ap-northeast-1',
  credentials: credentials
});

var storeItem = 'hello, world';

var MD5 = require('md5.js');
var md5binary = new MD5().update(storeItem).digest();
console.log(md5binary);
console.log(md5binary.toString('hex'));
md5sum = md5binary.toString('base64');

var s3 = new AWS.S3();
s3.putObject(
  {
    Bucket: 'bucket_name',
    Key: 'tmp.txt',
    Body: storeItem,
    ContentMD5: md5sum
  },
  function(err, data) {
    if (err) console.log(err, err.stack);
    else console.log(data);
  }
)

クレデンシャルやバケット適宜読み替えてください。

これを実行してみると

% node index.js
<Buffer e4 d7 f1 b4 ed 2e 42 d1 58 98 f4 b2 7b 01 9d a4>
e4d7f1b4ed2e42d15898f4b27b019da4
{ ETag: '"e4d7f1b4ed2e42d15898f4b27b019da4"' }

となり、s3にファイルのアップロードが可能です。
今回は putObject関数で単一のアップロードをしているので、s3のetagはファイルのmd5と一致します。
実際に echo コマンドと md5 コマンドを使って確認してみると、確かにアップロードしたファイルのetagと一致しています。

% echo -n "hello, world" > foo.txt && md5 foo.txt
MD5 (foo.txt) = e4d7f1b4ed2e42d15898f4b27b019da4

データ破損をcontent md5で検知する

と、言いましたが、意図的にデータの破損をさせることは難しいです。
なので今回は、

  • 「データが破損した」
  • =「アップロードしたファイルのmd5が変わった」
  • =「アップロードしたファイルのmd5がアップロード時に指定したmd5と異なっている」

と読み替え、アップロードしたファイルのmd5と指定したmd5が異なる場合の挙動を確認してみます。

そのために以下のようにソースコードを変更し、

 var storeItem = 'hello, world';

 var MD5 = require('md5.js');
-var md5binary = new MD5().update(storeItem).digest();
+var md5tmp = new MD5().update(storeItem);
+md5tmp._a += 1 // 適当にmd5を改ざん
+var md5binary = md5tmp.digest();
 console.log(md5binary);
 console.log(md5binary.toString('hex'));
 md5sum = md5binary.toString('base64');

ContentMD5 で指定するmd5をアップロードされるファイルのものと異なるようにします。
これを実行してみると、

% node index.js
<Buffer 7c cc 99 9c e3 1c 12 7e 22 18 90 79 53 5a 2a 9e>
7ccc999ce31c127e22189079535a2a9e
{ BadDigest: The Content-MD5 you specified did not match what we received.
    at Request.extractError (...
    ... )
  message: 'The Content-MD5 you specified did not match what we received.',
  code: 'BadDigest',
  region: null,
  time: xxx,
  requestId: 'xxx',
  extendedRequestId: 'xxx',
  cfId: undefined,
  statusCode: 400,
  retryable: false,
  retryDelay: xxx }

としっかり失敗します。
適切にmd5を設定できれば、アップロード時のビット欠落等によるアップロードエラーを検知できることがわかります。

今回は単純なputObjectで試しましたが、マルチパートアップロードの場合には、各パートごとにアップロードの完全性担保が必要になります。
具体的には、putObject の場合と同様に uploadPartでアップロードするパートのmd5を指定すればいいだけです。