Playing with the TrueRNG 2 hardware random number generator

Many applications can benefit from a true, hardware based random number generator. Sure, having an independent source of entropy is good for generating encryption keys, but anything from financial simulations, bootstrap estimators, Monte Carlo integration to video games might be able to benefit from a little affordable device that does not rely on the right pseudo-random number generator being seeded in the exact right way.

Recently, I came across TrueRNG 2 on Amazon. I made an impulse decision to take it for a spin. In a scenario sure to stimulate tin foil hat wearers everywhere, It arrived a day late after taking an unexpected detour due to a USPS routing error. I did not detect anything nefarious, but, I am mentioning this just in case you care. Me … well, I realize that there is nothing I can do if someone is willing to target me this way. I just thought I would mention this fun coincidence.

The design of TrueRNG 2 is based on the so-called avalanche effect. The device is not open hardware. I guess I can split open the case, but I am not motivated to do that right now.

The device I received was in a black plastic enclosure with no markings on it except for a cheap sticker with TrueRNG2 / ubld.it printed over the background of an American flag. Installation instructions for Linux and Windows are available on ubld.it. On Archlinux, you add a udev rule, and use rngd to mix in the devices output into /dev/random's entropy pool. This use is actually quite appealing, as it means pulling pseudo-random numbers from /dev/random becomes much less likely to block.

You can also add yourself to the uucp group, and read from the hardware device to your heart's content.

By design, the device is a virtual USB modem, so, on OSX, you can just access it as such, although the system does not make use of it.

On Windows, you get a signed INF file that installs the device as a modem (it seems it uses COM7 by default). The operating system's random number generation facilities do not take advantage of it. There is an executable one can download, and a C++ class one can incorporate into programs.

I considered using Perl's Win32::SerialPort, but it made me feel a little overwhelmed, and slightly dirty. Instead, I wrote the following crappy C++ code to do the least amount of work to shuffle some bytes for me:

#include <windows.h>
#include <fcntl.h>
#include <io.h>

#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

static void
die (const std::string& msg, HANDLE h)
{
    DWORD e = GetLastError();
    if (h != INVALID_HANDLE_VALUE)
    {
        CloseHandle(h);
    }
    std::cerr << msg << "\n" << "Error: " << e << "\n";
    exit( EXIT_FAILURE );
}

static HANDLE
open_port(std::string port)
{
    HANDLE h = CreateFileA(
        port.c_str(),
        GENERIC_READ | GENERIC_WRITE,
        0,
        nullptr,
        OPEN_EXISTING,
        0,
        nullptr
    );

    if (h == INVALID_HANDLE_VALUE)
    {
        die("Open failed", h);
    }

    return h;
}

static void
close_port(HANDLE h)
{
    CloseHandle(h);
    return;
}

static void
initialize_port(HANDLE h)
{
    ClearCommError(h, nullptr, nullptr);

    DCB dcb;
    dcb.DCBlength = sizeof(dcb);

    GetCommState(h, &dcb);
    dcb.BaudRate = CBR_115200;
    dcb.fBinary = TRUE;
    dcb.fParity = FALSE;
    dcb.fOutxCtsFlow = FALSE;
    dcb.fOutxDsrFlow = FALSE;
    dcb.fDtrControl = DTR_CONTROL_ENABLE;
    dcb.fDsrSensitivity = FALSE;
    dcb.fTXContinueOnXoff = TRUE;
    dcb.fOutX = FALSE;
    dcb.fInX = FALSE;
    dcb.fErrorChar = FALSE;
    dcb.fNull = FALSE;
    dcb.fRtsControl = RTS_CONTROL_ENABLE;
    dcb.fAbortOnError = FALSE;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT;

    if (!SetCommState(h, &dcb))
    {
        die("Failed to set COM port options", h);
    }

    COMMTIMEOUTS to = { 0 };
    to.ReadIntervalTimeout = 200;
    to.ReadTotalTimeoutMultiplier = 1;
    to.ReadTotalTimeoutConstant = 1;
    if (!SetCommTimeouts(h, &to))
    {
        die("Failed to set COM port timeouts", h);
    }

    return;
}

static void
setdtr(HANDLE h)
{
    if (!EscapeCommFunction(h, SETDTR))
    {
        die("Failed to set DTR", h);
    }
    return;
}

static void
cleardtr(HANDLE h)
{
    if (!EscapeCommFunction(h, CLRDTR))
    {
        die("Failed to clear DTR", h);
    }
    return;
}

static void
print_some_random_bytes(HANDLE h, DWORD nbytes)
{
    std::string buffer;
    buffer.resize(nbytes, 0x55);
    DWORD nread = 0;

    while (nread < nbytes)
    {
        DWORD n = 0;
        if (!ReadFile(h, &buffer[0], nbytes, &n, nullptr))
        {
            die ("Failed to read from COM port", h);
        }
        nread += n;
    }

    _setmode(_fileno(stdout), _O_BINARY);
    std::cout << buffer << std::flush;
    _setmode(_fileno(stdout), _O_TEXT);

    return;
}

int main(int argc, char *argv[])
{
    std::string port;
    DWORD nbytes;

    switch (argc)
    {
        case 3:
            nbytes = std::stoul(argv[2]);
        case 2:
            port = std::string(argv[1]);
            break;
        default:
            port = std::string("COM7");
            nbytes = 256;
    }

    HANDLE h = open_port(port);
    initialize_port(h);
    setdtr(h);

    print_some_random_bytes(h, nbytes);

    cleardtr(h);
    close_port(h);

    return EXIT_SUCCESS;
}

As you can see, the operation is simple: Turn on DTR to read, turn it off when done (to save a little bit of battery).

Using Visual Studio 2015 Community Edition, you can compile this from the command line simply with:

cl /EHsc /O1 readtruerng.cpp

and use it simply by specifying the COM port and the number of random bytes you want:

C:\...\TrueRNG> readtruerng COM7 16 | xxd
00000000: a5c9 0e56 722f b915 31ba 529e 2c87 139c  ...Vr/..1.R.,...

On the performance side,

timethis "readtruerng COM7 1048576 > NUL"

takes about 24 seconds. This corresponds to about 349.5 kbps, and is in line with the “High Output Speed: >350 kilobits / second” claim on the product web site. Using dd on Linux and OS X, I get a little bit above 360 kbps.

I also built ent on Windows to check the output. Here is what I get:

C:\...\TrueRNG> readtruerng COM7 1048576 | ent
Entropy = 7.999815 bits per byte.

Optimum compression would reduce the size
of this 1048576 byte file by 0 percent.

Chi square distribution for 1048576 samples is 268.73, and randomly
would exceed this value 26.53 percent of the times.

Arithmetic mean value of data bytes is 127.5092 (127.5 = random).
Monte Carlo value for Pi is 3.144230439 (error 0.08 percent).
Serial correlation coefficient is -0.000889 (totally uncorrelated = 0.0).

As a visual check, I wrote a quick and dirty Perl script to generate cute bitmaps:

#!/usr/bin/env perl

use strict;
use warnings;

my $width = $ARGV[0] || 512;
my $height = $ARGV[1] || 512;
my $frames = $ARGV[2] || 100;

for my $frame (1 .. $frames) {
    my $need_bytes = ($width * $height) / 8;

    open my $pipe, '-|', "readtruerng.exe COM7 $need_bytes"
        or die "Cannot open pipe to read from COM port: $!";
    binmode $pipe;

    my $chunk = do { use bytes; local $/; <$pipe> };

    close $pipe
        or die "Failed to close pipe: $!";

    my $pbmfile = sprintf('frame%05d.pbm', $frame);
    open my $pbm, '>', $pbmfile
        or die "Cannot open '$pbmfile': $!";
    binmode $pbm;
    print $pbm "P1\n$width $height\n";

    my @bits = do {
        use bytes;
        map split(qr//, sprintf('%08b', ord)), split(qr//, $chunk);
    };

    if (my $remainder = @bits % 32) {
        push @bits, ('0') x $remainder;
    }

    for (my $i = 0; $i < @bits; $i += 32) {
        print $pbm "@bits[$i .. ($i + 31)]\n";
    }

    close $pbm
        or die "Error closing '$pbmfile': $!";
}

and created an animated GIF using:

gm convert -depth 1 -delay 3 *.pbm noise.gif

[ Sample bitmap ]

At this point, I am only exploring basic functionality. The raw output of the device may not be suitable for use in applications, and software whitening may be necessary.

Looks promising. If you are interested, TrueRNG 3 is available on Amazon, and on Tindie.

PS: You can discuss this post on /r/programming.

PPS: On Windows, you can also use PowerShell to access the device's ouput.

PPPS: See also 100 frames of 800x600 noise on Imgur.