FileZilla から Storj を利用する時、12個の英単語からなる秘密鍵を入力する必要があります。今回は、この秘密鍵がどのようにファイルの暗号化に使用されているのか調べてみました。
なお、調査対象のソースコードは
- https://github.com/Storj/libstorj/blob/master/src/downloader.c
- https://github.com/Storj/libstorj/blob/master/src/crypto.c
- https://github.com/Storj/libstorj/blob/master/src/bip39.c
あたりです。C言語以外では、
- https://github.com/Storj/storj.js/blob/master/lib/api/download.js
- https://github.com/Storj/core/blob/ae563e7362b22ebe94616edeff723c1889763c27/lib/crypto-tools/deterministic-key-iv.js
- https://github.com/Storj/core/blob/ae563e7362b22ebe94616edeff723c1889763c27/lib/crypto-tools/decrypt-stream.js
に今は使われていないものの JavaScript で書かれたバージョンもあります。
Storj は、バケットとファイルという二つの概念でファイルを管理しています。バケットはフォルダのようなものですが、入れ子にはできません。そのため、各ファイルは必ず一つのバケットに属していて、バケットID とファイル ID のペアで一意に識別できます。ファイルの暗号化にはこのバケット ID とファイル ID も使われます。
さて、FileZilla で見える秘密鍵は12個の英単語です。どこかで見たことがあるような形式ですが、Bitcoin 等でウォレットのバックアップに使うニーモニックコードです。Bitcoin では、このニーモニックコードを使って乱数生成器の種を設定しますが、Storj ではこの値のハッシュ値を暗号化の秘密鍵に使っています。
具体的な秘密鍵の生成方法は次の通りです。(なお以下のコードはテストしていないので動かないかもしれません)
まず、ニーモニックコードからマスターシードを取得します。
import Mnemonic from 'bitcore-mnemonic’;
const encryptionKey = Mnemonic('ニーモニックコード');
const seed = encryptionKey.toSeed();
次に、マスターシードとバケット ID を連結させ、sha512 ハッシュを取ります。そして、先頭の 256bit をバケット鍵とします。
import crypto from 'crypto';
const bucketID = Buffer(‘バケットID', 'hex')
const bucketKey = crypto.createHash('sha512').update(Buffer.concat([seed, bucketID])).digest().slice(0, 32);
今度は、バケット鍵とファイル ID を連結させ、sha512 ハッシュを取ります。そして、先頭の 256bit をファイル鍵とします。
const fileID = Buffer(‘ファイルID', 'hex')
const fileKey = crypto.createHash('sha512').update(Buffer.concat([bucketKey, fileID])).digest().slice(0, 32);
ファイルは、このようにして得られたファイル鍵の sha256 ハッシュを秘密鍵、ファイルIDのRIPEMD-160 ハッシュを初期化ベクトルとして、aes-256-ctr によって暗号化されています。
import fs from 'fs';
const key = crypto.createHash('sha256').update(fileKey).digest();
const iv = crypto.createHash('rmd160').update(fileID).digest().slice(0, 16)
const decipher = crypto.createCipheriv('aes-256-ctr', key, iv);
const input = fs.createReadStream(‘暗号化前のファイル');
const output = fs.createWriteStream(‘暗号化後のファイル');
input.pipe(decipher).pipe(output);
JavaScript で書くと C より随分コンパクトになりますね。