// scripts/encrypt_pdf.ts import * as fs from 'fs' import * as path from 'path' import * as crypto from 'crypto' import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PDF_DIR = path.resolve(__dirname, '../../public/pdf') // 生成一个256位(32字节)的密钥 function generateKey(): Buffer { return crypto.randomBytes(32) // 256 bits = 32 bytes } function getKey(password: string): Buffer { return crypto.createHash('sha256').update(password).digest() } function encryptGCM(inputPath: string, outputPath: string, password: string): void { const key = getKey(password) const iv = crypto.randomBytes(12) // GCM 推荐的 IV 长度 console.log(`✔ iv: ${iv.toString('base64')}`) const input = fs.readFileSync(inputPath) const cipher = crypto.createCipheriv('aes-256-gcm', key, iv) const encrypted = Buffer.concat([cipher.update(input), cipher.final()]) // console.log(`✔ encrypted: ${encrypted.toString('base64')}`) const authTag = cipher.getAuthTag() console.log(`✔ authTag: ${authTag.toString('base64')}`) const passwordBuffer = Buffer.from(password, 'base64') const passwordLen = passwordBuffer.length const lenBuf = Buffer.alloc(4) lenBuf.writeUInt32BE(passwordLen) const output = Buffer.concat([iv, encrypted, authTag, passwordBuffer, lenBuf]) // 格式:[IV][DATA][TAG][PASSWORD][PASSWORD_LEN] fs.writeFileSync(outputPath, output) console.log(`✔ Encrypted (GCM): ${path.basename(inputPath)}`) } function encryptAllPDFs(): void { const files = fs.readdirSync(PDF_DIR) for (const file of files) { const fullPath = path.join(PDF_DIR, file) if (!fs.statSync(fullPath).isFile() || !file.endsWith('.pdf')) continue const encPath = fullPath + '.enc' if (fs.existsSync(encPath)) { console.log(`⏭ Skipped (already encrypted): ${file}`) continue } const key = generateKey() encryptGCM(fullPath, encPath, key.toString('base64')) } } encryptAllPDFs()