Score:0

CSPRNG in Javascript?

cn flag

I'm trying to obtain a random, unpredictable quite long number (± 20-25 digits) using Javascript (created by user's client) as fast and light as possible. Is this solution reliable, robust and secure enough?

When the online page is opened, the 13-digit timestamp is stored. A timer determines the number of milliseconds before user clicks on 'OK' (let's assume he got a short text to read or anything else to do). A collection of 100 invisible 'pixels' (1*1px HTML spans) is created with randomly fixed initial RGBA colors (A=0=transparency).

 let d = Date.now(); // timestamp 13 digits
 let n = 100         // number of 'pixels' 
 let pixels = ''

 for (i=0; i<n; i++) {
    let r =  Math.floor(Math.random() * 256); 
    let g =  Math.floor(Math.random() * 256); 
    let b =  Math.floor(Math.random() * 256);
    let c =  'rgba('+r+','+g+','+b+',0)'
    pixels += '<span id="pix'+i+'" style="background-color:'+c+'"></span>'
 }

Once it's done we randomly change the color of each 'pixel' every 100th of a second

 let changeColor = setInterval(function(){
    for (i=0; i<n; i++) {
        let r =  Math.floor(Math.random() * 256); 
        let g =  Math.floor(Math.random() * 256); 
        let b =  Math.floor(Math.random() * 256); 
        let c =  'rgba('+r+','+g+','+b+',0)'
        document.getElementById('pix'+i).style.backgroundColor = c
    }
 },10);

When user clicks on 'OK' the function is stopped, a random pixel is determined and its RGB values are found

let x =  Math.floor(Math.random() * n);  // 39
let px = window.getComputedStyle(document.getElementById('pix'+x),null).backgroundColor
let rgb = px.match(/\d+/g);  // pix 39 = [21,13,152]

Then we multiply each RGB value by x * a random factor [1 to 5]:

let r = rgb[0]*(x*Math.floor(Math.random() * 5)+1), // 21 * 39 * 2 = 1638
    g = rgb[1]*(x*Math.floor(Math.random() * 5)+1), // 13 * 39 * 1 = 507
    b = rgb[2]*(x*Math.floor(Math.random() * 5)+1)  // 152 * 39 * 4 = 23712

By adding x + the timer + a random extract of the timestamp we obtain:

let t = Date.now()-d;                       // timer on click
let p = Math.floor(Math.random() * 4)+3;    
let z = d.toString().substr(-p)             // the xx last timestamp value (3->7)

let val = x+''+r+g+b+''+t+z                 // 39 1368 507 23712 1348 55601

Then we randomly shuffle the result:

function shuffle(a) {
  let r = a.length, temp, rand;
  while (0 !== r) {
    rand = Math.floor(Math.random() * r);
    r -= 1;
    temp = a[r];
    a[r] = a[rand];
    a[rand] = temp;
  }
  return a;   // 39136850723712134855601 -> 25851963017738613021534
}

  Tests -> console
  17:22:34 pix #39 = [21,13,152] 9348234523267751239843
  17:22:42 pix #39 = [21,13,152] 109715237240854257137
  17:23:02 pix #39 = [21,13,152] 100889146450039658553439

If we need a longer result (50, 100 digits) we can create 500 'pixels' and randomly choose 10 of them instead of one. Or add an entropy value (mouse's movement on screen : https://www.grc.com/r&d/js.htm) to the obtained value. What do you think?

Eugene Styer avatar
dz flag
From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random: 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.
Maarten Bodewes avatar
in flag
The problem with this scheme is that `Math.random()` doesn't supply random numbers; usually it is generated using a 32 bit seed that may be easy to predict. So the only input that seems entirely random is the time between the calculations starting and the user hitting [OK]. Oh, and you include a `Date()`, which is not that random at all. How random this is depends more on the system than anything else I suppose. I would at least expect that somebody that can program a script to start the calculation at certain time & almost instantaneously could force it to be very non-random.
Maarten Bodewes avatar
in flag
[This would be much more random](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)
ph flag
Using colored pixels doesn't provide any functionality above storing those values in an array.
Score:1
fr flag

Math.random is not a cryptographically secure PRNG. For example, Firefox uses an XorShift-based generator, which will leak its entire state after just a few calls. Therefore, you're not really creating random pixels here; you're just picking values based on the same previous seed.

MDN describes the Web Cryptography API function for generating cryptographically secure random numbers. If you're working in Node.js instead, it comes with built-in functions which do the same thing. These are designed to implement CSPRNGs, usually the system one, which you should use unless you are certain you need something else. They will be suitable for almost all cryptographic needs, and using the system CSPRNG is strongly recommended by most cryptographers.

If you needed something that was reproducible, you could use something like ChaCha20 with a key and nonce generated from the system CSPRNG, or an HMAC-DRBG with SHA-2 or SHA-3. However, this is not necessary in most situations, and it's better when possible to avoid implementing your own cryptography in favor of known, trusted implementations, so if you did need this, you'd be better off using a known implementation.

Wolden avatar
cn flag
Thanks for answer and comments. I agree for the weakness of Math.random but beyond the positions of principle, does it make these results obtained predictable and therefore not secure. If so, why and how? Thank you for the help in understanding, I'm nothing but a beginner in cryptography ;-)
Wolden avatar
cn flag
as shown in my last 3 lines (tests->console) once the function is stopped, timestamp and pixel are defined. I've clicked a lot of times on the OK button till I've found 3 results for the same pix (#39): with the same RGB values I obtain 3 very different results. Are they predictable (i.e in a predictable 'list of possibles results')?
bk2204 avatar
fr flag
They are probably predicable enough that this approach is subject to brute force. Just because it "looks random" doesn't mean that it isn't predictable. I'm not going to do a full cryptanalysis on this approach because I think using the system CSPRNG is obviously better and is the logical choice, but if you use `Math.random` as a random source where you need a CSPRNG, I will request a CVE against your software.
Wolden avatar
cn flag
Thanks for explanations :-)
Score:0
cn flag

I've used above 6 times the Math.random() function, one to get a 0->255 value (r,g,b), one to get a 0->100 value (pixel number), and some others to get smaller values (i.e 3->5 for timestamp)

The 0->255 (r,g,b) value is now taken from

    let rand = new Uint8Array(3);           // 3 vals 0 -> 255
    window.crypto.getRandomValues(rand);
    let r =  rand[0]; 
    let g =  rand[1]; 
    let b =  rand[2];

and the 0->100 value (pixel number) from

    let pix = new Uint8Array(1);
    window.crypto.getRandomValues(pix);
    let v =  Math.floor(pix[0]/2.55);       // val 0->100

Once I've replaced all the Math.random() functions with these new implementations, do you think it is more secure and reliable/resistant to attacks?

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.