Skip to content

Falling back to Math.random() even if polyfill is used #35

@stefan-schweiger

Description

@stefan-schweiger

I've first encountered this when using expo (react-native) where I have to polyfill the globalThis.crypto with the following code right at the very beginning of my App.js:

import { getRandomValues as expoCryptoGetRandomValues } from 'expo-crypto';

class Crypto {
  getRandomValues = expoCryptoGetRandomValues;
}

const webCrypto = typeof crypto !== 'undefined' ? crypto : new Crypto();

export function polyfillWebCrypto() {
  if (typeof crypto === 'undefined') {
    Object.defineProperty(globalThis, 'crypto', {
      configurable: true,
      enumerable: true,
      get: () => webCrypto,
    });
  }
}

Even though the polyfill is applied you still end up getting the Because there is no global crypto property in this context, cryptographically unsafe Math.random() is used message in the logs. I think the reason for this is that the randomWordArray function is declared in such a way that it will only evaluated once during the initialization, but I wasn't able to find a order of operations during which the polyfill is already applied before this.

It's probably a bit less efficient, but you could get around this issue if you would define randomWordArray in core.js instead in the following way:

const randomWordArray = (nBytes) => {
  const crypto =
    (typeof globalThis != 'undefined' ? globalThis : void 0)?.crypto ||
    (typeof global != 'undefined' ? global : void 0)?.crypto ||
    (typeof window != 'undefined' ? window : void 0)?.crypto ||
    (typeof self != 'undefined' ? self : void 0)?.crypto ||
    (typeof frames != 'undefined' ? frames : void 0)?.[0]?.crypto;

  if (crypto) {
    const words = [];

    for (let i = 0; i < nBytes; i += 4) {
      words.push(crypto.getRandomValues(new Uint32Array(1))[0]);
    }

    return new WordArray(words, nBytes);
  }

  console.warn('Because there is no global crypto property in this context, cryptographically unsafe Math.random() is used');
  const words = [];
  
  const r = (m_w) => {
    let _m_w = m_w;
    let _m_z = 0x3ade68b1;
    const mask = 0xffffffff;

    return () => {
      _m_z = (0x9069 * (_m_z & 0xFFFF) + (_m_z >> 0x10)) & mask;
      _m_w = (0x4650 * (_m_w & 0xFFFF) + (_m_w >> 0x10)) & mask;
      let result = ((_m_z << 0x10) + _m_w) & mask;
      result /= 0x100000000;
      result += 0.5;
      return result * (Math.random() > 0.5 ? 1 : -1);
    };
  };

  for (let i = 0, rcache; i < nBytes; i += 4) {
    const _r = r((rcache || Math.random()) * 0x100000000);

    rcache = _r() * 0x3ade67b7;
    words.push((_r() * 0x100000000) | 0);
  }

  return new WordArray(words, nBytes);
}

Is this anything you would consider changing?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions