目的
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:stream
のPassThrough
でアップロード用の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();
}