crypto-js to deno std/crypto

어제에 이어서 crypto-js의 AES.encrypt/AES.decrypt 를 구현해봅시다.

import { crypto } from "https://deno.land/std@0.208.0/crypto/mod.ts";
import {
  decodeBase64,
  encodeBase64,
} from "https://deno.land/std@0.208.0/encoding/base64.ts";

function encodeText(text: string): Uint8Array {
  return new TextEncoder().encode(text);
}

async function getSeasoning(
  passphrase: string,
  salt: ArrayBuffer,
): Promise<{ key: ArrayBuffer; iv: ArrayBuffer }> {
  const password = new Uint8Array([
    ...encodeText(passphrase),
    ...new Uint8Array(salt),
  ]);
  const md5Hashes: Array<Uint8Array> = [];
  let digest = password;
  for (let i = 0; i < 3; i++) {
    const hash = await crypto.subtle.digest("MD5", digest).then((h) =>
      new Uint8Array(h)
    );
    md5Hashes.push(hash);
    digest = new Uint8Array([...hash, ...password]);
  }
  return {
    key: new Uint8Array([...md5Hashes[0], ...md5Hashes[1]]),
    iv: md5Hashes[2],
  };
}

export async function encrypt(
  text: string,
  passphrase: string,
): Promise<string> {
  const salt = crypto.getRandomValues(new Uint8Array(8));
  const { key, iv } = await getSeasoning(passphrase, salt);
  const cryptoKey = await crypto.subtle.importKey(
    "raw",
    key,
    {
      name: "AES-CBC",
      length: 256,
    },
    true,
    ["encrypt"],
  );
  const contents = encodeText(text);
  const encrypted = await crypto.subtle.encrypt(
    {
      name: "AES-CBC",
      length: 256,
      iv,
    },
    cryptoKey,
    contents,
  ).then((buffer) => new Uint8Array(buffer));
  return encodeBase64(
    new Uint8Array([
      ...encodeText("Salted__"),
      ...salt,
      ...encrypted,
    ]),
  );
}

export async function decrypt(
  text: string,
  passphrase: string,
): Promise<string> {
  const decoded = decodeBase64(text);
  const salt = decoded.slice(8, 16);
  const { key, iv } = await getSeasoning(passphrase, salt);
  const cryptoKey = await crypto.subtle.importKey(
    "raw",
    key,
    {
      name: "AES-CBC",
      length: 256,
    },
    true,
    ["decrypt"],
  );
  const contents = decoded.slice(16);
  const decrypted = await crypto.subtle.decrypt(
    {
      name: "AES-CBC",
      length: 256,
      iv,
    },
    cryptoKey,
    contents,
  ).then((buffer) => new Uint8Array(buffer));
  return new TextDecoder().decode(decrypted);
}

테스트 코드

import CryptoJS from "npm:crypto-js";
import { decrypt, encrypt } from "./crypto.ts";
import * as assertions from "https://deno.land/std@0.208.0/assert/mod.ts";

const passphrase = "test";

Deno.test("Encryption Test", async () => {
  const originalText = "Hello World!";
  const cryptoJsEncrypted = CryptoJS.AES.encrypt(originalText, passphrase).toString();
  const cryptoEncrypted = await encrypt(originalText, passphrase);
  
  const cryptoJsDecrypted = CryptoJS.AES.decrypt(cryptoEncrypted, passphrase).toString(CryptoJS.enc.Utf8);
  const cryptoDecrypted = await decrypt(cryptoJsEncrypted, passphrase);

  assertions.assertEquals(cryptoJsDecrypted, originalText);
  assertions.assertEquals(cryptoDecrypted, originalText);
})

잘 작동한다.

답글 남기기

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