LoginSignup
1
0

S3 - node:cryptoを使用したAES復号化、S3ファイルストリーム操作

Posted at

目的

S3上の暗号化されたファイルをStreamで復号化、アップロードする

暗号化の種類

  • 暗号化方式: AES
  • 鍵ビット長: 128bit
  • ブロック暗号のモード: CBC
  • 初期ベクトルIV: ファイル先頭16byte

暗号化の詳細は以下を参照
暗号技術入門04 ブロック暗号のモード〜ブロック暗号をどのように繰り返すのか〜

node:cryptoの復号化実装例

Class: Decipherに以下3パターンの例がある

  • Example: Using Decipher objects as streams:
  • Example: Using Decipher and piped streams:
  • Example: Using the decipher.update() and decipher.final() methods:

今回使用するのは2つ目の入力用/出力用のStreamをpipeで繋ぐ方法

ローカルの暗号化されたファイルをStreamで復号化

import fs from 'node:fs';
import crypto from 'node:crypto';

const password = 'T012345678901234';

// ファイルの先頭16byteをIVとして返却する
async function getIv(fileName) {
    const src = fs.createReadStream(fileName, { start: 0, end: 15 });
    let iv = undefined;

    return new Promise((resolve, reject) => {
      src.on('data', chunk => {
        if (iv === undefined) {
          iv = chunk;
        }
      });
      src.on('error', error => {
        reject(error);
      });
      src.on('close', () => resolve(iv));
    });
}

// ファイルの17byte以降を復号化、ファイル出力する
async function decodeFile(fileName, iv) {
  
  const src = fs.createReadStream(fileName, { start: 16 });
  const dest = fs.createWriteStream('dest.txt');
  
  return new Promise((resolve, reject) => {
    const decipher = crypto.createDecipheriv('aes-128-cbc', password, iv);

    src.pipe(decipher).pipe(dest);
    src.on('error', error => {
      dest.destroy(error);
      reject(error);
    });
    src.on('close', () => resolve());
  });
}

async function main() {
  try {
    const fileName = 'file.bytes';
    const iv = await getIv(fileName);
    await decodeFile(fileName, iv);
  } catch (err) {
    console.log(err);
  }
}

await main();

S3上の暗号化されたファイルをStreamで復号化

S3用のSDK@aws-sdk/client-s3で、S3上のファイルをStreamとして取り出すことができる
StreamをS3上にアップロードするためには、@aws-sdk/lib-storageを使用する
ただし、pipeを使用にはnode:streamPassThroughでアップロード用のStreamを作成する必要がある

import crypto from 'node:crypto';
import path from 'node:path';
import { PassThrough } from 'node:stream';

import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';

async function decryptFile(bucket, key) {
  const region = 'ap-northeast-1';
  const s3Client = new S3Client({
    region,
  });

  const getParam = {
    Bucket: bucket,
    Key: key,
  };

  // ファイルの先頭16byteをIVとする
  const response = await s3Client.send(
    new GetObjectCommand({
      ...getParam,
      Range: 'bytes=0-15',
    }),
  );
  const iv = await response.Body.transformToByteArray();

  // ファイルの17byte以降を入力Streamとする
  const inStream = (
    await s3Client.send(
      new GetObjectCommand({
        ...getParam,
        Range: 'bytes=16-',
      }),
    )
  ).Body;

  // 出力Streamを用意する
  const outStream = new PassThrough();

  const password = 'T012345678901234';
  const decipher = crypto.createDecipheriv('aes-128-cbc', password, iv);
  inStream.pipe(decipher).pipe(outStream);

  const upload = new Upload({
    client: s3Client,
    params: {
      Bucket: bucket,
      Key: `${path.dirname(key)}/dest.txt`,
      Body: outStream,
    },
  });
  await upload.done();
}

その他参考

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