node:cyprto to deno crypto

기존에 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 모두 잘 작동한다.

스택 오버플로에 올린 질문은 아래다.

https://stackoverflow.com/questions/77670497/deno-crypto-cannot-decrypt-encrypted-files-by-nodecrypto

추가로 답변해주신분이 말씀하시길 AES-GCM 128bits의 iv의 길이는 16바이트가 아니어야 더 안전하고 일반적으로 12바이트를 사용한다고 한다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다