0

The web crypto API defines two different KDF utilities, namely deriveBits and deriveKey. While the former allows you to specify the desired output length (RFC 5869 calls this parameter L), the latter doesn't seem to do so.

I need to use HKDF with SHA-512 (as the spec I'm implementing requires this) in order to derive an HMAC-SHA256 key. In other words, 256 output bits are sufficient; but only deriveBits seems to have a corresponding parameter:

const baseKey = await crypto.subtle.importKey('raw', new Uint8Array(32), { name: 'HKDF' }, false, ['deriveKey', 'deriveBits'])
const salt = new Uint8Array(16)
const info = new Uint8Array(8)

// deriveBits → derives 256 bit:
const rawHmacKey1 = await crypto.subtle.deriveBits({ name: 'HKDF', hash: 'SHA-512', salt: salt, info: info }, baseKey, 256);
const hmacKey1 = await crypto.subtle.importKey('raw', rawHmacKey1, { name: 'HMAC', hash: 'SHA-256' }, true, ['sign']);
const exported1 = new Uint8Array(await crypto.subtle.exportKey('raw', hmacKey1));
console.log(btoa(exported1)) // MTgxLDEyLDgsMTExLDI3LDI0OCw4NSw3OCw0MywxMCw5MywyNDIsMTksMTg5LDE5MSwxNzMsMTM2LDEwMCwyMSwyMjcsMzksMTI2LDE3OSwxOTYsNTAsODYsNjEsMjcsMTQzLDIxMiwxOTksMjAz

// deriveKey → derives 512 bit:
const hmacKey2 = await crypto.subtle.deriveKey({ name: 'HKDF', hash: 'SHA-512', salt: salt, info: info }, baseKey, { name: 'HMAC', hash: 'SHA-256' }, true, ['sign']);
const exported2 = new Uint8Array(await crypto.subtle.exportKey('raw', hmacKey2));
console.log(btoa(exported2)) // MTgxLDEyLDgsMTExLDI3LDI0OCw4NSw3OCw0MywxMCw5MywyNDIsMTksMTg5LDE5MSwxNzMsMTM2LDEwMCwyMSwyMjcsMzksMTI2LDE3OSwxOTYsNTAsODYsNjEsMjcsMTQzLDIxMiwxOTksMjAzLDUwLDE4Myw3Miw4OSwyMDQsMTcsMzksMTY5LDE4OCw5Niw3NSw5NSwxMTMsMjQ3LDExNywxOTQsNDYsMTY0LDIyOSwyMTMsNzUsMjE5LDI0NCwyMjYsMjAwLDE2MCw5NCwxNCw3MiwxNjMsMTcwLDEwMg==

Note that when using deriveKey I explicitly specified { name: 'HMAC', hash: 'SHA-256' } as key usage parameter. Regardless the derived key length seems to solely depend on the specified hash algorithm (SHA-512 → 64 bytes).

As I would prefer to use deriveKey, my question is: How do I specify the desired output key size?

1 Answer 1

1

The length is specified by the third parameter derivedKeyAlgorithm of deriveKey().

In the case of an HMAC, a HmacKeyGenParams is passed, which encapsulates the three parameters name, hash and length. If length is missing, as in your example, the block size of the hash function is used by default, which is 512 bits for SHA-256 (s. here) and thus explains the result of your test.

If you want a 256 bits key, simply specify length:

(async () => {

const baseKey = await crypto.subtle.importKey('raw', new Uint8Array(32), { name: 'HKDF' }, false, ['deriveKey', 'deriveBits'])
const salt = new Uint8Array(16)
const info = new Uint8Array(8)

// deriveBits → derives 256 bit:
const rawHmacKey1 = await crypto.subtle.deriveBits({ name: 'HKDF', hash: 'SHA-512', salt: salt, info: info }, baseKey, 256);
const hmacKey1 = await crypto.subtle.importKey('raw', rawHmacKey1, { name: 'HMAC', hash: 'SHA-256' }, true, ['sign']);
const exported1 = new Uint8Array(await crypto.subtle.exportKey('raw', hmacKey1));
console.log(btoa(exported1)) // MTgxLDEyLDgsMTExLDI3LDI0OCw4NSw3OCw0MywxMCw5MywyNDIsMTksMTg5LDE5MSwxNzMsMTM2LDEwMCwyMSwyMjcsMzksMTI2LDE3OSwxOTYsNTAsODYsNjEsMjcsMTQzLDIxMiwxOTksMjAz

// deriveKey → derives 256 bit:
const hmacKey2 = await crypto.subtle.deriveKey({ name: 'HKDF', hash: 'SHA-512', salt: salt, info: info }, baseKey, { name: 'HMAC', hash: 'SHA-256', length: 256 }, true, ['sign']);
const exported2 = new Uint8Array(await crypto.subtle.exportKey('raw', hmacKey2));
console.log(btoa(exported2)) // MTgxLDEyLDgsMTExLDI3LDI0OCw4NSw3OCw0MywxMCw5MywyNDIsMTksMTg5LDE5MSwxNzMsMTM2LDEwMCwyMSwyMjcsMzksMTI2LDE3OSwxOTYsNTAsODYsNjEsMjcsMTQzLDIxMiwxOTksMjAz

})();


Note that your Base64 encoding is incorrect. The code below shows a working implementation.

(async () => {

const baseKey = await crypto.subtle.importKey('raw', new Uint8Array(32), { name: 'HKDF' }, false, ['deriveKey', 'deriveBits'])
const salt = new Uint8Array(16)
const info = new Uint8Array(8)

// deriveBits → derives 256 bit:
const rawHmacKey1 = await crypto.subtle.deriveBits({ name: 'HKDF', hash: 'SHA-512', salt: salt, info: info }, baseKey, 256);
const hmacKey1 = await crypto.subtle.importKey('raw', rawHmacKey1, { name: 'HMAC', hash: 'SHA-256' }, true, ['sign']);
const exported1 = await crypto.subtle.exportKey('raw', hmacKey1);
console.log(ab2b64(exported1)) // tQwIbxv4VU4rCl3yE72/rYhkFeMnfrPEMlY9G4/Ux8s=

// deriveKey → derives 256 bit:
const hmacKey2 = await crypto.subtle.deriveKey({ name: 'HKDF', hash: 'SHA-512', salt: salt, info: info }, baseKey, { name: 'HMAC', hash: 'SHA-256', length: 256 }, true, ['sign']);
const exported2 = await crypto.subtle.exportKey('raw', hmacKey2);
console.log(ab2b64(exported2)) //tQwIbxv4VU4rCl3yE72/rYhkFeMnfrPEMlY9G4/Ux8s=

function ab2b64(arrayBuffer) {
    return window.btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)))
}

})();

Sign up to request clarification or add additional context in comments.

1 Comment

thanks that was too easy. Now I wish I had asked earlier. (btw I'm not actually using btoa, just tried to get a minimal example that can be reproduced in every browser)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.