How to generate a random 6 digit passphrase in node.js using randomBytes()
Asked Answered
T

2

5

I am trying to generate exactly 6 random digits in Node.js, which needs to be cryptographically secure. Here is my code:

var crypto = require('crypto');

crypto.randomBytes(2, function(err, buffer) {
    console.log(parseInt(buffer.toString('hex'), 16));
});

The problem is that the result from this can be 4 or 5 digits, because we are converting from hex to decimal. Is there a way to keep the cryptographically secure function randomBytes(), but guarantee a 6 digit result?

Tape answered 7/7, 2013 at 22:30 Comment(0)
H
8

A new function for doing this was added to Node v14.17

crypto.randomInt([min, ]max[, callback])

To get a 6 digit integer.

const crypto = require('crypto');
crypto.randomInt(100000, 999999, (err, n) => {
    if (err) throw err;
    console.log(`Random 6 digit integer: ${n}`);
});

Also per the documentation https://nodejs.org/api/crypto.html#cryptorandomintmin-max-callback you should also check that your min and max values are safe integers via:

Number.isSafeInteger() 
Hostile answered 6/12, 2021 at 19:53 Comment(5)
crypto.randomInt(0, 999999, (err, num) => { if (err) reject(err) const string = num + "" const padding = "0".repeat(digits - string.length) resolve(padding + string) })Orpington
@Orpington I am not so sure that padding a short result w zeros meets the standard of cryptographically secure (shrug)?Hostile
I think @Orpington 's answer make senseCostermonger
crypto.randomInt(0, 999999).toString().padStart(6, '0')Costermonger
crypto.randomInt(0, 1_000_000).toString().padStart(6, '0') would be correct, as it excludes max.Blackout
V
3

2 bytes have a maximum value of 65535, so you will never get 6 digits if you only use 2 bytes.

Use 3 bytes instead and then reduce it to 6 digits using substr:

var crypto = require('crypto');
crypto.randomBytes(3, function(err, buffer) {
    console.log(parseInt(buffer.toString('hex'), 16).toString().substr(0,6));
});

Another solution would be to execute randomBytes until you get a 6 digit value:

var crypto = require('crypto');
var secureVal = 0;
function generateSecureVal(cb) {
    crypto.randomBytes(3, function(err, buffer) {
        secureVal = parseInt(buffer.toString('hex'), 16);
        if (secureVal > 999999 || secureVal < 100000) {
            generateSecureVal(cb);
        } else {
            cb();
        }
    });
}

generateSecureVal(function(){
    console.log(secureVal);
});

The problem with the above is that it could theoretically get stuck in a neverending loop, and it will most likely use way more CPU cycles than the first example.

Veal answered 7/7, 2013 at 22:45 Comment(6)
Using substr(), does that keep it cryptographically secure?Tape
@Tape I am not entirely sure, but considering that the result is derived from a source that is cryptographically secure I wouldn't see why. Would be good to get some more input from someone who's better versed with cryptocraphics :)Veal
Unfortunately the first option is not cryptographically secure as it is biassed. The issue is that randomBytes() returns a value from 0 to 2^24 - 1. This is not a round number in decimals, only in binary or hexadecimals. The trick is to keep as many fully random bits without introducing bias. Your returned value in the first function has a bias towards lower numbers. The other one is better, but does not use a modulus, so it is inefficient. Furthermore it uses recursion while the depth is not known - this may deplete your stack.Polanco
@owlstead Thanks for your input! Do you have any suggestions on how to improve it, or do it in another way?Veal
Yes, there is a Q/A on crypto.stackexchange.com (search for random number range) that explains your options. Easiest is to convert the implementation of SecureRandom.nextInt(int) into JavaScript.Polanco
link to question Maarten referenced: crypto.stackexchange.com/questions/7996/…Hostile

© 2022 - 2024 — McMap. All rights reserved.