add option to randomize keys

This commit is contained in:
marlon-erler
2026-09-30 17:33:53 +02:00
parent 24610af08e
commit c78c5747cf
246 changed files with 20560 additions and 33 deletions
+90
View File
@@ -0,0 +1,90 @@
import {type MergeExclusive} from 'type-fest';
type BaseOptions = {
/**
Length of the returned string.
Must be a non-negative integer.
This is the number of characters, so a string generated from a `characters` set with characters outside the [Basic Multilingual Plane](https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane), like emoji, has a larger `.length` than this.
*/
length: number;
};
type TypeOption = {
/**
Use only characters from a predefined set of allowed characters.
Cannot be set at the same time as the `characters` option.
@default 'hex'
The `distinguishable` set contains only uppercase characters that are not easily confused: `CDEHKMPRTUWXY012458`. It can be useful if you need to print out a short string that you'd like users to read and type back in with minimal errors. For example, reading a code off of a screen that needs to be typed into a phone to connect two devices.
The `ascii-printable` set contains all [printable ASCII characters](https://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters) except the space: ``!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~`` Useful for generating passwords where all possible ASCII characters should be used.
The `alphanumeric` set contains uppercase letters, lowercase letters, and digits: `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`. Useful for generating [nonce](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/nonce) values.
@example
```
cryptoRandomString({length: 10});
//=> '87fc70e2b9'
cryptoRandomString({length: 10, type: 'base64'});
//=> 'mhsX7xmIv/'
cryptoRandomString({length: 10, type: 'url-safe'});
//=> 'VEjfNW3Yej'
cryptoRandomString({length: 10, type: 'numeric'});
//=> '8314659141'
cryptoRandomString({length: 6, type: 'distinguishable'});
//=> 'CDEHKM'
cryptoRandomString({length: 10, type: 'ascii-printable'});
//=> '`#Rt8$IK>B'
cryptoRandomString({length: 10, type: 'alphanumeric'});
//=> 'DMuKL8YtE7'
```
*/
type?: 'hex' | 'base64' | 'url-safe' | 'numeric' | 'distinguishable' | 'ascii-printable' | 'alphanumeric';
};
type CharactersOption = {
/**
Use only characters from a custom set of allowed characters.
Cannot be set at the same time as the `type` option.
Each character is picked with equal probability, so repeating a character in the set makes it more likely to be picked. The length limits count Unicode characters, not UTF-16 code units, so characters outside the [Basic Multilingual Plane](https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane), like emoji, are handled correctly.
Minimum length: `1`
Maximum length: `65536`
@example
```
cryptoRandomString({length: 10, characters: '0123456789'});
//=> '8796225811'
```
*/
characters?: string;
};
export type Options = BaseOptions & MergeExclusive<TypeOption, CharactersOption>;
/**
Generate a [cryptographically strong](https://en.wikipedia.org/wiki/Strong_cryptography) random string.
@returns A randomized string.
@example
```
import cryptoRandomString from 'crypto-random-string';
cryptoRandomString({length: 10});
//=> '2cf05d94db'
```
*/
export default function cryptoRandomString(options: Options): string;
+104
View File
@@ -0,0 +1,104 @@
import {uint8ArrayToBase64, uint8ArrayToHex} from 'uint8array-extras';
// `crypto.getRandomValues` throws an error if too much entropy is requested at once. (https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#exceptions)
const maxBytesPerRequest = 65_536;
// Each character is picked with a single `Uint16` selector value, so a character set cannot be larger than the range of one.
const maxCharacterSetSize = 0x1_00_00;
function fillWithRandomValues(typedArray) {
const maxElementsPerRequest = maxBytesPerRequest / typedArray.BYTES_PER_ELEMENT;
for (let offset = 0; offset < typedArray.length; offset += maxElementsPerRequest) {
// `TypedArray#subarray` clamps the end index and shares the underlying buffer, so this fills the array in place without any copying.
crypto.getRandomValues(typedArray.subarray(offset, offset + maxElementsPerRequest));
}
return typedArray;
}
const randomBytes = byteLength => fillWithRandomValues(new Uint8Array(byteLength));
const generateForCustomCharacters = (length, characters) => {
// Generating entropy is faster than complex math operations, so we use the simplest way
const characterCount = characters.length;
const validSelectorCount = Math.floor(maxCharacterSetSize / characterCount) * characterCount; // Using values at or above this will ruin distribution when using modular division
// Generating a bit more than required, adjusted for how many values we expect to discard, so we usually only need one pass
const entropyLength = Math.ceil(1.1 * length * (maxCharacterSetSize / validSelectorCount));
let string = '';
let stringLength = 0;
while (stringLength < length) {
const entropy = fillWithRandomValues(new Uint16Array(entropyLength));
for (let index = 0; index < entropyLength; index++) {
const entropyValue = entropy[index];
if (entropyValue < validSelectorCount) { // Skip values which will ruin distribution when using modular division
string += characters[entropyValue % characterCount];
stringLength++;
if (stringLength === length) {
return string;
}
}
}
}
return string;
};
const characterSets = new Map([
['url-safe', [...'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~']],
['numeric', [...'0123456789']],
['distinguishable', [...'CDEHKMPRTUWXY012458']],
['ascii-printable', [...'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~']],
['alphanumeric', [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789']],
]);
const allowedTypes = new Set(['hex', 'base64', ...characterSets.keys()]);
export default function cryptoRandomString({length, type, characters}) {
if (!Number.isSafeInteger(length) || length < 0) {
throw new TypeError('Expected `length` to be a non-negative integer');
}
if (type !== undefined && characters !== undefined) {
throw new TypeError('Expected either `type` or `characters`');
}
if (characters !== undefined) {
if (typeof characters !== 'string') {
throw new TypeError('Expected `characters` to be a string');
}
// Spread to keep characters outside the Basic Multilingual Plane, like emoji, intact.
const customCharacterSet = [...characters];
if (customCharacterSet.length === 0) {
throw new TypeError('Expected `characters` to contain at least 1 character');
}
if (customCharacterSet.length > maxCharacterSetSize) {
throw new TypeError(`Expected \`characters\` to contain at most ${maxCharacterSetSize} characters, got ${customCharacterSet.length}`);
}
return generateForCustomCharacters(length, customCharacterSet);
}
if (type !== undefined && !allowedTypes.has(type)) {
throw new TypeError(`Unknown type: ${type}`);
}
const characterSet = characterSets.get(type);
if (characterSet !== undefined) {
return generateForCustomCharacters(length, characterSet);
}
if (type === 'base64') {
return uint8ArrayToBase64(randomBytes(Math.ceil(length * 0.75))).slice(0, length); // Needs 0.75 bytes of entropy per character
}
return uint8ArrayToHex(randomBytes(Math.ceil(length * 0.5))).slice(0, length); // Needs 0.5 bytes of entropy per character
}
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
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.
+54
View File
@@ -0,0 +1,54 @@
{
"name": "crypto-random-string",
"version": "6.0.0",
"description": "Generate a cryptographically strong random string",
"license": "MIT",
"repository": "sindresorhus/crypto-random-string",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": {
"types": "./index.d.ts",
"default": "./index.js"
},
"sideEffects": false,
"engines": {
"node": ">=22"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"random",
"string",
"text",
"id",
"identifier",
"slug",
"salt",
"pin",
"crypto",
"strong",
"secure",
"hex",
"secret",
"protect"
],
"dependencies": {
"type-fest": "^5.8.0",
"uint8array-extras": "^1.5.0"
},
"devDependencies": {
"ava": "^8.0.1",
"tsd": "^0.33.0",
"xo": "^4.0.0"
}
}
+100
View File
@@ -0,0 +1,100 @@
# crypto-random-string
> Generate a [cryptographically strong](https://en.wikipedia.org/wiki/Strong_cryptography) random string
Can be useful for creating an identifier, slug, salt, PIN code, fixture, etc.
Works in Node.js and browsers.
## Install
```sh
npm install crypto-random-string
```
## Usage
```js
import cryptoRandomString from 'crypto-random-string';
cryptoRandomString({length: 10});
//=> '2cf05d94db'
cryptoRandomString({length: 10, type: 'base64'});
//=> 'YMiMbaQl6I'
cryptoRandomString({length: 10, type: 'url-safe'});
//=> 'YN-tqc8pOw'
cryptoRandomString({length: 10, type: 'numeric'});
//=> '8314659141'
cryptoRandomString({length: 6, type: 'distinguishable'});
//=> 'CDEHKM'
cryptoRandomString({length: 10, type: 'ascii-printable'});
//=> '`#Rt8$IK>B'
cryptoRandomString({length: 10, type: 'alphanumeric'});
//=> 'DMuKL8YtE7'
cryptoRandomString({length: 10, characters: 'abc'});
//=> 'abaaccabac'
```
## API
### cryptoRandomString(options)
Returns a randomized string. [Hex](https://en.wikipedia.org/wiki/Hexadecimal) by default.
#### options
Type: `object`
##### length
*Required*\
Type: `number` *(non-negative integer)*
Length of the returned string.
This is the number of characters, so a string generated from a `characters` set with characters outside the [Basic Multilingual Plane](https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane), like emoji, has a larger `.length` than this.
##### type
Type: `string`\
Default: `'hex'`\
Values: `'hex' | 'base64' | 'url-safe' | 'numeric' | 'distinguishable' | 'ascii-printable' | 'alphanumeric'`
Use only characters from a predefined set of allowed characters.
Cannot be set at the same time as the `characters` option.
The `distinguishable` set contains only uppercase characters that are not easily confused: `CDEHKMPRTUWXY012458`. It can be useful if you need to print out a short string that you'd like users to read and type back in with minimal errors. For example, reading a code off of a screen that needs to be typed into a phone to connect two devices.
The `ascii-printable` set contains all [printable ASCII characters](https://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters) except the space: ``!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~`` Useful for generating passwords where all possible ASCII characters should be used.
The `alphanumeric` set contains uppercase letters, lowercase letters, and digits: `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`. Useful for generating [nonce](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/nonce) values.
##### characters
Type: `string`\
Minimum length: `1`\
Maximum length: `65536`
Use only characters from a custom set of allowed characters.
Cannot be set at the same time as the `type` option.
Each character is picked with equal probability, so repeating a character in the set makes it more likely to be picked. The length limits count Unicode characters, not UTF-16 code units, so characters outside the [Basic Multilingual Plane](https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane), like emoji, are handled correctly.
## Related
- [random-int](https://github.com/sindresorhus/random-int) - Generate a random integer
- [random-float](https://github.com/sindresorhus/random-float) - Generate a random float
- [random-item](https://github.com/sindresorhus/random-item) - Get a random item from an array
- [random-boolean](https://github.com/arthurvr/random-boolean) - Get a random boolean
- [random-object-key](https://github.com/sindresorhus/random-object-key) - Get a random key from an object
- [random-object-value](https://github.com/sindresorhus/random-object-value) - Get a random value from an object
- [unique-random](https://github.com/sindresorhus/unique-random) - Generate random numbers that are consecutively unique