SkillAgentSearch skills...

MFKDF

JavaScript Implementation of a Next-Generation Multi-Factor Key Derivation Function (MFKDF2)

Install / Use

/learn @multifactor/MFKDF
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<img src="https://raw.githubusercontent.com/multifactor/MFKDF/main/logo.png" height="64">

Next-Generation Multi-Factor Key Derivation Function (MFKDF2)

GitHub issues Coverage Tests BSD GitHub tag GitHub release NPM release

Site | Docs | Demo | Videos | Contributing | Security | Multifactor | Paper | Author

The Next-Generation Multi-Factor Key Derivation Function (MFKDF2) is a function that takes multiple inputs and outputs a string of bytes that can be used as a cryptographic key. It serves the same purpose as a password-based key derivation function (PBKDF), but is stronger than password-based key derivation due to its support for multiple authentication factors, including HOTP, TOTP, and hardware tokens like YubiKey. MFKDF2 also enables self-service account recovery via K-of-N (secret-sharing style) key derivation, eliminating the need for central recovery keys, and supports arbitrarily complex key derivation policies. It builds on the now-deprecated original MFKDF.

Contents

Introduction

Password-based key derivation functions (eg. PBKDF2) are used to derive cryptographic keys from a password. Doing so allows users to encrypt secrets on the client side without having to worry about key management. But most users have notoriously insecure passwords, with up to 81% of them re-using passwords across multiple accounts. Even when multi-factor authentication is used to protect an account with a weak password, and password-derived keys are only as secure as the passwords they're based on.

The multi-factor key derivation function (MFKDF) improves upon password-based key derivation by using all of a user's authentication factors, not just their password, to derive a key. This library provides four key advantages over current password-based key derivation techniques:

  1. Beyond passwords: supports deriving key material from a variety of common factors, including HOTP, TOTP, and hardware tokens like YubiKey.
  2. Increased entropy: all factors must be simultaneously correct to derive a key, exponentially increasing the difficulty of brute-force attacks.
  3. Self-service recovery: threshold keys can be used to recover lost factors on the client side without creating a centralized point of failure.
  4. Authentication policies: multi-factor derived keys can cryptographically enforce arbitrarily complex authentication policies.

Getting Started

Download MFKDF.js

There are three ways to add mfkdf.js to your project: self-hosted, using a CDN, or using NPM (recommended).

Option 1: Self-Hosted

First download the latest release on GitHub, then add mfkdf.js or mfkdf.min.js to your page like so:

<script src="mfkdf.min.js"></script>

Option 2: CDN

You can automatically include the latest version of mfkdf.min.js in your page like so:

<script src="https://cdn.jsdelivr.net/gh/multifactor/mfkdf/mfkdf.min.js"></script>

Note that this may automatically update to include breaking changes in the future. Therefore, it is recommended that you get the latest single-version tag with SRI from jsDelivr instead.

Option 3: NPM (recommended)

Add MFKDF to your NPM project:

npm install mfkdf

Require MFKDF like so:

const mfkdf = require('mfkdf');

Migrating

MFKDF2 retains as much backwards-compatibility as possible with the original MFKDF API, but makes the following breaking changes compared to the original MFKDF:

  • Removed ISO key-based authentication, we recommend use of MFCHF2 instead
  • Removed support for enveloped secrets and keys, we recommend deriving sub-keys or using external secret storage
  • Removed support for KDFs other than argon2id; any argon2 params higher than (but not lower than) OWASP defaults are supported
  • Removed support for custom key sizes; derived keys are always 256 bits, and can be stretched or truncated from there

Additionally, we've made a number of major security and feature improvements, including:

  • A number of security improvements, including share encryption, policy integrity, and per-factor salting
  • Key derivation parameters can be hardened over time without changing the key
  • Support for Passkeys as a factor via the WebAuthn PRF extension
  • Support for deriving passwords from an MFKDF2-derived key (via MFDPG2)
  • Optional support for timing oracles to harden TOTP factor construction

In general, MFKDF2 is more opinionated than the original MFKDF, with the goal of being more secure by default and making insecure design decisions harder, at the cost of some flexibility. It also focuses on key derivation has less anscillary features, offloading cryptographic use of derived keys to external libraries in order to improve this library's auditability and reduce its attack surface. As a result, it also removes many problematic dependencies from the original MFKDF library.

Multi-Factor Key Derivation

Setup Key

Before you can derive a multi-factor derived key, you must setup a "key policy," which is essentially just a JSON document which specifies how a key is derived and ensures the key is the same every time (as long as the factors are correct). Setting up this policy yourself is difficult and potentially dangerous if insecure configuration options are chosen; therefore, the setup.key utility is provided with safe defaults. You can use it like so:

// setup 16 byte 3-factor multi-factor derived key with a password, HOTP code, and UUID code
const setup = await mfkdf.setup.key([
  await mfkdf.setup.factors.password('password'),
  await mfkdf.setup.factors.hotp({ secret: Buffer.from('abcdefghijklmnopqrst') }),
  await mfkdf.setup.factors.uuid({ uuid: '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' })
])

Every factor in a multi-factor derived key must have a unique ID. If you use multiple factors of the same type, make sure to specify an ID like so:

const result = await mfkdf.setup.key([
  await mfkdf.setup.factors.password('Tr0ub4dour', { id: 'password1' }),
  await mfkdf.setup.factors.password('abcdefgh', { id: 'password2' })
])

Setup returns an MFKDFDerivedKey object. Therefore, you can now access the derived key directly:

setup.key.toString('hex') // -> 34d2…5771

Some of the factors you setup may have their own outputs at this stage. You can access them like so:

console.log(setup.outputs)
// -> {
//  password: { strength: { ... } },
//  hotp: { uri: 'otpauth://...', ... },
//  uuid: { uuid: '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' }
// }

You can also save the resulting key policy for later use like so:

// save key policy
const policy = JSON.stringify(setup.policy)

Derive Key

Later, you can derive the same key using the saved key policy and established factors:

// derive key using the 3 factors
const derive = await mfkdf.derive.key(JSON.parse(policy), {
  password: mfkdf.derive.factors.password('password'),
  hotp: mfkdf.derive.factors.hotp(241063),
  uuid: mfkdf.derive.factors.uuid('9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d')
})

Derive also returns an MFKDFDerivedKey object. Therefore, you can again access the derived key directly like so:

// key should be the same if correct factors are provided
derive.key.toString('hex') // -> 34d2…5771

Some factors (like TOTP and HOTP) cause the key policy to change every time it is derived. Thus, don't forget to save the new key policy after deriving it:

// save new key policy
const newPolicy = JSON.stringify(derive.policy)

Factors

The following basic MFKDF factors are currently supported:

| Factor | Setup | Derive | | --------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | Password | setup.factors.password | derive.factors.password | | UUID | setup.factors.uuid | [derive.factors.uuid]

Related Skills

View on GitHub
GitHub Stars44
CategoryDevelopment
Updated1mo ago
Forks10

Languages

JavaScript

Security Score

90/100

Audited on Feb 11, 2026

No findings