All files Encrypt.ts

96.87% Statements 31/32
42.85% Branches 3/7
100% Functions 8/8
96.87% Lines 31/32

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 1x     4x     1x                 3x 3x       1x 1x           1x 1x 1x 1x       4x 4x 4x       1x                   1x                       3x 3x 3x   3x   3x 3x   3x                         1x 1x   1x 1x   1x    
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);
    Iif (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);
}