Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
stefan-schweiger opened this issue Jul 13, 2023 · 3 comments
Open

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

stefan-schweiger opened this issue Jul 13, 2023 · 3 comments

Comments

@stefan-schweiger
Copy link

stefan-schweiger commented Jul 13, 2023

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?

@entronad
Copy link
Owner

The problem is in a js file, any imported lib will execute before any code written in this file, the polyfill work should be imported from another lib or file before importing CryptoES, not written by code in the file: https://stackoverflow.com/questions/65656104/how-to-run-several-code-before-import-syntax-in-javascript

There is a mature solution in RN: brix/crypto-js#259 (comment)

Assigning crypto every time in random function is not a good idea, CryptoJS doesn't do like it either.

@stefan-schweiger
Copy link
Author

In my actual project i'm importing the polyfill from a file in App.js way before I even touch crypto-es the first time anywhere (like suggested in the comments you linked). Probably the issue is the babel compilation order mentioned in one of the comments. I'm just wondering why https://github.com/uuidjs/uuid works without a problem with my polyfill (which is even more strict about not being backwards compatible).

If you only concern is with the assignment of crypto it can also be moved to the outside scope and only be tried to reassign if it wasn't set yet.

let crypto = undefined;

const randomWordArray = (nBytes) => {
  crypto =
    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);
}

@entronad
Copy link
Owner

How about try this polifill: https://github.com/zloirock/core-js#ecmascript-globalthis

Repository owner deleted a comment from technosoft-admin Mar 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants