기존에 node:crypto를 이용해서 파일을 암호화했었다.
이걸 이제 deno로 옮기려는데 며칠을 삽질을 해도 제대로 안되었다… 그래서 결국에 스택오버플로에 질문을 올렸고 매우 빠르게 답변을 받았다.
일단 node:crypto는 webcrypto를 구현한 것이 아니고 따라서 webcrypto (deno crypto가 구현한 것)과는 바로 연동?이 되지 않는다고 한다.
node:crypto쪽에서 webcrypto와 연동이 되게 하려면 node:crypto 쪽에서 encrypt를 할 때 authTag에 대한 정보도 같이 넘겨주어야 제대로 작동한다.
그래서 기존에는 다음과 같이 node:crypto를 이용하여 암호화를 했다면
import crypto from "node:crypto";
export async function encrypt(buffer: Uint8Array, passphrase: string): Promise<Uint8Array> {
const key = new TextEncoder().encode(passphrase);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-128-gcm", key, iv);
return new Uint8Array([...iv, ...cipher.update(buffer), ...cipher.final()]);
}
webcrypto와 연동을 위해서는 아래와 같이 authTag의 정보도 같이 넣어주고
import crypto from "node:crypto";
export async function encrypt(buffer: Uint8Array, passphrase: string): Promise<Uint8Array> {
const key = new TextEncoder().encode(passphrase);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-128-gcm", key, iv);
return new Uint8Array([...iv, ...cipher.update(buffer), ...cipher.final(), ...cipher.getAuthTag()]);
}
node:crypto에서 복호화 시에는 태그를 따로 떼어서 설정을 해주는 방식으로 한다.
export async function decrypt(buffer: Uint8Array, passphrase: string): Promise<Uint8Array> {
const key = new TextEncoder().encode(passphrase);
const iv = buffer.slice(0, 16);
const contents = buffer.slice(16, buffer.length - 16);
const tag = buffer.slice(buffer.length - 16);
const decipher = crypto.createDecipheriv("aes-128-gcm", key, iv);
decipher.setAuthTag(tag);
return new Uint8Array([...decipher.update(contents)]);
}
webcrypto (deno crypto) 쪽에서는 태그에 관한 별도의 작업 없이 그냥 사용하면 된다.
export async function encrypt(buffer: Uint8Array, passphrase: string): Promise<Uint8Array> {
const key = new TextEncoder().encode(passphrase);
const iv = crypto.getRandomValues(new Uint8Array(16));
const cryptoKey = await crypto.subtle.importKey(
"raw",
key,
{ name: "AES-GCM", length: 128 },
true,
["encrypt"]
);
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", length: 128, iv },
cryptoKey,
buffer
);
return new Uint8Array([...iv, ...new Uint8Array(encrypted)]);
}
export async function decrypt(buffer: Uint8Array, passphrase: string): Promise<Uint8Array> {
const key = new TextEncoder().encode(passphrase);
const iv = buffer.slice(16);
const cryptoKey = await crypto.subtle.importKey(
"raw",
key,
{ name: "AES-GCM", length: 128 },
true,
["decrypt"]
);
const contents = buffer.slice(16);
return await crypto.subtle.ecrypt(
{ name: "AES-GCM", length: 128, iv },
cryptoKey,
contents
).then((buf) => new Uint8Array(buf))
}
이렇게 할 경우 node encrypt -> deno decrypt, deno encrypt -> node decrypt 모두 잘 작동한다.
스택 오버플로에 올린 질문은 아래다.
추가로 답변해주신분이 말씀하시길 AES-GCM 128bits의 iv의 길이는 16바이트가 아니어야 더 안전하고 일반적으로 12바이트를 사용한다고 한다.
답글 남기기