initial commit. phase 1 complete

This commit is contained in:
2026-05-05 20:45:19 +02:00
parent d9c68313a0
commit 89e058ffac
20631 changed files with 3224610 additions and 43 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2021 Filip Skokan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+72
View File
@@ -0,0 +1,72 @@
# hkdf
> HKDF with no dependencies using runtime's native crypto
HKDF is a simple key derivation function defined in [RFC 5869][].
## Documentation
**hkdf**(`digest`, `ikm`, `salt`, `info`, `keylen`): `Promise`<`Uint8Array`\>
The given `ikm`, `salt` and `info` are used with the `digest` to derive a key of `keylen` bytes.
### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `digest` | ``"sha256"`` \| ``"sha384"`` \| ``"sha512"`` \| ``"sha1"`` | The digest algorithm to use. |
| `ikm` | `Uint8Array` \| `string` | The input keying material. It must be at least one byte in length. |
| `salt` | `Uint8Array` \| `string` | The salt value. Must be provided but can be zero-length. |
| `info` | `Uint8Array` \| `string` | Additional info value. Must be provided but can be zero-length, and cannot be more than 1024 bytes. |
| `keylen` | `number` | The length in bytes of the key to generate. Must be greater than 0 and no more than 255 times the digest size. |
### Returns
`Promise`<`Uint8Array`\>
### Example
**`example`** ESM import
```js
import hkdf from '@panva/hkdf'
```
**`example`** CJS import
```js
const { hkdf } = require('@panva/hkdf')
```
**`example`** Deno import
```js
import hkdf from 'https://deno.land/x/hkdf/index.ts'
```
**`example`** Usage
```js
const derivedKey = await hkdf(
'sha256',
'key',
'salt',
'info',
64
)
```
## Supported Runtimes
The supported JavaScript runtimes include ones that
- are reasonably up to date ECMAScript
- support the utilized Web API globals and standard built-in objects
- These are
- _(This is not an exhaustive list)_
- Browsers
- Cloudflare Workers
- Deno
- Electron
- Netlify Edge Functions
- Next.js Middlewares
- Node.js
- Vercel Edge Functions
[RFC 5869]: https://www.rfc-editor.org/rfc/rfc5869.html
+50
View File
@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = exports.hkdf = void 0;
const hkdf_js_1 = require("./runtime/hkdf.js");
function normalizeDigest(digest) {
switch (digest) {
case 'sha256':
case 'sha384':
case 'sha512':
case 'sha1':
return digest;
default:
throw new TypeError('unsupported "digest" value');
}
}
function normalizeUint8Array(input, label) {
if (typeof input === 'string')
return new TextEncoder().encode(input);
if (!(input instanceof Uint8Array))
throw new TypeError(`"${label}"" must be an instance of Uint8Array or a string`);
return input;
}
function normalizeIkm(input) {
const ikm = normalizeUint8Array(input, 'ikm');
if (!ikm.byteLength)
throw new TypeError(`"ikm" must be at least one byte in length`);
return ikm;
}
function normalizeInfo(input) {
const info = normalizeUint8Array(input, 'info');
if (info.byteLength > 1024) {
throw TypeError('"info" must not contain more than 1024 bytes');
}
return info;
}
function normalizeKeylen(input, digest) {
if (typeof input !== 'number' || !Number.isInteger(input) || input < 1) {
throw new TypeError('"keylen" must be a positive integer');
}
const hashlen = parseInt(digest.substr(3), 10) >> 3 || 20;
if (input > 255 * hashlen) {
throw new TypeError('"keylen" too large');
}
return input;
}
async function hkdf(digest, ikm, salt, info, keylen) {
return (0, hkdf_js_1.default)(normalizeDigest(digest), normalizeIkm(ikm), normalizeUint8Array(salt, 'salt'), normalizeInfo(info), normalizeKeylen(keylen, digest));
}
exports.hkdf = hkdf;
exports.default = hkdf;
+23
View File
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const crypto_1 = require("crypto");
exports.default = (digest, ikm, salt, info, keylen) => {
const hashlen = parseInt(digest.substr(3), 10) >> 3 || 20;
const prk = (0, crypto_1.createHmac)(digest, salt.byteLength ? salt : new Uint8Array(hashlen))
.update(ikm)
.digest();
const N = Math.ceil(keylen / hashlen);
const T = new Uint8Array(hashlen * N + info.byteLength + 1);
let prev = 0;
let start = 0;
for (let c = 1; c <= N; c++) {
T.set(info, start);
T[start + info.byteLength] = c;
T.set((0, crypto_1.createHmac)(digest, prk)
.update(T.subarray(prev, start + info.byteLength + 1))
.digest(), start);
prev = start;
start += hashlen;
}
return T.slice(0, keylen);
};
+16
View File
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const crypto = require("crypto");
const fallback_js_1 = require("./fallback.js");
let hkdf;
if (typeof crypto.hkdf === 'function' && !process.versions.electron) {
hkdf = async (...args) => new Promise((resolve, reject) => {
crypto.hkdf(...args, (err, arrayBuffer) => {
if (err)
reject(err);
else
resolve(new Uint8Array(arrayBuffer));
});
});
}
exports.default = async (digest, ikm, salt, info, keylen) => (hkdf || fallback_js_1.default)(digest, ikm, salt, info, keylen);
+46
View File
@@ -0,0 +1,46 @@
import derive from './runtime/hkdf.js';
function normalizeDigest(digest) {
switch (digest) {
case 'sha256':
case 'sha384':
case 'sha512':
case 'sha1':
return digest;
default:
throw new TypeError('unsupported "digest" value');
}
}
function normalizeUint8Array(input, label) {
if (typeof input === 'string')
return new TextEncoder().encode(input);
if (!(input instanceof Uint8Array))
throw new TypeError(`"${label}"" must be an instance of Uint8Array or a string`);
return input;
}
function normalizeIkm(input) {
const ikm = normalizeUint8Array(input, 'ikm');
if (!ikm.byteLength)
throw new TypeError(`"ikm" must be at least one byte in length`);
return ikm;
}
function normalizeInfo(input) {
const info = normalizeUint8Array(input, 'info');
if (info.byteLength > 1024) {
throw TypeError('"info" must not contain more than 1024 bytes');
}
return info;
}
function normalizeKeylen(input, digest) {
if (typeof input !== 'number' || !Number.isInteger(input) || input < 1) {
throw new TypeError('"keylen" must be a positive integer');
}
const hashlen = parseInt(digest.substr(3), 10) >> 3 || 20;
if (input > 255 * hashlen) {
throw new TypeError('"keylen" too large');
}
return input;
}
async function hkdf(digest, ikm, salt, info, keylen) {
return derive(normalizeDigest(digest), normalizeIkm(ikm), normalizeUint8Array(salt, 'salt'), normalizeInfo(info), normalizeKeylen(keylen, digest));
}
export { hkdf, hkdf as default };
+1
View File
@@ -0,0 +1 @@
{"type":"module","sideEffects":false}
+21
View File
@@ -0,0 +1,21 @@
import { createHmac } from 'crypto';
export default (digest, ikm, salt, info, keylen) => {
const hashlen = parseInt(digest.substr(3), 10) >> 3 || 20;
const prk = createHmac(digest, salt.byteLength ? salt : new Uint8Array(hashlen))
.update(ikm)
.digest();
const N = Math.ceil(keylen / hashlen);
const T = new Uint8Array(hashlen * N + info.byteLength + 1);
let prev = 0;
let start = 0;
for (let c = 1; c <= N; c++) {
T.set(info, start);
T[start + info.byteLength] = c;
T.set(createHmac(digest, prk)
.update(T.subarray(prev, start + info.byteLength + 1))
.digest(), start);
prev = start;
start += hashlen;
}
return T.slice(0, keylen);
};
+14
View File
@@ -0,0 +1,14 @@
import * as crypto from 'crypto';
import fallback from './fallback.js';
let hkdf;
if (typeof crypto.hkdf === 'function' && !process.versions.electron) {
hkdf = async (...args) => new Promise((resolve, reject) => {
crypto.hkdf(...args, (err, arrayBuffer) => {
if (err)
reject(err);
else
resolve(new Uint8Array(arrayBuffer));
});
});
}
export default async (digest, ikm, salt, info, keylen) => (hkdf || fallback)(digest, ikm, salt, info, keylen);
+2
View File
@@ -0,0 +1,2 @@
declare function hkdf(digest: 'sha256' | 'sha384' | 'sha512' | 'sha1' | string, ikm: Uint8Array | string, salt: Uint8Array | string, info: Uint8Array | string, keylen: number): Promise<Uint8Array>;
export { hkdf, hkdf as default };
+46
View File
@@ -0,0 +1,46 @@
import derive from './runtime/hkdf.js';
function normalizeDigest(digest) {
switch (digest) {
case 'sha256':
case 'sha384':
case 'sha512':
case 'sha1':
return digest;
default:
throw new TypeError('unsupported "digest" value');
}
}
function normalizeUint8Array(input, label) {
if (typeof input === 'string')
return new TextEncoder().encode(input);
if (!(input instanceof Uint8Array))
throw new TypeError(`"${label}"" must be an instance of Uint8Array or a string`);
return input;
}
function normalizeIkm(input) {
const ikm = normalizeUint8Array(input, 'ikm');
if (!ikm.byteLength)
throw new TypeError(`"ikm" must be at least one byte in length`);
return ikm;
}
function normalizeInfo(input) {
const info = normalizeUint8Array(input, 'info');
if (info.byteLength > 1024) {
throw TypeError('"info" must not contain more than 1024 bytes');
}
return info;
}
function normalizeKeylen(input, digest) {
if (typeof input !== 'number' || !Number.isInteger(input) || input < 1) {
throw new TypeError('"keylen" must be a positive integer');
}
const hashlen = parseInt(digest.substr(3), 10) >> 3 || 20;
if (input > 255 * hashlen) {
throw new TypeError('"keylen" too large');
}
return input;
}
async function hkdf(digest, ikm, salt, info, keylen) {
return derive(normalizeDigest(digest), normalizeIkm(ikm), normalizeUint8Array(salt, 'salt'), normalizeInfo(info), normalizeKeylen(keylen, digest));
}
export { hkdf, hkdf as default };
+1
View File
@@ -0,0 +1 @@
{"type":"module","sideEffects":false}
+18
View File
@@ -0,0 +1,18 @@
const getGlobal = () => {
if (typeof globalThis !== 'undefined')
return globalThis;
if (typeof self !== 'undefined')
return self;
if (typeof window !== 'undefined')
return window;
throw new Error('unable to locate global object');
};
export default async (digest, ikm, salt, info, keylen) => {
const { crypto: { subtle }, } = getGlobal();
return new Uint8Array(await subtle.deriveBits({
name: 'HKDF',
hash: `SHA-${digest.substr(3)}`,
salt,
info,
}, await subtle.importKey('raw', ikm, 'HKDF', false, ['deriveBits']), keylen << 3));
};
+49
View File
@@ -0,0 +1,49 @@
{
"name": "@panva/hkdf",
"version": "1.2.1",
"description": "HKDF with no dependencies using runtime's native crypto",
"keywords": [
"browser",
"cloudflare",
"deno",
"electron",
"hkdf",
"isomorphic",
"rfc5869",
"RFC 5869",
"universal",
"webcrypto",
"workers"
],
"homepage": "https://github.com/panva/hkdf",
"repository": "panva/hkdf",
"funding": {
"url": "https://github.com/sponsors/panva"
},
"license": "MIT",
"author": "Filip Skokan <panva.ip@gmail.com>",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"bun": "./dist/web/index.js",
"deno": "./dist/web/index.js",
"browser": "./dist/web/index.js",
"worker": "./dist/web/index.js",
"workerd": "./dist/web/index.js",
"import": "./dist/node/esm/index.js",
"require": "./dist/node/cjs/index.js"
},
"./package.json": "./package.json"
},
"main": "./dist/node/cjs/index.js",
"browser": "./dist/web/index.js",
"types": "./dist/types/index.d.ts",
"files": [
"dist/**/package.json",
"dist/**/*.js",
"dist/types/**/*.d.ts",
"!dist/types/runtime/*",
"!dist/deno/**/*"
]
}