diff --git a/source/node/dataPacking.ts b/source/node/dataPacking.ts index 4bcaa9b..90d741a 100644 --- a/source/node/dataPacking.ts +++ b/source/node/dataPacking.ts @@ -1,6 +1,7 @@ import { getBinarySignature, getBinaryContentBorder } from "../shared/signature"; import { SIZE_ENCODING_BYTES } from "../symbols"; import { + DerivationMethod, EncryptedBinaryComponents, EncryptedComponentsBase, EncryptedPayloadFooter, @@ -51,8 +52,9 @@ export function prepareFooter(encryptedComponents: EncryptedPayloadFooter): Buff export function prepareHeader(encryptedComponents: EncryptedPayloadHeader): Buffer { const signature = Buffer.from(getBinarySignature()); - const { iv, salt, rounds, method } = encryptedComponents; + const { derivation, iv, salt, rounds, method } = encryptedComponents; const componentsPrefix = JSON.stringify({ + derivation, iv, salt, rounds, @@ -103,10 +105,17 @@ export function unpackEncryptedData(encryptedContent: Buffer): EncryptedBinaryCo const footerData = encryptedContent.slice(offset, offset + footerSize); offset += footerSize; // Decode - const { iv, salt, rounds, method } = JSON.parse(headerData.toString("utf8")); + const { + derivation = DerivationMethod.PBKDF2_SHA256, + iv, + salt, + rounds, + method + } = JSON.parse(headerData.toString("utf8")); const { auth } = JSON.parse(footerData.toString("utf8")); return { content: contentBuff, + derivation, iv, salt, auth, diff --git a/source/node/derivation.ts b/source/node/derivation.ts index 61607b7..8b542c2 100644 --- a/source/node/derivation.ts +++ b/source/node/derivation.ts @@ -1,6 +1,6 @@ import { pbkdf2 as derivePBKDF2 } from "pbkdf2"; import { DERIVED_KEY_ALGORITHM, HMAC_KEY_SIZE, PASSWORD_KEY_SIZE } from "../symbols"; -import { DerivedKeyInfo } from "../types"; +import { DerivationMethod, DerivedKeyInfo } from "../types"; export async function deriveKeyFromPassword( password: string, @@ -17,6 +17,7 @@ export async function deriveKeyFromPassword( if (!rounds || rounds <= 0) { throw new Error("Failed deriving key: Rounds must be greater than 0"); } + const method = DerivationMethod.PBKDF2_SHA256; const bits = generateHMAC ? (PASSWORD_KEY_SIZE + HMAC_KEY_SIZE) * 8 : PASSWORD_KEY_SIZE * 8; const derivedKeyData = await pbkdf2(password, salt, rounds, bits); const derivedKeyHex = derivedKeyData.toString("hex"); @@ -25,12 +26,13 @@ export async function deriveKeyFromPassword( ? Buffer.from(derivedKeyHex.substr(0, dkhLength / 2), "hex") : Buffer.from(derivedKeyHex, "hex"); return { - salt: salt, - key: keyBuffer, - rounds: rounds, hmac: generateHMAC ? Buffer.from(derivedKeyHex.substr(dkhLength / 2, dkhLength / 2), "hex") - : null + : null, + key: keyBuffer, + method, + rounds: rounds, + salt: salt }; } diff --git a/source/node/stream.ts b/source/node/stream.ts index a6865a0..233051c 100644 --- a/source/node/stream.ts +++ b/source/node/stream.ts @@ -259,6 +259,7 @@ export function createEncryptStream(adapter: IocaneAdapter, password: string): W .then(({ iv, keyDerivationInfo }) => { const ivHex = iv.toString("hex"); const header = prepareHeader({ + derivation: keyDerivationInfo.method, iv: ivHex, salt: keyDerivationInfo.salt, rounds: keyDerivationInfo.rounds, diff --git a/source/shared/textPacking.ts b/source/shared/textPacking.ts index 444dfe8..131d90b 100644 --- a/source/shared/textPacking.ts +++ b/source/shared/textPacking.ts @@ -1,25 +1,47 @@ -import { EncryptedComponents, EncryptionAlgorithm, PackedEncryptedText } from "../types"; +import { + DerivationMethod, + EncryptedComponents, + EncryptionAlgorithm, + PackedEncryptedText +} from "../types"; import { ALGO_DEFAULT } from "../symbols"; +/** + * This is a legacy default from LONG ago - it is not used for new encryptions, and + * is only applied when decrypting an old string that never specified the number of + * rounds. + */ const PBKDF2_ROUND_DEFAULT = 1000; export function packEncryptedText(encryptedComponents: EncryptedComponents): PackedEncryptedText { - const { content, iv, salt, auth, rounds, method } = encryptedComponents; - return [content, iv, salt, auth, rounds, method].join("$"); + const { content, derivation, iv, salt, auth, rounds, method } = encryptedComponents; + return [content, iv, salt, auth, rounds, method, derivation].join("$"); } export function unpackEncryptedText(encryptedContent: PackedEncryptedText): EncryptedComponents { - const [content, iv, salt, auth, roundsRaw, methodRaw] = < - [string, string, string, string, string, EncryptionAlgorithm] + const [content, iv, salt, auth, roundsRaw, methodRaw, deriveMethodRaw] = < + [string, string, string, string, string, EncryptionAlgorithm, string] >encryptedContent.split("$"); // iocane was originally part of Buttercup's core package and used defaults from that originally. // There will be 4 components for pre 0.15.0 archives, and 5 in newer archives. The 5th component - // is the pbkdf2 round count, which is optional: + // is the pbkdf2 round count, which was optional: const rounds = roundsRaw ? parseInt(roundsRaw, 10) : PBKDF2_ROUND_DEFAULT; - // Originally only "cbc" was supported, but GCM was added in version 1 + // Encryption method: originally only "cbc" was supported, but GCM was added in version 1 const method = methodRaw || ALGO_DEFAULT; + // Derivation method: Original iocane strings used PBKDF2+SHA256 implicitly + const deriveMethodNum = deriveMethodRaw + ? parseInt(deriveMethodRaw, 10) + : DerivationMethod.PBKDF2_SHA256; + if (Object.values(DerivationMethod).includes(deriveMethodNum) === false) { + throw new Error( + `Failed unpacking encrypted content: Invalid key derivation type: ${deriveMethodRaw}` + ); + } + const derivation = deriveMethodNum as DerivationMethod; + // Return results return { content, + derivation, iv, salt, auth, diff --git a/source/types.ts b/source/types.ts index e50498c..276cbeb 100644 --- a/source/types.ts +++ b/source/types.ts @@ -4,11 +4,17 @@ export type BufferLike = Buffer | ArrayBuffer; export type DataLike = string | Buffer | ArrayBuffer; +export enum DerivationMethod { + PBKDF2_SHA256 = 1, + PBKDF2_SHA512 = 2 +} + export interface DerivedKeyInfo { - salt: string; - key: Buffer | ArrayBuffer; hmac: Buffer | ArrayBuffer | null; + key: Buffer | ArrayBuffer; + method: DerivationMethod; rounds: number; + salt: string; } export interface EncryptedBinaryComponents extends EncryptedComponentsBase { @@ -26,10 +32,11 @@ export interface EncryptedPayloadFooter { } export interface EncryptedPayloadHeader { + derivation: DerivationMethod; iv: string; - salt: string; - rounds: number; method: EncryptionAlgorithm; + rounds: number; + salt: string; } export enum EncryptionAlgorithm { diff --git a/source/web/dataPacking.ts b/source/web/dataPacking.ts index f1f1967..ffce261 100644 --- a/source/web/dataPacking.ts +++ b/source/web/dataPacking.ts @@ -8,6 +8,7 @@ import { } from "./tools"; import { SIZE_ENCODING_BYTES } from "../symbols"; import { + DerivationMethod, EncryptedBinaryComponents, EncryptedComponentsBase, EncryptedPayloadFooter, @@ -58,8 +59,9 @@ export function prepareFooter(encryptedComponents: EncryptedPayloadFooter): Arra export function prepareHeader(encryptedComponents: EncryptedPayloadHeader): ArrayBuffer { const signature = new Uint8Array(getBinarySignature()).buffer; - const { iv, salt, rounds, method } = encryptedComponents; + const { derivation, iv, salt, rounds, method } = encryptedComponents; const componentsPrefix = JSON.stringify({ + derivation, iv, salt, rounds, @@ -115,10 +117,17 @@ export function unpackEncryptedData(encryptedContent: ArrayBuffer): EncryptedBin const footerData = encryptedContent.slice(offset, offset + footerSize); offset += footerSize; // Decode - const { iv, salt, rounds, method } = JSON.parse(arrayBufferToString(headerData)); + const { + derivation = DerivationMethod.PBKDF2_SHA256, + iv, + salt, + rounds, + method + } = JSON.parse(arrayBufferToString(headerData)); const { auth } = JSON.parse(arrayBufferToString(footerData)); return { content: contentBuff, + derivation, iv, salt, auth, diff --git a/source/web/derivation.ts b/source/web/derivation.ts index 4f56f08..359b450 100644 --- a/source/web/derivation.ts +++ b/source/web/derivation.ts @@ -5,7 +5,7 @@ import { stringToArrayBuffer } from "./tools"; import { HMAC_KEY_SIZE, PASSWORD_KEY_SIZE } from "../symbols"; -import { DerivedKeyInfo } from "../types"; +import { DerivationMethod, DerivedKeyInfo } from "../types"; function checkBrowserSupport() { if (!window.TextEncoder || !window.TextDecoder) { @@ -28,6 +28,7 @@ export async function deriveKeyFromPassword( if (!rounds || rounds <= 0) { throw new Error("Failed deriving key: Rounds must be greater than 0"); } + const method = DerivationMethod.PBKDF2_SHA256; const bits = generateHMAC ? (PASSWORD_KEY_SIZE + HMAC_KEY_SIZE) * 8 : PASSWORD_KEY_SIZE * 8; const derivedKeyData = await pbkdf2(password, salt, rounds, bits); const derivedKeyHex = arrayBufferToHexString(derivedKeyData); @@ -36,12 +37,13 @@ export async function deriveKeyFromPassword( ? hexStringToArrayBuffer(derivedKeyHex.substr(0, dkhLength / 2)) : hexStringToArrayBuffer(derivedKeyHex); return { - salt: salt, - key: keyBuffer, - rounds: rounds, hmac: generateHMAC ? hexStringToArrayBuffer(derivedKeyHex.substr(dkhLength / 2, dkhLength / 2)) - : null + : null, + key: keyBuffer, + method, + rounds: rounds, + salt }; }