diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3921e01..a2c4c4a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,8 +12,8 @@ jobs: node-version: - 16 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/browser.js b/browser.js index e96a23a..a42c0c8 100644 --- a/browser.js +++ b/browser.js @@ -1,32 +1,23 @@ /* eslint-env browser */ import {createStringGenerator, createAsyncStringGenerator} from './core.js'; -const toHex = uInt8Array => uInt8Array.map(byte => byte.toString(16).padStart(2, '0')).join(''); - -const decoder = new TextDecoder('utf8'); -const toBase64 = uInt8Array => btoa(decoder.decode(uInt8Array)); +const toHex = uInt8Array => [...uInt8Array].map(byte => byte.toString(16).padStart(2, '0')).join(''); +const toBase64 = uInt8Array => btoa(String.fromCodePoint(...uInt8Array)); // `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 maxEntropy = 65_536; function getRandomValues(byteLength) { - const generatedBytes = []; - - while (byteLength > 0) { - const bytesToGenerate = Math.min(byteLength, maxEntropy); - generatedBytes.push(crypto.getRandomValues(new Uint8Array({length: bytesToGenerate}))); - byteLength -= bytesToGenerate; - } - - const result = new Uint8Array(generatedBytes.reduce((sum, {byteLength}) => sum + byteLength, 0)); - let currentIndex = 0; + const generatedBytes = new Uint8Array(byteLength); - for (const bytes of generatedBytes) { - result.set(bytes, currentIndex); - currentIndex += bytes.byteLength; + for (let totalGeneratedBytes = 0; totalGeneratedBytes < byteLength; totalGeneratedBytes += maxEntropy) { + generatedBytes.set( + crypto.getRandomValues(new Uint8Array(Math.min(maxEntropy, byteLength - totalGeneratedBytes))), + totalGeneratedBytes, + ); } - return result; + return generatedBytes; } function specialRandomBytes(byteLength, type, length) { diff --git a/core.js b/core.js index 2c77e93..6b0d5b1 100644 --- a/core.js +++ b/core.js @@ -4,6 +4,8 @@ const distinguishableCharacters = [...'CDEHKMPRTUWXY012458']; const asciiPrintableCharacters = [...'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~']; const alphanumericCharacters = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789']; +const readUInt16LE = (uInt8Array, offset) => uInt8Array[offset] + (uInt8Array[offset + 1] * (2 ** 8)); + const generateForCustomCharacters = (length, characters, randomBytes) => { // Generating entropy is faster than complex math operations, so we use the simplest way const characterCount = characters.length; @@ -17,7 +19,7 @@ const generateForCustomCharacters = (length, characters, randomBytes) => { let entropyPosition = 0; while (entropyPosition < entropyLength && stringLength < length) { - const entropyValue = entropy.readUInt16LE(entropyPosition); + const entropyValue = readUInt16LE(entropy, entropyPosition); entropyPosition += 2; if (entropyValue > maxValidSelector) { // Skip values which will ruin distribution when using modular division continue; @@ -44,7 +46,7 @@ const generateForCustomCharactersAsync = async (length, characters, randomBytesA let entropyPosition = 0; while (entropyPosition < entropyLength && stringLength < length) { - const entropyValue = entropy.readUInt16LE(entropyPosition); + const entropyValue = readUInt16LE(entropy, entropyPosition); entropyPosition += 2; if (entropyValue > maxValidSelector) { // Skip values which will ruin distribution when using modular division continue; diff --git a/index.js b/index.js index ec03cb0..3bf6906 100644 --- a/index.js +++ b/index.js @@ -4,8 +4,8 @@ import {createStringGenerator, createAsyncStringGenerator} from './core.js'; const randomBytesAsync = promisify(crypto.randomBytes); -export default createStringGenerator((byteLength, type, length) => crypto.randomBytes(byteLength).toString(type).slice(0, length), crypto.randomBytes); +export default createStringGenerator((byteLength, type, length) => crypto.randomBytes(byteLength).toString(type).slice(0, length), size => new Uint8Array(crypto.randomBytes(size))); export const cryptoRandomStringAsync = createAsyncStringGenerator(async (byteLength, type, length) => { const buffer = await randomBytesAsync(byteLength); return buffer.toString(type).slice(0, length); -}, randomBytesAsync); +}, async size => new Uint8Array(await randomBytesAsync(size))); diff --git a/test.js b/test.js index e36031e..fe205ea 100644 --- a/test.js +++ b/test.js @@ -129,15 +129,11 @@ function runTests(test) { test.run(); } -const nodeTests = suite('Node.js', { +runTests(suite('Node.js', { cryptoRandomString: nodeCryptoRandomString, cryptoRandomStringAsync: nodeCryptoRandomStringAsync, -}); - -const browserTests = suite('Browser', { +})); +runTests(suite('Browser', { cryptoRandomString: browserCryptoRandomString, cryptoRandomStringAsync: browserCryptoRandomStringAsync, -}); - -runTests(nodeTests); -runTests(browserTests); +}));