Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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