1. 14

  2. 8

    Fun fact, in the days of yore most browsers seeded Math.random() globally from system time, and that could actually be used to track a browsing session across any number of sites and origins.

    Essentially the attack was

    1. Determine what RNG is being used (yay user agent test!)
    2. Query Math.random() N times. N is determined by the exact PRNG being used, at the time you could derive the entire RNG state from either one or two values from Math.random()
    3. current_time = +new Date(); the_past_time = current_time - (say 5 days or so)
    4. Based on (1) you can roll back the RNG arbitrarily because they were all variations on the LCRNGs
    5. start running the RNG in reverse from (1) using the state from (2).
    6. repeat step (5) until you get an RNG state seed between the_past_time and current_time
    7. At this point you have a value that is reasonably likely to have been the launch time of this particular browser session, and that is likely unique enough to provide a significant tracking cookie.

    Separating into different realms was needed because sites kept trying to do real crypto (or otherwise needed real RNG) and if evil.com knew one of those sites was running in another window/tab it could sample its local Math.random() to work out where your session’s RNG was and then with that forward enough info to evil.com to reconstruct the “random” numbers being used on victim.com

    Follow on: if you ever need real random numbers you need the getRandomBytes (or whatever that API is, I forget) in window.crypto, as should be obvious at this point, Math.random has no meaningful quality requirements.

    In the very early days on Safari for Windows, Math.random used the windows rand_s (IIRC) which is a CSPRNG and that resulted in a 5-10% performance hit on the at the time relevant SunSpider benchmark

    1. 4

      It’s odd that this doesn’t mention crypto.getRandomValues(new Uint8Array(32)) as a way to get a seed.

      Otherwise very nice and I like that it explicitly explains RNGs as Moore machines. :)

      1. 2

        Thanks for spotting this. I originally had some rules for myself that restricted any crypto functions that provide entropy (as well as Math.random) so I could talk about entropy in general + browser entropy sources.

        This got lost in the editing process and I’ve added a note 👍

        1. 2

          Thanks. I will admit that using getRandomValues in a blog post titled “Without Math.random” might feel like cheating… but I think the stuff about how PRNGs can be implemented was the most interesting portion anyway. <3

      2. 2

        As a hobby game developer, I would like to protest the opaqueness of seed use in most RNG implementations. The article mentions math.Random() not having an argument. If I understand it correctly (I have not used java in a loooong time) it also appears to be static. I use mostly c# and the situation is a little better there. I can create a new instance of Random and I can do so optionally with a seed as an initializer. But then later if I have a set of n randomizers used in my program and I want to reproduce some behaviour for testing purposes, or to reconstitute previously generated content, I can’t just look at myRandomInstance.Seed. The initial seed is not saved. So I have to keep a list of n seeds as well.

        To solve this I just wrote a wrapper, but I think this use case is worth bearing in mind if you are implementing a random algorithm. Procedural generation in games can involve creating huge amounts of random data, often involving many different random sequences and potentially even heirarchical trees of randomizers. It needs to be possible to reconstitute all of it perfectly in a completely deterministic way in order to debug edge cases.

        1. 1

          Thank you for sharing your use-case of random number generation.

          You mention Java, just in case that isn’t a typo, I should add that this article is about JavaScript. I agree that it would be nice for Math.random() to take a seed argument. However, since the underlying algorithm is dependant on browser implementation, it would be hard to get reproducible results in many cases.

          To solve this I just wrote a wrapper

          I have used some random generation in Unity games for game jams but I have never had to polish anything (and therefore, haven’t had much experience with debugging games). I like idea of the wrapper you have described here. It sounds very useful. Are the seeds just stored in during ‘debug mode’ sessions or are they reported to you by end-user clients during a crash or error event?

          1. 2

            You mention Java

            Oops, I just wasn’t paying attention. The Java function is named the same and has the same signature.

            Are the seeds just stored in during ‘debug mode’ sessions or are they reported to you by end-user clients during a crash or error event?

            Most of my projects are pretty unpolished as well, but the plan/theory is the latter strategy. All seeds would be logged (they are also used in save game files) so that if something goes wrong, the game state can be completely rebuilt from the logs.

            My wrapper also tends to accumulate helper functions like gaussian distribution, and IntRange(int from, int to) and whatever else comes to mind. At one point I worked on a weighted stepper function which could take a list of weighted probabilities and select one, i.e. {a:1, b:2, c:0.5, d:0.0001} would return (a) with a probabilities of 1/3.5001

            I do understand of course most of the complexities of procedural content generation are outside of the scope of a simple random function. I imagine there are probably libraries out there that have all of this stuff in them. It would also be good to have a set of tools for generating various noise functions and composing them together.