All files Encrypt.ts

94.36% Statements 67/71
66.66% Branches 8/12
100% Functions 8/8
94.36% Lines 67/71

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 1021x               1x 1x   5x 5x 5x   1x         1x 3x 3x 3x 3x 3x 3x   1x 2x 2x           2x 2x 2x 2x 2x   1x 5x 5x 5x 5x   1x 1x 1x 1x 1x 1x 1x   1x   1x 2x 2x 1x   3x 3x 3x         3x 3x 3x 3x   3x   3x 3x   3x 3x 3x 3x 3x 3x   2x 2x 2x 2x 2x 2x 2x 2x   2x 2x   2x 2x  
import {
  createCipheriv,
  createDecipheriv,
  randomBytes,
  scryptSync,
} from "crypto";
import { SecretString } from "./Secret";
 
const ALGORITHM = "aes-256-cbc";
const DELIMITER = "#";
 
function makeKey(password: SecretString, salt: Buffer): Buffer {
  return scryptSync(password.value(), salt, 32);
}
 
export class EncryptedValue {
  cipherText: string;
  iv: Buffer;
  salt: Buffer;
 
  static makeFromPlainText(
    plainText: string,
    password: SecretString
  ): EncryptedValue {
    const { cipherText, iv, salt } = encrypt(plainText, password);
    return new EncryptedValue(cipherText, iv, salt);
  }
 
  static makeFromSerializedText(serializedText: string): EncryptedValue {
    const splited = serializedText.split(DELIMITER);
    if (splited.length != 3) {
      throw new Error(
        `invalid serializedText, splited length is not 3, original:${serializedText}, splited:${splited}`
      );
    }
 
    const iv = Buffer.from(splited[0] || "", "hex");
    const salt = Buffer.from(splited[1] || "", "hex");
    const cipherText = splited[2] || "";
    return new EncryptedValue(cipherText, iv, salt);
  }
 
  constructor(cipherText: string, iv: Buffer, salt: Buffer) {
    this.cipherText = cipherText;
    this.iv = iv;
    this.salt = salt;
  }
 
  serialize(): string {
    return (
      this.iv.toString("hex") +
      DELIMITER +
      this.salt.toString("hex") +
      DELIMITER +
      this.cipherText
    );
  }
 
  decrypt(password: SecretString): SecretString {
    return decrypt(this.cipherText, password, this.salt, this.iv);
  }
}
 
function encrypt(
  plainText: string,
  password: SecretString
): {
  cipherText: string;
  iv: Buffer;
  salt: Buffer;
} {
  const iv = randomBytes(16);
  const salt = randomBytes(16);
  const key = makeKey(password, salt);
 
  const cipher = createCipheriv(ALGORITHM, key, iv);
 
  let cipherText = cipher.update(plainText, "utf8", "hex");
  cipherText += cipher.final("hex");
 
  return {
    cipherText,
    iv,
    salt,
  };
}
 
function decrypt(
  cipherText: string,
  password: SecretString,
  salt: Buffer,
  iv: Buffer
): SecretString {
  const key = makeKey(password, salt);
  const decipher = createDecipheriv(ALGORITHM, key, iv);
 
  let plainText = decipher.update(cipherText, "hex", "utf8");
  plainText += decipher.final("utf8");
 
  return new SecretString(plainText);
}