Skip to content

Instantly share code, notes, and snippets.

@santoshshinde2012
Last active November 29, 2023 18:17
Show Gist options
  • Save santoshshinde2012/1913bcb3d92b35b042ddcf28e0543413 to your computer and use it in GitHub Desktop.
Save santoshshinde2012/1913bcb3d92b35b042ddcf28e0543413 to your computer and use it in GitHub Desktop.

Revisions

  1. santoshshinde2012 revised this gist Sep 23, 2022. 1 changed file with 0 additions and 16 deletions.
    16 changes: 0 additions & 16 deletions Crypto.md
    Original file line number Diff line number Diff line change
    @@ -1,19 +1,3 @@
    ### Secure Client-Side Storage Data Using With Web Crypto (Typescript)

    #### 1. Steps to Encrypt

    - Create Password based Key using (PBKDF2)
    - Create an AES-GCM key
    - Encrypt the input data

    ##### 2. Steps to Decrypt

    - Extract salt, iv and encrypted data from encrypted string
    - Create Password based Key using (PBKDF2)
    - Create an AES-GCM key
    - Decrypt the input data


    ```
    export default class Crypto {
    // iterations: It must be a number and should be set as high as possible.
  2. santoshshinde2012 created this gist Sep 23, 2022.
    248 changes: 248 additions & 0 deletions Crypto.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,248 @@
    ### Secure Client-Side Storage Data Using With Web Crypto (Typescript)

    #### 1. Steps to Encrypt

    - Create Password based Key using (PBKDF2)
    - Create an AES-GCM key
    - Encrypt the input data

    ##### 2. Steps to Decrypt

    - Extract salt, iv and encrypted data from encrypted string
    - Create Password based Key using (PBKDF2)
    - Create an AES-GCM key
    - Decrypt the input data


    ```
    export default class Crypto {
    // iterations: It must be a number and should be set as high as possible.
    // So, the more is the number of iterations, the more secure the derived key will be,
    // but in that case it takes greater amount of time to complete.
    // number of interation - the value of 2145 is randomly chosen
    private static iteration = 10;
    // algorithm - AES 256 GCM Mode
    private static encryptionAlgorithm = "AES-GCM";
    // random initialization vector length
    private static ivLength = 12;
    // random salt length
    private static saltLength = 16;
    // digest: It is a digest algorithms of string type.
    private static digest = "SHA-256";
    // text encoder
    private static enc = new TextEncoder();
    // text decoder
    private static dec = new TextDecoder();
    /**
    *
    * @param u8
    * @returns
    */
    private static base64Encode(u8: Uint8Array): string {
    return btoa(String.fromCharCode.apply(undefined, [...u8]));
    }
    /**
    *
    * @param str
    * @returns
    */
    private static base64Decode(str: string): Uint8Array {
    return Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
    }
    /**
    *
    * @param secretKey
    * @returns
    */
    private static getPasswordKey(secretKey: string): Promise<CryptoKey> {
    return window.crypto.subtle.importKey(
    "raw",
    Crypto.enc.encode(secretKey),
    "PBKDF2",
    false,
    ["deriveKey"]
    );
    }
    /**
    *
    * @param passwordKey
    * @param salt
    * @param iteration
    * @param digest
    * @param encryptionAlgorithm
    * @param keyUsage
    * @returns
    */
    private static deriveKey(
    passwordKey: CryptoKey,
    salt: Uint8Array,
    iteration: number,
    digest: string,
    encryptionAlgorithm: string,
    keyUsage: ["encrypt"] | ["decrypt"]
    ): Promise<CryptoKey> {
    return window.crypto.subtle.deriveKey(
    {
    name: "PBKDF2",
    salt,
    iterations: iteration,
    hash: digest,
    },
    passwordKey,
    {
    name: encryptionAlgorithm,
    length: 256,
    },
    false,
    keyUsage
    );
    }
    /**
    *
    * @param secretKey
    * @param data
    * @returns
    */
    public static async encrypt(
    secretKey: string,
    data: string
    ): Promise<string> {
    try {
    // generate random salt
    const salt = window.crypto.getRandomValues(
    new Uint8Array(Crypto.saltLength)
    );
    // How to transport IV ?
    // Generally the IV is prefixed to the ciphertext or calculated using some kind of nonce on both sides.
    const iv = window.crypto.getRandomValues(new Uint8Array(Crypto.ivLength));
    // create master key from secretKey
    // The method gives an asynchronous Password-Based Key Derivation
    // Create a password based key (PBKDF2) that will be used to derive the AES-GCM key used for encryption
    const passwordKey = await Crypto.getPasswordKey(secretKey);
    // to derive a secret key from a master key for encryption
    // Create an AES-GCM key using the PBKDF2 key and a randomized salt value.
    const aesKey = await Crypto.deriveKey(
    passwordKey,
    salt,
    Crypto.iteration,
    Crypto.digest,
    Crypto.encryptionAlgorithm,
    ["encrypt"]
    );
    // create a Cipher object, with the stated algorithm, key and initialization vector (iv).
    // @algorithm - AES 256 GCM Mode
    // @key
    // @iv
    // @options
    // Encrypt the input data using the AES-GCM key and a randomized initialization vector (iv).
    const encryptedContent = await window.crypto.subtle.encrypt(
    {
    name: Crypto.encryptionAlgorithm,
    iv,
    },
    aesKey,
    Crypto.enc.encode(data)
    );
    // convert encrypted string to buffer
    const encryptedContentArr: Uint8Array = new Uint8Array(encryptedContent);
    // create buffer array with length [salt + iv + encryptedContentArr]
    const buff: Uint8Array = new Uint8Array(
    salt.byteLength + iv.byteLength + encryptedContentArr.byteLength
    );
    // set salt at first postion
    buff.set(salt, 0);
    // set iv at second postion
    buff.set(iv, salt.byteLength);
    // set encrypted at third postion
    buff.set(encryptedContentArr, salt.byteLength + iv.byteLength);
    // encode the buffer array
    const base64Buff: string = Crypto.base64Encode(buff);
    // return encrypted string
    return base64Buff;
    } catch (error) {
    // if any expection occurs
    console.error(`Error - ${error}`);
    return "";
    }
    }
    /**
    *
    * @param secretKey
    * @param ciphertext
    * @returns
    */
    public static async decrypt(secretKey: string, ciphertext: string) {
    try {
    // Creates a new Buffer containing the given JavaScript string {str}
    const encryptedDataBuff = Crypto.base64Decode(ciphertext);
    // extract salt from encrypted data
    const salt = encryptedDataBuff.slice(0, Crypto.saltLength);
    // extract iv from encrypted data
    const iv = encryptedDataBuff.slice(
    Crypto.saltLength,
    Crypto.saltLength + Crypto.ivLength
    );
    // extract encrypted text from encrypted data
    const data = encryptedDataBuff.slice(Crypto.saltLength + Crypto.ivLength);
    // create master key from secretKey
    // The method gives an asynchronous Password-Based Key Derivation
    // Create a password based key (PBKDF2) that will be used to derive the AES-GCM key used for decryption.
    const passwordKey = await Crypto.getPasswordKey(secretKey);
    // to derive a secret key from a master key for decryption
    // Create an AES-GCM key using the PBKDF2 key and the salt from the ArrayBuffer.
    const aesKey = await Crypto.deriveKey(
    passwordKey,
    salt,
    Crypto.iteration,
    Crypto.digest,
    Crypto.encryptionAlgorithm,
    ["decrypt"]
    );
    // Return the buffer containing the value of cipher object.
    // Decrypt the input data using the AES-GCM key and the iv from the ArrayBuffer.
    const decryptedContent = await window.crypto.subtle.decrypt(
    {
    name: Crypto.encryptionAlgorithm,
    iv,
    },
    aesKey,
    data
    );
    // Returns the result of running encoding's decoder.
    return Crypto.dec.decode(decryptedContent);
    } catch (error) {
    // if any expection occurs
    console.error(`Error - ${error}`);
    return "";
    }
    }
    }
    ````