Skip to content

Commit

Permalink
Merge pull request #1 from erfanium/main
Browse files Browse the repository at this point in the history
Remove std/node dependency
  • Loading branch information
piyush-bhatt committed Jul 3, 2021
2 parents 94b0477 + 576df69 commit d92da42
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 82 deletions.
23 changes: 2 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ Deno module based on [crypto-random-string](https://github.com/sindresorhus/cryp
## Import Module

```typescript
import { cryptoRandomString, cryptoRandomStringAsync } from "https://deno.land/x/[email protected]/mod.ts"
import { cryptoRandomString } from "https://deno.land/x/[email protected]/mod.ts"
// or
import { cryptoRandomString, cryptoRandomStringAsync } from "https://github.com/piyush-bhatt/crypto-random-string/raw/main/mod.ts"
import { cryptoRandomString } from "https://github.com/piyush-bhatt/crypto-random-string/raw/main/mod.ts"
```

## Usage
Expand All @@ -21,36 +21,20 @@ import { cryptoRandomString, cryptoRandomStringAsync } from "https://github.com/

cryptoRandomString({length: 10}); // '0696cb9e70'

await cryptoRandomStringAsync({length: 10}); // 'c8d4b0140d'

cryptoRandomString({length: 10, type: 'base64'}); // 'dw3mgWC5uO'

await cryptoRandomStringAsync({length: 10, type: 'base64'}); // 'k6ALljZx+E'

cryptoRandomString({length: 10, type: 'url-safe'}); // '0pN1Y2Jz.X'

await cryptoRandomStringAsync({length: 10, type: 'url-safe'}); // '7.F5oBY9Qy'

cryptoRandomString({length: 10, type: 'numeric'}); // '1639380067'

await cryptoRandomStringAsync({length: 10, type: 'numeric'}); // '0923903115'

cryptoRandomString({length: 6, type: 'distinguishable'}); // 'H4HH5D'

await cryptoRandomStringAsync({length: 6, type: 'distinguishable'}); // 'D2Y254'

cryptoRandomString({length: 10, type: 'ascii-printable'}); // '#I&J.GP./9'

await cryptoRandomStringAsync({length: 10, type: 'ascii-printable'}); // '7t%FxZkyL('

cryptoRandomString({length: 10, type: 'alphanumeric'}); // 'ZtgC2J6aU5'

await cryptoRandomStringAsync({length: 10, type: 'alphanumeric'}); // 'FELQVN9S8H'

cryptoRandomString({length: 10, characters: 'abc'}); // 'abcabccbcc'

await cryptoRandomStringAsync({length: 10, characters: 'abc'}); // 'abcbbbacbb'

```

## API
Expand All @@ -59,9 +43,6 @@ await cryptoRandomStringAsync({length: 10, characters: 'abc'}); // 'abcbbbacbb'

Returns a randomized string. [Hex](https://en.wikipedia.org/wiki/Hexadecimal) by default.

### cryptoRandomStringAsync(options)

Returns a promise which resolves to a randomized string. [Hex](https://en.wikipedia.org/wiki/Hexadecimal) by default.

#### options

Expand Down
101 changes: 50 additions & 51 deletions cryptoRandomString.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { randomBytes } from "./deps.ts";
import { promisify } from "./deps.ts";
import {
ALLOWED_TYPES,
ALPHANUMERIC_CHARACTERS,
Expand All @@ -8,40 +6,49 @@ import {
NUMERIC_CHARACTERS,
URL_SAFE_CHARACTERS,
} from "./constants.ts";
import { encodeToBase64, encodeToHex } from "./deps.ts";

const randomBytesAsync = promisify(randomBytes);

const generateForCustomCharacters = (length: number, characters: string[]) => {
// Generating entropy is faster than complex math operations, so we use the simplest way
const characterCount = characters.length;
const maxValidSelector =
(Math.floor(0x10000 / characterCount) * characterCount) - 1; // Using values above this will ruin distribution when using modular division
const entropyLength = 2 * Math.ceil(1.1 * length); // Generating a bit more than required so chances we need more than one pass will be really low
let string = "";
let stringLength = 0;

while (stringLength < length) { // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
const entropy = randomBytes(entropyLength);
let entropyPosition = 0;
export interface GenerateRandomBytes {
(
byteLength: number,
type: "hex" | "base64",
length: number,
): string;
}

export interface GenerateForCustomCharacters {
(length: number, characters: string[]): string;
}

export const MAX_RANDOM_VALUES = 65536;
export const MAX_SIZE = 4294967295;

function randomBytes(size: number) {
if (size > MAX_SIZE) {
throw new RangeError(
`The value of "size" is out of range. It must be >= 0 && <= ${MAX_SIZE}. Received ${size}`,
);
}

while (entropyPosition < entropyLength && stringLength < length) {
const entropyValue = entropy.readUInt16LE(entropyPosition);
entropyPosition += 2;
if (entropyValue > maxValidSelector) { // Skip values which will ruin distribution when using modular division
continue;
}
const bytes = new Uint8Array(size);

string += characters[entropyValue % characterCount];
stringLength++;
//Work around for getRandomValues max generation
if (size > MAX_RANDOM_VALUES) {
for (let generated = 0; generated < size; generated += MAX_RANDOM_VALUES) {
crypto.getRandomValues(
bytes.subarray(generated, generated + MAX_RANDOM_VALUES),
);
}
} else {
crypto.getRandomValues(bytes);
}

return string;
};
return bytes;
}

const generateForCustomCharactersAsync = async (
length: number,
characters: string[],
const generateForCustomCharacters: GenerateForCustomCharacters = (
length,
characters,
) => {
// Generating entropy is faster than complex math operations, so we use the simplest way
const characterCount = characters.length;
Expand All @@ -52,11 +59,16 @@ const generateForCustomCharactersAsync = async (
let stringLength = 0;

while (stringLength < length) { // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
const entropy = await randomBytesAsync(entropyLength); // eslint-disable-line no-await-in-loop
const entropy = randomBytes(entropyLength);
let entropyPosition = 0;

while (entropyPosition < entropyLength && stringLength < length) {
const entropyValue = entropy.readUInt16LE(entropyPosition);
const entropyValue = new DataView(
entropy.buffer,
entropy.byteOffset,
entropy.byteLength,
)
.getUint16(entropyPosition, true);
entropyPosition += 2;
if (entropyValue > maxValidSelector) { // Skip values which will ruin distribution when using modular division
continue;
Expand All @@ -70,24 +82,15 @@ const generateForCustomCharactersAsync = async (
return string;
};

const generateRandomBytes = (
byteLength: number,
type: string,
length: number,
) => randomBytes(byteLength).toString(type).slice(0, length);

const generateRandomBytesAsync = async (
byteLength: number,
type: string,
length: number,
) => {
const buffer = await randomBytesAsync(byteLength);
return buffer.toString(type).slice(0, length);
const generateRandomBytes: GenerateRandomBytes = (byteLength, type, length) => {
const bytes = randomBytes(byteLength);
const str = type === "base64" ? encodeToBase64(bytes) : encodeToHex(bytes);
return str.slice(0, length);
};

const createGenerator = (
generateForCustomCharacters: Function,
generateRandomBytes: Function,
generateForCustomCharacters: GenerateForCustomCharacters,
generateRandomBytes: GenerateRandomBytes,
) =>
(
{ length, type, characters }: {
Expand Down Expand Up @@ -165,9 +168,5 @@ const cryptoRandomString = createGenerator(
generateForCustomCharacters,
generateRandomBytes,
);
const cryptoRandomStringAsync = createGenerator(
generateForCustomCharactersAsync,
generateRandomBytesAsync,
);

export { cryptoRandomString, cryptoRandomStringAsync };
export { cryptoRandomString };
4 changes: 2 additions & 2 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { randomBytes } from "https://deno.land/std@0.83.0/node/crypto.ts";
export { promisify } from "https://deno.land/std@0.83.0/node/util.ts";
export { encodeToString as encodeToHex } from "https://deno.land/std@0.99.0/encoding/hex.ts";
export { encode as encodeToBase64 } from "https://deno.land/std@0.99.0/encoding/base64.ts";
9 changes: 1 addition & 8 deletions mod_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assertEquals, assertMatch, assertThrows } from "./test_deps.ts";
import { cryptoRandomString, cryptoRandomStringAsync } from "./mod.ts";
import { cryptoRandomString } from "./mod.ts";

// Probabilistic, result is always less than or equal to actual set size, chance it is less is below 1e-256 for sizes up to 32656
const generatedCharacterSetSize = (
Expand All @@ -25,13 +25,6 @@ Deno.test("main", () => {
assertEquals(generatedCharacterSetSize({}, 16), 16);
});

Deno.test("async", async () => {
assertEquals((await cryptoRandomStringAsync({ length: 0 })).length, 0);
assertEquals((await cryptoRandomStringAsync({ length: 10 })).length, 10);
assertEquals((await cryptoRandomStringAsync({ length: 100 })).length, 100);
assertMatch(await cryptoRandomStringAsync({ length: 100 }), /^[a-f\d]*$/);
});

Deno.test("hex", () => {
assertEquals(cryptoRandomString({ length: 0, type: "hex" }).length, 0);
assertEquals(cryptoRandomString({ length: 10, type: "hex" }).length, 10);
Expand Down

0 comments on commit d92da42

Please sign in to comment.