Skip to content

Commit

Permalink
Add derivation type argument
Browse files Browse the repository at this point in the history
  • Loading branch information
perry-mitchell committed Dec 16, 2023
1 parent d70fc2a commit 69b3305
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 25 deletions.
13 changes: 11 additions & 2 deletions source/node/dataPacking.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getBinarySignature, getBinaryContentBorder } from "../shared/signature";
import { SIZE_ENCODING_BYTES } from "../symbols";
import {
DerivationMethod,
EncryptedBinaryComponents,
EncryptedComponentsBase,
EncryptedPayloadFooter,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions source/node/derivation.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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");
Expand All @@ -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
};
}

Expand Down
1 change: 1 addition & 0 deletions source/node/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
36 changes: 29 additions & 7 deletions source/shared/textPacking.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
15 changes: 11 additions & 4 deletions source/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
13 changes: 11 additions & 2 deletions source/web/dataPacking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from "./tools";
import { SIZE_ENCODING_BYTES } from "../symbols";
import {
DerivationMethod,
EncryptedBinaryComponents,
EncryptedComponentsBase,
EncryptedPayloadFooter,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions source/web/derivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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
};
}

Expand Down

0 comments on commit 69b3305

Please sign in to comment.