Skip to content

Instantly share code, notes, and snippets.

@overheadhunter
Last active July 4, 2022 18:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save overheadhunter/93a7da2ae51fa41cef3205223089e45e to your computer and use it in GitHub Desktop.
Save overheadhunter/93a7da2ae51fa41cef3205223089e45e to your computer and use it in GitHub Desktop.
TypeScript Key Derivation Functions ANSI-X9.63 and NIST SP 800-56A Rev. 2 ConcatKDF
/**
* KDF as defined in <a href="https://doi.org/10.6028/NIST.SP.800-56Ar2">NIST SP 800-56A Rev. 2 Section 5.8.1</a> using SHA-256
*
* @param z A shared secret
* @param keyDataLen Desired key length (in bytes)
* @param otherInfo Concatenated form of AlgorithmID || PartyUInfo || PartyVInfo {|| SuppPubInfo }{|| SuppPrivInfo }
* @returns key data
*/
public static async concatKDF(z: Uint8Array, keyDataLen: number, otherInfo: Uint8Array): Promise<Uint8Array> {
const hashLen = 32; // output length of SHA-256
const reps = Math.ceil(keyDataLen / hashLen);
if (reps >= 0xFFFFFFFF) {
throw new Error('unsupported keyDataLen');
}
if (4 + z.byteLength + otherInfo.byteLength > 0xFFFFFFFF) {
// technically max hash length for sha256 is 2^64-1 bits, but JS doesn't allow (safe) 64 bit numbers
// it is safe to restrict this kdf to smaller input lengths
throw new Error('unsupported input length');
}
const key = new Uint8Array(reps * hashLen);
const tmp = new ArrayBuffer(4 + z.byteLength + otherInfo.byteLength);
for (let i = 0; i < reps; i++) {
new DataView(tmp, 0, 4).setUint32(0, i + 1, false);
new Uint8Array(tmp).set(z, 4);
new Uint8Array(tmp).set(otherInfo, 4 + z.byteLength);
const digest = await crypto.subtle.digest('SHA-256', tmp);
key.set(new Uint8Array(digest), i * hashLen);
}
return key.slice(0, keyDataLen);
}
/**
* Performs <a href="https://www.secg.org/sec1-v2.pdf">ANSI-X9.63-KDF</a> with SHA-256
* @param sharedSecret A shared secret
* @param sharedInfo Additional authenticated data
* @param keyDataLen Desired key length (in bytes)
* @return key data
*/
public static async x963KDF(sharedSecret: Uint8Array, sharedInfo: Uint8Array, keyDataLen: number): Promise<Uint8Array> {
const hashLen = 32;
const n = Math.ceil(keyDataLen / hashLen);
const buffer = new Uint8Array(n * hashLen);
const tmp = new ArrayBuffer(sharedSecret.byteLength + 4 + sharedInfo.byteLength);
for (let i = 0; i < n; i++) {
new Uint8Array(tmp).set(sharedSecret, 0);
new DataView(tmp, sharedSecret.byteLength, 4).setUint32(0, i + 1, false);
new Uint8Array(tmp).set(sharedInfo, sharedSecret.byteLength + 4);
const digest = await crypto.subtle.digest('SHA-256', tmp);
buffer.set(new Uint8Array(digest), i * hashLen);
}
return buffer.slice(0, keyDataLen);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment