Skip to content

Commit

Permalink
Provide extracted key with encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
darkwing committed Oct 31, 2022
1 parent 59020ae commit 2b99fa8
Showing 1 changed file with 116 additions and 13 deletions.
129 changes: 116 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,69 @@
interface DetailedEncryptionResult {
vault: string;
exportedKeyString: string;
}

interface EncryptionResult {
data: string;
iv: string;
salt?: string;
}

interface DetailedDecryptResult {
exportedKeyString: string;
vault: unknown;
salt: string;
}

const EXPORT_FORMAT = 'jwk';
const DERIVED_KEY_FORMAT = 'AES-GCM';
const STRING_ENCODING = 'utf-8';

/**
* Encrypts a data object that can be any serializable value using
* a provided password.
*
* @param {string} password - password to use for encryption
* @param {R} dataObj - data to encrypt
* @param {CryptoKey} key - a CryptoKey instance
* @param {string} salt - salt used to encrypt
* @returns {Promise<string>} cypher text
*/
export async function encrypt<R>(
password: string,
dataObj: R,
key?: CryptoKey,
salt: string = generateSalt(),
): Promise<string> {
const salt = generateSalt();

const passwordDerivedKey = await keyFromPassword(password, salt);
const payload = await encryptWithKey(passwordDerivedKey, dataObj);
const cryptoKey = key || (await keyFromPassword(password, salt));
const payload = await encryptWithKey(cryptoKey, dataObj);
payload.salt = salt;
return JSON.stringify(payload);
}

/**
* Encrypts a data object that can be any serializable value using
* a provided password.
*
* @param {string} password - password to use for encryption
* @param {R} dataObj - data to encrypt
* @returns {Promise<DetailedEncryptionResult>} object with vault and exportedKeyString
*/
export async function encryptWithDetail<R>(
password: string,
dataObj: R,
): Promise<DetailedEncryptionResult> {
const salt = generateSalt();
const key = await keyFromPassword(password, salt);
const exportedKeyString = await exportKey(key);
const vault = await encrypt(password, dataObj, key, salt);

return {
vault,
exportedKeyString,
};
}

/**
* Encrypts the provided serializable javascript object using the
* provided CryptoKey and returns an object containing the cypher text and
Expand All @@ -37,12 +77,12 @@ export async function encryptWithKey<R>(
dataObj: R,
): Promise<EncryptionResult> {
const data = JSON.stringify(dataObj);
const dataBuffer = Buffer.from(data, 'utf-8');
const dataBuffer = Buffer.from(data, STRING_ENCODING);
const vector = global.crypto.getRandomValues(new Uint8Array(16));

const buf = await global.crypto.subtle.encrypt(
{
name: 'AES-GCM',
name: DERIVED_KEY_FORMAT,
iv: vector,
},
key,
Expand All @@ -63,12 +103,45 @@ export async function encryptWithKey<R>(
* the resulting value
* @param {string} password - password to decrypt with
* @param {string} text - cypher text to decrypt
* @param {CryptoKey} key - a key to use for decrypting
* @returns {object}
*/
export async function decrypt<R>(password: string, text: string): Promise<R> {
export async function decrypt(
password: string,
text: string,
key?: CryptoKey,
): Promise<unknown> {
const payload = JSON.parse(text);
const { salt } = payload;

const cryptoKey = key || (await keyFromPassword(password, salt));

const result = await decryptWithKey(cryptoKey, payload);
return result;
}

/**
* Given a password and a cypher text, decrypts the text and returns
* the resulting value, keyString, and salt
* @param {string} password - password to decrypt with
* @param {string} text - cypher text to decrypt
* @returns {object}
*/
export async function decryptWithDetail(
password: string,
text: string,
): Promise<DetailedDecryptResult> {
const payload = JSON.parse(text);
const { salt } = payload;
const key = await keyFromPassword(password, salt);
return await decryptWithKey(key, payload);
const exportedKeyString = await exportKey(key);
const vault = await decrypt(password, text, key);

return {
exportedKeyString,
vault,
salt,
};
}

/**
Expand All @@ -87,13 +160,13 @@ export async function decryptWithKey<R>(
let decryptedObj;
try {
const result = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: vector },
{ name: DERIVED_KEY_FORMAT, iv: vector },
key,
encryptedData,
);

const decryptedData = new Uint8Array(result);
const decryptedStr = Buffer.from(decryptedData).toString('utf-8');
const decryptedStr = Buffer.from(decryptedData).toString(STRING_ENCODING);
decryptedObj = JSON.parse(decryptedStr);
} catch (e) {
throw new Error('Incorrect password');
Expand All @@ -102,6 +175,36 @@ export async function decryptWithKey<R>(
return decryptedObj;
}

/**
* Receives an exported CryptoKey string and creates a key
* @param {string} keyString - keyString to import
* @returns {CryptoKey}
*/
export async function createKeyFromString(
keyString: string,
): Promise<CryptoKey> {
const key = await window.crypto.subtle.importKey(
EXPORT_FORMAT,
JSON.parse(keyString),
DERIVED_KEY_FORMAT,
true,
['encrypt', 'decrypt'],
);

return key;
}

/**
* Receives an exported CryptoKey string, creates a key,
* and decrypts cipher text with the reconstructed key
* @param {CryptoKey} key - key to export
* @returns {string}
*/
async function exportKey(key: CryptoKey): Promise<string> {
const exportedKey = await window.crypto.subtle.exportKey(EXPORT_FORMAT, key);
return JSON.stringify(exportedKey);
}

/**
* Generate a CryptoKey from a password and random salt
* @param {string} password - The password to use to generate key
Expand All @@ -111,7 +214,7 @@ export async function keyFromPassword(
password: string,
salt: string,
): Promise<CryptoKey> {
const passBuffer = Buffer.from(password, 'utf-8');
const passBuffer = Buffer.from(password, STRING_ENCODING);
const saltBuffer = Buffer.from(salt, 'base64');

const key = await global.crypto.subtle.importKey(
Expand All @@ -130,8 +233,8 @@ export async function keyFromPassword(
hash: 'SHA-256',
},
key,
{ name: 'AES-GCM', length: 256 },
false,
{ name: DERIVED_KEY_FORMAT, length: 256 },
true,
['encrypt', 'decrypt'],
);

Expand Down

0 comments on commit 2b99fa8

Please sign in to comment.