|
const TextCharacters = "0123456789abcdef"; |
|
const Radix = TextCharacters.length; |
|
|
|
const BufferSize = 1024 ** 3; // 1MB |
|
const SecretKeyLength = 32; |
|
const IvSize = 16; |
|
|
|
const checkBuf = (buf: Uint8Array): Uint8Array => { |
|
if (buf.length <= 0) { |
|
throw new Error(`ERROR: Data is empty`); |
|
} else if (buf.length > BufferSize) { |
|
throw new Error(`ERROR: Data size too large. Max: ${BufferSize} bytes`); |
|
} |
|
return buf; |
|
}; |
|
|
|
const convBinToText = (arr: Uint8Array): string => |
|
Array.from(arr) |
|
.map((n: number): string => |
|
[TextCharacters.at(Math.floor(n / Radix)), TextCharacters.at(n % Radix)] |
|
.join(""), |
|
) |
|
.join(""); |
|
|
|
const convTextToBin = (text: string): Uint8Array => { |
|
const list: number[] = []; |
|
for (let p = 0; p < text.length; p += 2) { |
|
list.push(parseInt(text.substring(p, p + 2), Radix)); |
|
} |
|
return new Uint8Array(list); |
|
}; |
|
|
|
const getSecretKey = async (): Promise<CryptoKey> => { |
|
const secret = Deno.env.get("SECRET_KEY"); |
|
if (secret == null) throw new Error("SECRET_KEY value not exists"); |
|
if (secret.length !== SecretKeyLength * 2) throw new Error(`SECRET_KEY length must be ${SecretKeyLength * 2}.`) |
|
const rawKey = convTextToBin(secret); |
|
if (rawKey.length !== SecretKeyLength) throw new Error(`SECRET_KEY binary length must be ${SecretKeyLength}.`) |
|
return await crypto.subtle.importKey("raw", rawKey, "AES-CBC", true, [ |
|
"encrypt", |
|
"decrypt", |
|
]); |
|
}; |
|
|
|
const encrypt = async (secretKey: CryptoKey): Promise<void> => { |
|
let plainBuf = new Uint8Array(BufferSize + 1); |
|
const readBytes = await Deno.stdin.read(plainBuf); |
|
plainBuf = checkBuf(plainBuf.slice(0, readBytes ?? 0)); |
|
const ivByEnvVar = Deno.env.get("IV"); |
|
const iv = ivByEnvVar != null ? convTextToBin(ivByEnvVar) : window.crypto.getRandomValues(new Uint8Array(16)); |
|
const encryptedBuf = checkBuf( |
|
new Uint8Array( |
|
await crypto.subtle.encrypt( |
|
{ name: "AES-CBC", iv }, |
|
secretKey, |
|
plainBuf, |
|
), |
|
), |
|
); |
|
const mergeBuf = checkBuf(new Uint8Array(iv.length + encryptedBuf.length)); |
|
mergeBuf.set(iv, 0); |
|
mergeBuf.set(encryptedBuf, iv.length); |
|
const encryptedText = convBinToText(mergeBuf); |
|
console.log(encryptedText); |
|
}; |
|
|
|
const decrypt = async (secretKey: CryptoKey): Promise<void> => { |
|
let encryptedBuf = new Uint8Array(BufferSize + 1); |
|
const readBytes = await Deno.stdin.read(encryptedBuf); |
|
encryptedBuf = convTextToBin(new TextDecoder().decode(encryptedBuf.slice(0, readBytes ?? 0))); |
|
const iv = encryptedBuf.slice(0, IvSize); |
|
encryptedBuf = checkBuf(encryptedBuf.slice(IvSize, IvSize + (readBytes ?? 0))); |
|
const plainBuf = await window.crypto.subtle.decrypt( |
|
{ name: "AES-CBC", iv }, |
|
secretKey, |
|
encryptedBuf, |
|
); |
|
console.log(new TextDecoder().decode(new Uint8Array(plainBuf))); |
|
}; |
|
|
|
const command = Deno.args[0] ?? "(empty)"; |
|
if (command === "encrypt") { |
|
await encrypt(await getSecretKey()); |
|
} else if (command === "decrypt") { |
|
await decrypt(await getSecretKey()); |
|
} else { |
|
throw new Error(`Unknown command: ${Deno.args[0]}`); |
|
} |