browserify / randombytes

random bytes from browserify stand alone
MIT License
99 stars 47 forks source link

Implement browser getRandomValues without depending on crypto #37

Open aki-cat opened 6 months ago

aki-cat commented 6 months ago

This is based on this discussion

TL;DR: I propose having an implementation of getRandomValues that does not depend on a native implementation of crypto.

I am having an issue with bundling my code and running it in a non-browser environment, using modules that depend on crypto, which depends on randombytes' implementation of getRandomValues. If I bundle my code in "browser" mode, I get the error 'Secure random number generation is not supported by this browser.\nUse Chrome, Firefox or Internet Explorer 11'.

If I don't bundle it in browser mode but force browser fallback for crypto-browserify (which is necessary in my use-case, the javascript VM I'm using does not implement crypto), then I get a circular dependency and getRandomBytes is never implemented.

Is it possible to implement it in this package? If not is there any particular reason? And can I help in some way?

SValentyn commented 5 months ago

@aki-cat, I support your request for improvement. I faced the same problem. Thanks for your knowledgeable description of the issue 👍

dcousens commented 5 months ago

You need a random entropy source, and your VM may not be capable if it doesn't have native crypto

SValentyn commented 5 months ago

The code below solves my problem with polyfills (like crypto, stream, process) and errors using components from Node.js modules. I used the usual low-level Web Crypto API. Maybe it will be useful for someone.

/**
 * Information about Webpack 5 Breaking Changes...
 * Webpack 5 introduced breaking changes related to polyfills, especially for Node.js core modules like 'crypto'.
 * Previously, Webpack automatically provided polyfills for Node.js core modules, allowing developers to use them in browser environments seamlessly.
 * However, starting from Webpack 5, this behavior changed, and Webpack no longer includes polyfills for Node.js core modules by default.
 * This change requires developers to find alternative solutions for using Node.js core modules in web environments,
 * such as directly utilizing browser-native functionalities or third-party libraries.
 * In this utility file, the logic has been refactored to utilize the Web Crypto API directly instead of relying on the 'crypto' module.
 * The Web Crypto API provides cryptographic operations in web applications, allowing for secure cryptographic functionalities without the need for polyfills.
 *
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto | Web Crypto API – SubtleCrypto}
 * @see {@link https://github.com/webpack/changelog-v5?tab=readme-ov-file#automatic-nodejs-polyfills-removed | Webpack 5 – Automatic Node.js Polyfills Removed}
 * @see {@link https://stackoverflow.com/questions/67065644/nodejs-crypto-module-not-working-in-browser-after-bundling-with-webpack | NodeJS crypto module not working in browser after bundling with webpack}
 */

import { Buffer } from "buffer";

/**
 * Generates cryptographically strong random bytes using the Web Crypto API.
 * @param {number} size – The number of random bytes to generate.
 * @returns {Promise<Buffer>} A promise that resolves to a buffer containing random bytes.
 */
export async function randomBytes(size: number): Promise<Buffer> {
    const array = new Uint8Array(size);
    window.crypto.getRandomValues(array);
    return Buffer.from(array.buffer);
}

/**
 * Creates a hash of the provided data using the Web Crypto API.
 * @param {string} data – The data to hash.
 * @param {string} algorithm – The hash algorithm to use, e.g., 'SHA-256'.
 * @returns {Promise<Buffer>} A promise that resolves to the hash of the data as a buffer.
 */
export async function createHash(data: string, algorithm: string): Promise<Buffer> {
    const encoder = new TextEncoder();
    const dataBuffer = encoder.encode(data);
    const hashBuffer = await window.crypto.subtle.digest(algorithm, dataBuffer);
    return Buffer.from(hashBuffer);
}