Skip to content

Commit

Permalink
Merge pull request #30 from AraBlocks/kdf
Browse files Browse the repository at this point in the history
feat(kdf.js): Introduce key derivation functions
  • Loading branch information
LesterKim committed Nov 9, 2018
2 parents 09e29ca + 9356cf3 commit 7adfb1a
Show file tree
Hide file tree
Showing 6 changed files with 517 additions and 1 deletion.
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,69 @@ const server = kx.server({
})
```

### `crypto.kdf.keygen(key)` <a name="kdf-keygen"></a>

> **Stability: 2** - Stable
Generate a master ("secret") key. This function calls `crypto_kdf_keygen`
internally.

```js
const key = crypto.kdf.keygen()
```

### `crypto.kdf.init(key, buffer)` <a name="kdf-init"></a>

> **Stability: 2** - Stable
Initializes key and buffer with null subkey to return an object to update.

```js
const buffer = Buffer.from('examples')
const key = crypto.kdf.keygen()
const ctx = crypto.kdf.init(key, buffer) // buffer optional
```

### `crypto.kdf.update(ctx, id)` <a name="kdf-update"></a>

> **Stability: 2** - Stable
Updates context subkey from an ID. This function calls `crypto_kdf_derive`
internally.

```js
const buffer = Buffer.from('examples')
const key = crypto.kdf.keygen()
const ctx = crypto.kdf.init(key, buffer)
const subkey = crypto.kdf.update(ctx, 1)
```

### `crypto.kdf.final(ctx)` <a name="kdf-final"></a>

> **Stability: 2** - Stable
Finalizes context by setting `ctx.subkey` to `null`.

```js
const buffer = Buffer.from('examples')
const key = crypto.kdf.keygen()
const ctx = crypto.kdf.init(key, buffer)
const subkey = crypto.kdf.final(ctx)
```

### `crypto.kdf.derive(key, iterations, buffer)` <a name="kdf-derive"></a>

> **Stability: 2** - Stable
Derives a subkey from a key, number of iterations, and a context buffer.
This function calls `kdf.init`, `kdf.update`, and `kdf.final` internally.

```js
const buffer = Buffer.from('examples')
const key = crypto.kdf.keygen()
const subkey = crypto.kdf.derive(key, 1, buffer) // buffer optional
```

### `crypto.seal(message, opts)` <a name="seal"></a>

> **Stability: 2** - Stable
Expand Down Expand Up @@ -429,6 +492,7 @@ const mac = crypto.shash(message, key)
- [libsodium](https://download.libsodium.org/doc/)
- [sodium-universal](https://github.com/sodium-friends/sodium-universal)
- [ara-identity](https://github.com/arablocks/ara-identity)
- [Key derivation function](https://en.wikipedia.org/wiki/Key_derivation_function)
- [Stability index][stability-index]

## License
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const ed25519 = require('./ed25519')
const base58 = require('./base58')
const base64 = require('./base64')
const uint64 = require('./uint64')
const kdf = require('./kdf')
const sss = require('./sss')
const kx = require('./kx')

Expand All @@ -40,6 +41,7 @@ module.exports = {
sign,
seal,
box,
kdf,
sss,
kx,
}
181 changes: 181 additions & 0 deletions kdf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
const isBuffer = require('is-buffer')

/* eslint-disable camelcase */
const {
crypto_kdf_CONTEXTBYTES,
crypto_kdf_KEYBYTES,

crypto_kdf_derive_from_key,
crypto_kdf_keygen,
} = require('./sodium')

/**
* Generates a master key.
*
* @public
* @param {?(Buffer)} [key]
* @returns {Buffer}
* @throws TypeError
*/
function keygen(key) {
if (key && false === isBuffer(key)) {
throw new TypeError('kdf.keygen: Expecting key to be a buffer.')
}

if (undefined === key) {
key = Buffer.allocUnsafe(crypto_kdf_KEYBYTES)
}

if (crypto_kdf_KEYBYTES !== key.length) {
throw new TypeError(`kdf.keygen: Invalid key length: ${key.length}`)
}

crypto_kdf_keygen(key)
return key
}

/**
* Initializes key derivation.
*
* @public
* @param {Buffer} key
* @param {?(Buffer)} [buffer]
* @return {Object}
* @throws TypeError
*/
function init(key, buffer) {
if (key && false === isBuffer(key)) {
throw new TypeError('kdf.init: Expecting key to be a buffer.')
}

if (undefined === key) {
throw new TypeError('kdf.init: Expecting key to be defined.')
}

if (crypto_kdf_KEYBYTES !== key.length) {
throw new TypeError(`kdf.init: Invalid key length: ${key.length}`)
}

if (buffer) {
if (false === isBuffer(buffer)) {
throw new TypeError('kdf.init: Expecting context to be a buffer.')
}

if (crypto_kdf_CONTEXTBYTES !== buffer.length) {
throw new TypeError(`kdf.init: Invalid context length: ${buffer.length}`)
}
}

return {
buffer: buffer || Buffer.alloc(crypto_kdf_CONTEXTBYTES),
subkey: null,
key
}
}

/**
* Updates the subkey in the context object.
*
* @public
* @param {Object} ctx
* @param {Number} id
* @return {Buffer}
* @throws TypeError
*/
function update(ctx, id) {
if ('object' !== typeof ctx) {
throw new TypeError('kdf.update: Expecting ctx to be an object.')
}

if (ctx.subkey && !isBuffer(ctx.subkey)) {
throw new TypeError('kdf.update: Expecting ctx.subkey to be a buffer.')
}

if (!isBuffer(ctx.buffer)) {
throw new TypeError('kdf.update: Expecting ctx.buffer to be a buffer.')
}

if (crypto_kdf_CONTEXTBYTES !== ctx.buffer.length) {
throw new TypeError(`kdf.update: Invalid buffer length: ${ctx.buffer.length}.`)
}

if (!isBuffer(ctx.key)) {
throw new TypeError('kdf.update: Expecting ctx.key to be a buffer.')
}

if (crypto_kdf_KEYBYTES !== ctx.key.length) {
throw new TypeError(`kdf.update: Invalid buffer length: ${ctx.key.length}.`)
}

if ('number' !== typeof id) {
throw new TypeError('kdf.update: Expecting id to be a number.')
}

if (id < 0 || id > (2 ** 64) - 1) {
throw new TypeError('kdf.update: Expecting id to be between 0 and (2^64)-1.')
}

ctx.subkey = ctx.subkey || Buffer.allocUnsafe(crypto_kdf_KEYBYTES)
crypto_kdf_derive_from_key(ctx.subkey, id, ctx.buffer, ctx.key)

return ctx.subkey
}

/**
* Final step to null the original context subkey.
*
* @public
* @param {Object} ctx
* @return {Buffer}
* @throws TypeError
*/
function final(ctx) {
if ('object' !== typeof ctx) {
throw new TypeError('kdf.final: Expecting ctx to be an object.')
}

const { subkey } = ctx
ctx.subkey = null

return subkey
}

/**
* Derives a subkey using the master key and context.
*
* @public
* @param {Buffer} key
* @param {Number} iterations
* @param {?(Buffer)} [buffer]
* @return {Buffer}
* @throws TypeError
*/
function derive(key, iterations, buffer) {
const ctx = init(key, buffer)

if (iterations && 'number' !== typeof iterations) {
throw new TypeError('kdf.derive: Expecting subkeyId to be a number.')
}

if (iterations < 1 || iterations > (2 ** 64) - 1) {
throw new TypeError('kdf.derive: Expecting iterations to be between 1 and (2^64)-1.')
}

if (undefined === iterations) {
throw new TypeError('kdf.derive: Expecting iterations to be defined.')
}

for (let i = 0; i < iterations; ++i) {
update(ctx, i + 1)
}

return final(ctx)
}

module.exports = {
derive,
keygen,
update,
final,
init,
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ara-crypto",
"version": "0.7.1",
"version": "0.8.0",
"description": "Cryptographic functions used in Ara modules",
"main": "index.js",
"scripts": {
Expand Down
10 changes: 10 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ test.cb('crypto.box', (t) => {
t.end()
})

test.cb('crypto.kdf.keygen', (t) => {
t.true('function' === typeof crypto.kdf.keygen)
t.end()
})

test.cb('crypto.kdf.derive', (t) => {
t.true('function' === typeof crypto.kdf.derive)
t.end()
})

test.cb('crypto.kx', (t) => {
t.true('object' === typeof crypto.kx)
t.end()
Expand Down
Loading

0 comments on commit 7adfb1a

Please sign in to comment.