UNPKG

crypto-key-composer

Version:

A library to decompose and compose crypto keys of different types and formats

350 lines (282 loc) 10.1 kB
import { createBuffer } from 'node-forge/lib/util'; import sha1 from 'node-forge/lib/sha1'; import sha256 from 'node-forge/lib/sha256'; import sha512 from 'node-forge/lib/sha512'; import md5 from 'node-forge/lib/md5'; import pbkdf2 from 'node-forge/lib/pbkdf2'; import aes from 'node-forge/lib/aes'; import des from 'node-forge/lib/des'; import rc2 from 'node-forge/lib/rc2'; import randomBytes from './random'; import { uint8ArrayToBinaryString, binaryStringToUint8Array } from './binary'; import { UnsupportedAlgorithmError, DecryptionFailedError } from './errors'; const deriveKeyWithPbkdf2 = (password, params) => { const { salt, iterationCount, keyLength, prf } = params; const saltStr = uint8ArrayToBinaryString(salt); let prfMd; switch (prf) { case 'hmac-with-sha1': prfMd = sha1.create(); break; // TODO: node-forge doesn't have sha224 support, see: https://github.com/digitalbazaar/forge/issues/669 // case 'hmacWithSHA224': // prfMd = sha256.sha224.create(); // break; case 'hmac-with-sha256': prfMd = sha256.create(); break; case 'hmac-with-sha384': prfMd = sha512.sha384.create(); break; case 'hmac-with-sha512': prfMd = sha512.create(); break; default: throw new UnsupportedAlgorithmError(`Unsupported PBKDF2 prf id '${prf}'`); } const keyStr = pbkdf2(password, saltStr, iterationCount, keyLength, prfMd); return binaryStringToUint8Array(keyStr); }; const deriveKeyWithOpensslDeriveBytes = (password, params) => { const { salt, keyLength } = params; const saltStr = uint8ArrayToBinaryString(salt); const md = md5.create(); const hash = bytes => md.start().update(bytes).digest().getBytes(); const digests = [hash(password + saltStr)]; for (let length = 16, i = 1; length < keyLength; i += 1, length += 16) { digests.push(hash(digests[i - 1] + password + saltStr)); } const digestStr = digests.join('').substr(0, keyLength); return binaryStringToUint8Array(digestStr); }; const decryptWithAes = (key, encryptedData, params) => { const { iv, mode } = params; const ivStr = uint8ArrayToBinaryString(iv); const keyStr = uint8ArrayToBinaryString(key); const cipher = aes.createDecryptionCipher(keyStr, mode); cipher.start(ivStr); cipher.update(createBuffer(uint8ArrayToBinaryString(encryptedData))); if (!cipher.finish()) { throw new DecryptionFailedError('Decryption failed, mostly likely the password is wrong'); } return binaryStringToUint8Array(cipher.output.getBytes()); }; const encryptWithAes = (key, data, params) => { const { iv, mode } = params; const ivStr = uint8ArrayToBinaryString(iv); const keyStr = uint8ArrayToBinaryString(key); const cipher = aes.createEncryptionCipher(keyStr, mode); cipher.start(ivStr); cipher.update(createBuffer(uint8ArrayToBinaryString(data))); cipher.finish(); return binaryStringToUint8Array(cipher.output.getBytes()); }; const decryptWithDes = (key, encryptedData, params) => { const { iv, mode } = params; const ivStr = uint8ArrayToBinaryString(iv); const keyStr = uint8ArrayToBinaryString(key); const cipher = des.createDecryptionCipher(keyStr, mode); cipher.start(ivStr); cipher.update(createBuffer(uint8ArrayToBinaryString(encryptedData))); if (!cipher.finish()) { throw new DecryptionFailedError('Decryption failed, mostly likely the password is wrong'); } return binaryStringToUint8Array(cipher.output.getBytes()); }; const encryptWithDes = (key, data, params) => { const { iv, mode } = params; const ivStr = uint8ArrayToBinaryString(iv); const keyStr = uint8ArrayToBinaryString(key); const cipher = des.createEncryptionCipher(keyStr, mode); cipher.start(ivStr); cipher.update(createBuffer(uint8ArrayToBinaryString(data))); cipher.finish(); return binaryStringToUint8Array(cipher.output.getBytes()); }; const decryptWithRc2 = (key, encryptedData, params) => { const { iv, bits } = params; const ivStr = uint8ArrayToBinaryString(iv); const keyStr = uint8ArrayToBinaryString(key); const cipher = rc2.createDecryptionCipher(keyStr, bits); cipher.start(ivStr); cipher.update(createBuffer(uint8ArrayToBinaryString(encryptedData))); if (!cipher.finish()) { throw new DecryptionFailedError('Decryption failed, mostly likely the password is wrong'); } return binaryStringToUint8Array(cipher.output.getBytes()); }; const encryptWithRc2 = (key, data, params) => { const { iv, bits } = params; const ivStr = uint8ArrayToBinaryString(iv); const keyStr = uint8ArrayToBinaryString(key); const cipher = rc2.createEncryptionCipher(keyStr, bits); cipher.start(ivStr); cipher.update(createBuffer(uint8ArrayToBinaryString(data))); cipher.finish(); return binaryStringToUint8Array(cipher.output.getBytes()); }; const getRc2KeyLength = bits => { // RC2-CBCParameter encoding of the "effective key bits" as defined in: // https://tools.ietf.org/html/rfc2898#appendix-B.2.3 switch (bits) { case 40: return 5; case 64: return 8; case 128: return 16; default: throw new UnsupportedAlgorithmError(`Unsupported RC2 bits parameter with value '${bits}'`); } }; export const decryptWithPassword = (encryptedData, encryptionAlgorithm, password) => { const { keyDerivationFunc, encryptionScheme } = encryptionAlgorithm; let deriveKeyFn; let derivedKeyLength; let decryptFn; // Process encryption scheme switch (encryptionScheme.id) { case 'aes128-cbc': case 'aes192-cbc': case 'aes256-cbc': decryptFn = key => decryptWithAes(key, encryptedData, { ...encryptionScheme, mode: 'CBC' }); derivedKeyLength = Number(encryptionScheme.id.match(/^aes(\d+)-/)[1]) / 8; break; case 'rc2-cbc': decryptFn = key => decryptWithRc2(key, encryptedData, encryptionScheme); derivedKeyLength = getRc2KeyLength(encryptionScheme.bits); break; case 'des-ede3-cbc': decryptFn = key => decryptWithDes(key, encryptedData, { ...encryptionScheme, mode: 'CBC' }); derivedKeyLength = 24; break; case 'des-cbc': decryptFn = key => decryptWithDes(key, encryptedData, { ...encryptionScheme, mode: 'CBC' }); derivedKeyLength = 8; break; default: throw new UnsupportedAlgorithmError(`Unsupported encryption scheme id '${encryptionScheme.id}'`); } // Process key derivation func switch (keyDerivationFunc.id) { case 'pbkdf2': deriveKeyFn = () => deriveKeyWithPbkdf2(password, { ...keyDerivationFunc, keyLength: keyDerivationFunc.keyLength || derivedKeyLength }); break; case 'openssl-derive-bytes': deriveKeyFn = () => deriveKeyWithOpensslDeriveBytes(password, { keyLength: derivedKeyLength, salt: encryptionScheme.iv.slice(0, 8) }); break; default: throw new UnsupportedAlgorithmError(`Unsupported key derivation function id '${keyDerivationFunc.id}'`); } const derivedKey = deriveKeyFn(); const decryptedData = decryptFn(derivedKey); return decryptedData; }; export const encryptWithPassword = (data, encryptionAlgorithm, password) => { const keyDerivationFunc = { ...encryptionAlgorithm.keyDerivationFunc }; const encryptionScheme = { ...encryptionAlgorithm.encryptionScheme }; let deriveKeyFn; let derivedKeyLength; let encryptFn; // Process encryption scheme switch (encryptionScheme.id) { case 'aes128-cbc': case 'aes192-cbc': case 'aes256-cbc': encryptionScheme.iv = encryptionScheme.iv || randomBytes(16); encryptFn = key => encryptWithAes(key, data, { ...encryptionScheme, mode: 'CBC' }); derivedKeyLength = Number(encryptionScheme.id.match(/^aes(\d+)-/)[1]) / 8; break; case 'rc2-cbc': encryptionScheme.bits = encryptionScheme.bits || 128; encryptionScheme.iv = encryptionScheme.iv || randomBytes(16); encryptFn = key => encryptWithRc2(key, data, encryptionScheme); derivedKeyLength = getRc2KeyLength(encryptionScheme.bits); break; case 'des-ede3-cbc': encryptionScheme.iv = encryptionScheme.iv || randomBytes(8); encryptFn = key => encryptWithDes(key, data, { ...encryptionScheme, mode: 'CBC' }); derivedKeyLength = 24; break; case 'des-cbc': encryptionScheme.iv = encryptionScheme.iv || randomBytes(8); encryptFn = key => encryptWithDes(key, data, { ...encryptionScheme, mode: 'CBC' }); derivedKeyLength = 8; break; default: throw new UnsupportedAlgorithmError(`Unsupported encryption scheme id '${encryptionScheme.id}'`); } // Process key derivation name switch (keyDerivationFunc.id) { case 'pbkdf2': if (keyDerivationFunc.keyLength != null && derivedKeyLength !== keyDerivationFunc.keyLength) { throw new UnsupportedAlgorithmError(`The specified key length must be equal to ${derivedKeyLength} (or omitted)`); } keyDerivationFunc.salt = keyDerivationFunc.salt || randomBytes(16); keyDerivationFunc.iterationCount = keyDerivationFunc.iterationCount || 10000; keyDerivationFunc.keyLength = keyDerivationFunc.keyLength || derivedKeyLength; keyDerivationFunc.prf = keyDerivationFunc.prf || 'hmac-with-sha512'; deriveKeyFn = () => deriveKeyWithPbkdf2(password, keyDerivationFunc); break; case 'openssl-derive-bytes': keyDerivationFunc.keyLength = derivedKeyLength; keyDerivationFunc.salt = encryptionScheme.iv.slice(0, 8); deriveKeyFn = () => deriveKeyWithOpensslDeriveBytes(password, keyDerivationFunc); break; default: throw new UnsupportedAlgorithmError(`Unsupported key derivation function id '${keyDerivationFunc.id}'`); } const derivedKey = deriveKeyFn(); const encryptedData = encryptFn(derivedKey); return { effectiveEncryptionAlgorithm: { keyDerivationFunc, encryptionScheme }, encryptedData }; };