All files / symbol-qr-library/src/services EncryptionService.ts

100% Statements 24/24
100% Branches 2/2
100% Functions 3/3
100% Lines 24/24

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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                              1x     1x                         1x                       1x           16x     16x           16x     16x             16x 16x   16x               1x           8x 8x     8x 8x     8x           8x           8x 8x   2x   6x   1x   1x  
/*
 * Copyright (c) 2019 NEM Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License ");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const CryptoJS = require("crypto-js");
 
// internal dependencies
import {
    EncryptedPayload,
} from '../../index';
 
/**
 * Class `EncryptionService` describes a high level service
 * for encryption/decryption of data.
 *
 * Implemented algorithms for encryption/decryption include:
 * - AES with PBKDF2 (Password-Based Key Derivation Function)
 *
 * @since 0.3.0
 */
class EncryptionService {
 
    /**
     * The `encrypt` method will encrypt given `data` raw string
     * with given `password` password.
     *
     * First we generate a random salt of 32 bytes, then we iterate
     * 2000 times with PBKDF2 and encrypt with AES.
     *
     * @param password {string}
     * @param data {string}
     */
    public static encrypt(
        data: string,
        password: string,
    ): EncryptedPayload {
 
        // create random salt (32 bytes)
        const salt = CryptoJS.lib.WordArray.random(32);
 
        // derive key of 8 bytes with 2000 iterations of PBKDF2
        const key = CryptoJS.PBKDF2(password, salt, {
          keySize: 8,
          iterations: 2000,
        });
 
        // create encryption input vector of 16 bytes (iv)
        const iv = CryptoJS.lib.WordArray.random(16);
 
        // encrypt with AES
        const encrypted = CryptoJS.AES.encrypt(data, key,  {
            iv: iv,
            padding: CryptoJS.pad.Pkcs7,
            mode: CryptoJS.mode.CBC,
        });
 
        // create our `EncryptedPayload` (16 bytes iv as hex || cipher text)
        const ciphertext = iv.toString() + encrypted.toString();
        const used_salt = CryptoJS.enc.Hex.stringify(salt);
 
        return new EncryptedPayload(ciphertext, used_salt);
    }
 
    /**
     * AES_PBKF2_decryption will decrypt privateKey with provided password
     * @param payload the object containing the encrypted data.
     * @param password the password to decrypt the encrypted data
     */
    public static decrypt(
        payload: EncryptedPayload,
        password: string,
    ): string {
 
        // read payload
        const salt = CryptoJS.enc.Hex.parse(payload.salt);
        const priv = payload.ciphertext;
 
        // read encryption configuration
        const iv: string = CryptoJS.enc.Hex.parse(priv.substr(0, 32));
        const cipher: string = priv.substr(32);
 
        // re-generate key (PBKDF2)
        const key = CryptoJS.PBKDF2(password, salt, {
          keySize: 8,
          iterations: 2000,
        });
 
        // decrypt and return
        const decrypted = CryptoJS.AES.decrypt(cipher, key, {
            iv: iv,
            padding: CryptoJS.pad.Pkcs7,
            mode: CryptoJS.mode.CBC,
        });
 
        const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
        if (!decryptedText){
            // This happens sometimes when the wrong password is used instead of an Error.
            throw Error('Empty decrypted text!!');
        }
        return decryptedText;
    }
}
 
export {EncryptionService};