LoginSignup
1
0

More than 1 year has passed since last update.

CryptoJSで暗号化したファイルをどうしてもシェル上で復号したかった

Last updated at Posted at 2021-06-29

やりたかったこと

  1. Node.jsのCryptoJSで暗号化したファイルを保存する
  2. そのファイルをシェル上で読み込み、復号する
暗号化処理(Node.js)
const crypto = require('crypto');
const fs = require('fs');

/*
 * passphrase <String>
 * salt <String>
 * keylen <Number>
 * iv <String>
 * pass <String> ファイルパス
 * inputPath <String> ファイルパス
 * outputPath <String> ファイルパス
 */

// 暗号
const key = crypto.scryptSync(passphrase, salt, keylen);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
fs.writeFileSync(pass, passphrase);

// 読み込み => 暗号化 => 書き出し
const input = fs.createReadStream(inputPath);
const output = fs.createWriteStream(outputPath);
input.pipe(cipher).pipe(appendiv).pipe(output);
復号処理(Shell)
openssl -aes-256-cbc -d \
     -pass pass:<pass> \
     -salt <salt> \
     -in <inputPath> \
     -out <outputPath>

結果: bad decrypt

できなかったこと(めんどくさくなったこと)

opensslコマンドで復号

opensslが取り扱うsaltあり暗号文は、ヘッダとして Salted__ という8byteを持つ前提になっている。

暗号文とpassのみを渡して復号

IVを使って暗号化するとき、理論上passと暗号文からkeyとIVを復元できるが、手軽にこれを実現するライブラリを見つけられなかった。

やったこと

  1. 暗号化処理でIVを暗号文に連結して渡すように変更
  2. シェルスクリプト側でもNode.jsのCryptoJSを利用して復号するように変更

※IVは平文のまま安全でない経路で受け渡しできるとされている

暗号化処理(Node.js)
const crypto = require('crypto');
const fs = require('fs');


// 暗号
const key = crypto.scryptSync(passphrase, salt, keylen);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
fs.writeFileSync(pass, passphrase);

const input = fs.createReadStream(inputPath);
/* ↓追加処理↓ */
// https://stackoverflow.com/questions/41594042/node-transform-stream-append-string-to-end
const appendiv = new stream.Transform({
    transform(chunk, encoding, callback) {
        callback(null, chunk);
    },
    flush(callback) {
        this.push(iv);
        callback();
    }
});
/* ↑追加処理↑ */
const output = fs.createWriteStream(outputPath);
// 読み込み => 暗号化 => IVを末尾に付加 => 書き出し
input.pipe(cipher).pipe(appendiv).pipe(output);
node-script.js
/* 復号処理(Node.jsスクリプト) */

/*
 * IV_LEN <Number> IVの長さ
 */

exports.decipher = async(options) => {
    const pass = fs.readFileSync(options.pass).toString(/* passphraseの文字コードに注意 */); 
    const key = crypto.scryptSync(pass, options.salt, options.keylen); 
    const input = fs.readFileSync(options.inputPath);
    const iv = input.slice(-1 * IV_LEN); // 後ろからIV_LEN文字がIV

    const cipher = crypto.createDecipheriv(options.cipher, key, iv);
    let decrypted = cipher.update(input.slice(0, -1 * IV_LEN)); // 後ろからIV_LEN文字目より前が暗号文
    decrypted = Buffer.concat([decrypted, cipher.final()]);

    const output = fs.writeFileSync(options.outputPath, decrypted);
}

decipher.js
/* cacjsによりcliコマンド化 */

const cac = require('cac');
const cli = cac();
const nodeScript = require('./node-script');

cli
    .command('decipher', '復号する')
    .option('--pass <pass>', 'passが記載されたファイル')
    .option('--salt <salt>', 'ソルト文字列')
    .option('--keylen <keylen>', 'keyの長さ')
    .option('--inputPath <inputPath>', '暗号化されたファイル')
    .option('--outputPath <outputPath>', '復号されたファイル')
    .action(async (options) => {
        await nodeUtils.decipher(options);
    });

cli.help();

cli.parse();
シェルスクリプト
node decipher.js decipher \
  --pass <pass> \
  --salt <salt> \
  --keylen <keylen> \
  --in <inputPath> \
  --out <outputPath>

わかったこと

暗号化と復号は同じツールを使ってやるのが楽。

1
0
1

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
1
0