Skip to content

update to use cryptographic random if it is available #2447

New issue

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

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

Already on GitHub? # to your account

Merged
merged 1 commit into from
Jun 27, 2014

Conversation

usagi
Copy link
Contributor

@usagi usagi commented Jun 24, 2014

The pull-request based on #2439 (comment).

References:


Result of python tests/runner.py test_random_device

  • nodejs-0.10.25
test_random_device (test_core.default) ... (checking sanity from test runner)
INFO     root: (Emscripten: Running sanity checks)
ok

----------------------------------------------------------------------
Ran 1 test in 1.584s

OK

And, I tested Firefox-30 and Chromium-35 on Ubuntu-14.04 that is ok.
( The test is modify the code simply, add a logging in the code path and call std::random() and read std::ifstream("/dev/[u]random"). )

FS.createDevice('/dev', 'urandom', function() { return Math.floor(Math.random()*256); });
var random_device;
// for modern web browsers
if ( typeof( crypto ) !== "undefined" )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the coding conventions in surrounding code. This is how it should look

if (typeof crypto !== 'undefined) {
  ..
} else if (typeof ..) {
  ..
}

@usagi
Copy link
Contributor Author

usagi commented Jun 26, 2014

@kripken

Thanks for review.

I fixed to coding rule and environment check method, and then I rebase/squash the commit. Please re-review and merge.

var random_device;
if (typeof crypto !== 'undefined') {
// for modern web browsers
random_device = function() { return crypto.getRandomValues(new Uint8Array(1))[0]; };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One final thing, let's optimize this a little. Can create one such array before this line, and reuse it in the function. Typed array creation is notoriously slow, plus it creates garbage.

optimize cryptographic random generator.
@usagi
Copy link
Contributor Author

usagi commented Jun 27, 2014

@kripken

I fixed to the optimization. And, I was measured it on Chromium-30, result is good.


  • Benchmark C++ code: https://gist.github.com/usagi/7055e90a51a6b901405c
  • src/library_fs.js (before):
    • random_device = function() { return crypto.getRandomValues(new Uint8Array(1))[0]; };
    • Result score: about 28,000 [loops/3seconds] // slow
  • src/library_fs.js (after):
    • random_device = function() { return crypto.getRandomValues(arguments[0])[0]; }.bind(undefined, new Uint8Array(1));
    • Result score: about 1,900,000 [loops/3seconds] // fast

@kripken
Copy link
Member

kripken commented Jun 27, 2014

Thanks! I think it's best to avoid using the arguments object, but I don't want to bother you with any more revisions here ;) I'll fix it up after I merge.

kripken added a commit that referenced this pull request Jun 27, 2014
update to use cryptographic random if it is available
@kripken kripken merged commit 9a176dd into emscripten-core:incoming Jun 27, 2014
@fluffypony
Copy link

I know this is old, but I've been going through this now. Given the broken state of JS cryptographic randomness I think that any reliance on Math.random() (even as a fallback) is a bad idea, especially seeing as how emscripten is very handy for converting cryptographic functions to JS. That said, I'm aware that some use-cases may need broader support or don't care about cryptographic randomness, so the Math.random() fallback is useful for them.

Additionally, in order to support IE11, you need to check for window.msCrypto instead of window.crypto, as in this snippet used in openpgpjs:

    if (typeof window === 'undefined') {
      nodeCrypto = require('crypto');
    }

    if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
      window.crypto.getRandomValues(buf);
    } else if (typeof window !== 'undefined' && typeof window.msCrypto === 'object' && typeof window.msCrypto.getRandomValues === 'function') {
      window.msCrypto.getRandomValues(buf);
    } else if (nodeCrypto) {
      var bytes = nodeCrypto.randomBytes(buf.length);
      buf.set(bytes);
    } else if (this.randomBuffer.buffer) {
      this.randomBuffer.get(buf);
    } else {
      throw new Error('No secure random number generator available.');
    }

May I suggest using cryptographically secure randomness by default, with an option to allow a Math.Random() fallback if cryptographic randomness is not required?

@fluffypony
Copy link

Forgot to add that I can (probably) write that and submit a PR, unless someone else who is more familiar with emscripten's architecture wants to give it a go:)

@kripken
Copy link
Member

kripken commented Apr 15, 2015

PR for the ms fix is welcome of course.

Not sure where specifically you are suggesting we use secure crypto that we aren't now?

@fluffypony
Copy link

@kripken this bit -

else {
        // default for ES5 platforms
        random_device = function() { return Math.floor(Math.random()*256); };

References:

Or as Mozilla says on their Math.random() page -

Note: Math.random() does not provide cryptographically secure random numbers. Do not use them for anything related to security. Use the Web Crypto API instead, and more precisely the window.crypto.getRandomValues() method.

@kripken
Copy link
Member

kripken commented Apr 15, 2015

That code is hit only if crypto doesn't exist? That is, we do use secure crypto when it is available. Not sure what you are proposing that we do in the case that it isn't?

@fluffypony
Copy link

@kripken at the risk of unnecessarily repeating myself, I'll copy-and-paste from my original message:

May I suggest using cryptographically secure randomness by default, with an option to allow a Math.Random() fallback if cryptographic randomness is not required?

In other words, by default Math.random() should not be allowed at all, under any circumstances. An option can be set that allows for a Math.random() fallback if cryptographically insecure randomness is desirable (e.g. when rendering game objects where speed is more important).

@kripken
Copy link
Member

kripken commented Apr 15, 2015

Sorry if I'm being dense here. But I am having trouble understanding what the suggestion actually means. Let's say we don't allow Math.random() - what would execute instead of it, if the browser doesn't support crypto? Would that code abort with an error, or would you implement crypto in JS, or something else?

@fluffypony
Copy link

@kripken it should abort with an error.

There are some alternative JS libraries that try provide a cryptographically secure PRNG by getting entropy from mouse movement etc., but you don't really want a warm-up time of 30+ seconds as a fallback, plus a lot of them are poorly maintained or not very well written (so they're not ideal).

In other words, the preferred outcome should be:

  • emscripten on modern browsers, cryptographic randomness not required: use window.crypto / window.msCrypto
  • emscripten on modern browsers, cryptographic randomness required: definitely use window.crypto / window.msCrypto
  • emscripten on old browsers, cryptographic randomness not required: user Math.random() as window.crypto isn't available
  • emscripten on old browsers, cryptographic randomness required: take the JS developer trying to do this out back and put him out of his misery:-P

@kripken
Copy link
Member

kripken commented Apr 15, 2015

Got it. This is just for /dev/random, correct?

I'm not opposed to making this change to the default, but at minimum we should do it with a clear error message pointing to an optional flag to disable the assert, so people can override it. Also, I'd like to hear what other people think about this, in particular @juj .

@fluffypony
Copy link

Yes, just for /dev/random, all random functions hinge off there anyway.

If there's not enough visibility here I can move it to an issue instead?

@juj
Copy link
Collaborator

juj commented Apr 16, 2015

Reading http://man7.org/linux/man-pages/man4/random.4.html , I understand that /dev/random and /dev/urandom are both intended to be cryptographically secure. (although the wording on the caveats there are somehow funny, basically saying "it might be secure")

In that respect, I'd prefer that our implementation of those should be fully cryptographically secure by default. If we are in a scenario where we can't provide that, my thinking is that those files would not exist in the filesystem at all (or reading would generate error or eof, whichever would feel most idiomatic to signal "not available"). Then a build flag e.g. -s INSECURE_RANDOMNESS=1 or similar could be enabled to make those files available even in the case when they would be known to not be secure. Same treatment for all other APIs which advertise being crypto safe.

For any other APIs which do not intend to guarantee crypto security (C rand()) and so on, we should not even attempt to make them crypto secure, but focus on performance (and have them always available by default).

@fluffypony
Copy link

@juj cryptographers are careful to never guarantee security:-P

I agree with your conclusion - what is the knock-on effect for an app expecting /dev/random and it suddenly isn't there? Would the app be expected to handle that?

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants