Skip to content
Snippets Groups Projects
CryptoServiceNode.ts 5.55 KiB
// All functions are async in order to be compatible with the ICryptoService API
import md5 from "js-md5";

import * as crypto from "crypto";

import { AESGCMOutput, RSAKeys, ICryptoService } from "./ICryptoService";
import { ensureUint8Array } from "../../utils/common";

const AES_GCM_ALGO = "aes-256-gcm";

const getBytes = (
  value: string | ArrayBuffer,
  encoding: BufferEncoding
): Buffer => {
  let bytes;
  if (typeof value === "string") {
    const valueString = value as string;
    bytes = Buffer.from(valueString, encoding);
  } else {
    bytes = Buffer.from(value);
  }
  return bytes;
};

class CryptoServiceNode implements ICryptoService {
  public async encryptAESGCM(data: string): Promise<AESGCMOutput>;
  public async encryptAESGCM(data: ArrayBuffer): Promise<AESGCMOutput>;
  public async encryptAESGCM(
    data: string | ArrayBuffer,
    key?: Buffer,
    iv?: Buffer
  ): Promise<AESGCMOutput> {
    if (!key && !iv) {
      key = crypto.randomBytes(32);
      iv = crypto.randomBytes(12);
    }

    const cipher = crypto.createCipheriv(AES_GCM_ALGO, key, iv);

    let encrypted;
    if (typeof data === "string") {
      encrypted = cipher.update(data, "utf8");
    } else {
      encrypted = cipher.update(ensureUint8Array(data));
    }
    cipher.final();

    const authTag = cipher.getAuthTag();

    const encryptedWithTag = Buffer.concat([
      Buffer.from(encrypted),
      Buffer.from(authTag),
    ]);

    return {
      key: key,
      iv: iv,
      data: encryptedWithTag,
    };
  }

  public async decryptAESGCM(
    data: ArrayBuffer,
    key: ArrayBuffer,
    iv: ArrayBuffer
  ): Promise<string>;
  public async decryptAESGCM(
    data: ArrayBuffer,
    key: ArrayBuffer,
    iv: ArrayBuffer,
    returnBuffer: true
  ): Promise<ArrayBuffer>;
  public async decryptAESGCM(
    data: ArrayBuffer,
    key: ArrayBuffer,
    iv: ArrayBuffer,
    returnBuffer?: boolean
  ): Promise<string | ArrayBuffer> {
    const decipher = crypto.createDecipheriv(
      AES_GCM_ALGO,
      Buffer.from(key),
      Buffer.from(iv)
    );

    const authTag = data.slice(data.byteLength - 16, data.byteLength);

    const encrypted = data.slice(0, data.byteLength - 16);
    if (returnBuffer) {
      const decrypted: Buffer = decipher.update(ensureUint8Array(encrypted));
      decipher.setAuthTag(Buffer.from(authTag));
      return Buffer.concat([decrypted, decipher.final()]);
    }

    let str = decipher.update(ensureUint8Array(encrypted), null, "utf8");
    decipher.setAuthTag(Buffer.from(authTag));
    str += decipher.final("utf8");
    return str;
  }

  public async generateRSAKeys(): Promise<RSAKeys> {
    const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
      modulusLength: 4096,
      publicKeyEncoding: {
        type: "spki",
        format: "pem",
      },
      privateKeyEncoding: {
        type: "pkcs8",
        format: "pem",
      },
    });

    return {
      publicKeyPEM: publicKey,
      privateKeyPEM: privateKey,
    };
  }

  public async encryptRSA(
    publicKeyPEM: string,
    data: ArrayBuffer
  ): Promise<ArrayBuffer> {
    const encryptedData = crypto.publicEncrypt(
      {
        key: publicKeyPEM,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: "sha256",
      },
      // We convert the data string to a buffer using `Buffer.from`
      Buffer.from(data)
    );

    return encryptedData.buffer;
  }

  public async decryptRSA(
    privateKeyPEM: string,
    data: ArrayBuffer
  ): Promise<ArrayBuffer> {
    const encryptedData = crypto.privateDecrypt(
      {
        key: privateKeyPEM,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: "sha256",
      },
      // We convert the data string to a buffer using `Buffer.from`
      Buffer.from(data)
    );

    return encryptedData.buffer;
  }

  public async signRSA(
    privateKeyPEM: string,
    data: ArrayBuffer
  ): Promise<ArrayBuffer> {
    const sign = crypto.createSign("SHA256");
    sign.update(Buffer.from(data));
    sign.end();

    return sign.sign(privateKeyPEM).buffer;
  }

  public async SHA1(
    value: string | ArrayBuffer,
    encoding: BufferEncoding = "utf8"
  ): Promise<ArrayBuffer> {
    const bytes = getBytes(value, encoding);
    return crypto.createHash("sha1").update(bytes).digest();
  }

  public async SHA256(
    value: string | ArrayBuffer,
    encoding: BufferEncoding = "utf8"
  ): Promise<ArrayBuffer> {
    const bytes = getBytes(value, encoding);
    return crypto.createHash("sha256").update(bytes).digest();
  }

  public async SHA384(
    value: string | ArrayBuffer,
    encoding: BufferEncoding = "utf8"
  ): Promise<ArrayBuffer> {
    const bytes = getBytes(value, encoding);
    return crypto.createHash("sha384").update(bytes).digest();
  }

  public async SHA512(
    value: string | ArrayBuffer,
    encoding: BufferEncoding = "utf8"
  ): Promise<ArrayBuffer> {
    const bytes = getBytes(value, encoding);
    return crypto.createHash("sha512").update(bytes).digest();
  }

  public async MD5(
    value: string | ArrayBuffer,
    encoding: BufferEncoding = "utf8"
  ): Promise<ArrayBuffer> {
    const bytes = getBytes(value, encoding);
    return md5.arrayBuffer(bytes);
  }

  public async verifyRSASignature(
    publicKeyPEM: string,
    data: ArrayBuffer,
    signature: ArrayBuffer
  ): Promise<boolean> {
    const verify = crypto.createVerify("SHA256");
    verify.update(Buffer.from(data));
    verify.end();

    const publicKey = crypto.createPublicKey(
      Buffer.from(publicKeyPEM, "utf-8")
    );

    return verify.verify(publicKey, Buffer.from(signature));
  }
}

export default CryptoServiceNode;