Ankur Agarwal
Ankur Agarwal

Reputation: 24748

How to generate random numbers in the BusyBox shell

How can I generate random numbers using AShell (restricted bash)? I am using a BusyBox binary on the device which does not have od or $RANDOM. My device has /dev/urandom and /dev/random.

Upvotes: 8

Views: 8648

Answers (8)

Orsiris de Jong
Orsiris de Jong

Reputation: 3016

Trying escitalopram's solution didn't work on busybox v1.29.0 but inspired me doing a function.

sI did actually come up with a portable random number generation function that asks for the number of digits and should work fairly well (tested on Linux, WinNT10 bash, Busybox and msys2 so far).

# Get a random number on Windows BusyBox alike, also works on most Unixes
function PoorMansRandomGenerator {
    local digits="${1}"     # The number of digits of the number to generate

    local minimum=1
    local maximum
    local n=0
    
    if [ "$digits" == "" ]; then
        digits=5
    fi
    
    # Minimum already has a digit
    for n in $(seq 1 $((digits-1))); do
        minimum=$minimum"0"
        maximum=$maximum"9"
    done
    maximum=$maximum"9"
    
    #n=0; while [ $n -lt $minimum ]; do n=$n$(dd if=/dev/urandom bs=100 count=1 2>/dev/null | tr -cd '0-9'); done; n=$(echo $n | sed -e 's/^0//')
    # bs=19 since if real random strikes, having a 19 digits number is not supported
    while [ $n -lt $minimum ] || [ $n -gt $maximum ]; do
        if [ $n -lt $minimum ]; then
            # Add numbers
            n=$n$(dd if=/dev/urandom bs=19 count=1 2>/dev/null | tr -cd '0-9')
            n=$(echo $n | sed -e 's/^0//')
            if [ "$n" == "" ]; then
                n=0
            fi
        elif [ $n -gt $maximum ]; then
            n=$(echo $n | sed 's/.$//')
        fi
    done
    echo $n
}

The following gives a number between 1000 and 9999:

    echo $(PoorMansRandomGenerator 4)

Upvotes: -1

Jack G
Jack G

Reputation: 5322

The argument of "optional feature" in busybox is utterly baseless as everything in busybox can be turned on or off. You can even switch to less posix shells just to save a few dozen kb. Assuming a sane busybox config such as on Alpine, you can base64 from urandom like so:

base64 </dev/urandom | head -c16

This prints M6xtGC/DUSMF/1nW

Upvotes: 1

Orsiris de Jong
Orsiris de Jong

Reputation: 3016

Improved the above reply to a more simpler version,that also runs really faster, still compatible with Busybox, Linux, msys and WinNT10 bash.

function PoorMansRandomGenerator {
    local digits="${1}" # The number of digits to generate
    local number

    # Some read bytes can't be used, se we read twice the number of required bytes
    dd if=/dev/urandom bs=$digits count=2 2> /dev/null | while read -r -n1 char; do
            number=$number$(printf "%d" "'$char")
            if [ ${#number} -ge $digits ]; then
                    echo ${number:0:$digits}
                    break;
            fi
    done
}

Use with

echo $(PoorMansRandomGenerator 5)

​​​​​​​​​​​​​

Upvotes: -1

vmauery
vmauery

Reputation: 51

Hexdump and dc are both available with busybox. Use /dev/urandom for mostly random or /dev/random for better random. Either of these options are better than $RANDOM and are both faster than looping looking for printable characters.

32-bit decimal random number:

CNT=4
RND=$(dc 10 o 0x$(hexdump -e '"%02x" '$CNT' ""' -n $CNT /dev/random) p)

24-bit hex random number:

CNT=3
RND=0x$(hexdump -e '"%02x" '$CNT' ""' -n $CNT /dev/random)

To get smaller numbers, change the format of the hexdump format string and the count of bytes that hexdump reads.

Upvotes: 1

$RANDOM and od are optional features in BusyBox, I assume given your question that they aren't included in your binary. You mention in a comment that /dev/urandom is present, that's good, it means what you need to do is retrieve bytes from it in a usable form, and not the much more difficult problem of implementing a random number generator. Note that you should use /dev/urandom and not /dev/random, see Is a rand from /dev/urandom secure for a login key?.

If you have tr or sed, you can read bytes from /dev/urandom and discard any byte that isn't a desirable character. You'll also need a way to extract a fixed number of bytes from a stream: either head -c (requiring FEATURE_FANCY_HEAD to be enabled) or dd (requiring dd to be compiled in). The more bytes you discard, the slower this method will be. Still, generating random bytes is usually rather fast in comparison with forking and executing external binaries, so discarding a lot of them isn't going to hurt much. For example, the following snippet will produce a random number between 0 and 65535:

n=65536
while [ $n -ge 65536 ]; do
  n=1$(</dev/urandom tr -dc 0-9 | dd bs=5 count=1 2>/dev/null)
  n=$((n-100000))
done

Note that due to buffering, tr is going to process quite a few more bytes than what dd will end up keeping. BusyBox's tr reads a bufferful (at least 512 bytes) at a time, and flushes its output buffer whenever the input buffer is fully processed, so the command above will always read at least 512 bytes from /dev/urandom (and very rarely more since the expected take from 512 input bytes is 20 decimal digits).

If you need a unique printable string, just discard non-ASCII characters, and perhaps some annoying punctuation characters:

nonce=$(</dev/urandom tr -dc A-Za-z0-9-_ | head -c 22)

In this situation, I would seriously consider writing a small, dedicated C program. Here's one that reads four bytes and outputs the corresponding decimal number. It doesn't rely on any libc function other than the wrappers for the system calls read and write, so you can get a very small binary. Supporting a variable cap passed as a decimal integer on the command line is left as an exercise; it'll cost you hundreds of bytes of code (not something you need to worry about if your target is big enough to run Linux).

#include <stddef.h>
#include <unistd.h>
int main () {
    int n;
    unsigned long x = 0;
    unsigned char buf[4];
    char dec[11]; /* Must fit 256^sizeof(buf) in decimal plus one byte */
    char *start = dec + sizeof(dec) - 1;
    n = read(0, buf, sizeof(buf));
    if (n < (int)sizeof(buf)) return 1;
    for (n = 0; n < (int)sizeof(buf); n++) x = (x << 8 | buf[n]);
    *start = '\n';
    if (x == 0) *--start = '0';
    else while (x != 0) {
        --start;
        *start = '0' + (x % 10);
        x = x / 10;
    }
    while (n = write(1, start, dec + sizeof(dec) - start),
           n > 0 && n < dec + sizeof(dec) - start) {
        start += n;
    }
    return n < 0;
}

Upvotes: 8

escitalopram
escitalopram

Reputation: 3856

I Tried Gilles' first snippet with BusyBox 1.22.1 and I have some patches, which didn't fit into a comment:

while [ $n -gt 65535 ]; do
    n=$(</dev/urandom tr -dc 0-9 | dd bs=5 count=1 2>/dev/null | sed -e 's/^0\+//' )
done
  1. The loop condition should check for greater than the maximum value, otherwise there will be 0 executions.
  2. I silenced dd's stderr
  3. Leading zeros removed, which could lead to surprises in contexts where interpreted as octal (e.g. $(( )))

Upvotes: 0

Felipe Alvarez
Felipe Alvarez

Reputation: 3898

</dev/urandom sed 's/[^[:digit:]]\+//g' | head -c10

Upvotes: 1

paulsm4
paulsm4

Reputation: 121599

/dev/random or /dev/urandom are likely to be present.

Another option is to write a small C program that calls srand(), then rand().

Upvotes: 0

Related Questions