Yesterday, I received my Kidekin TRNG hardware random number generator. I was eager to purchase this, because on the Tindie website, the first 2 people to purchase the RNG would get $50 off, making the device $30 total. I quickly ordered one. Hilariously enough, I received a letter from the supplier that I was their first customer! Hah!

Upon opening the package, I noticed the size of the TRNG. It's roughly 10.5 cm from end-to-end which makes it somewhat awkward for a device sitting in your USB port on your laptop. It would work fine sitting in the back of a desktop or server, out of the way, but on my Thinkpad T61, it's a bit large to be sitting there 24/7 feeding my kernel CSPRNG.
Plugging the device in, the kernel actually sees two USB devices, not just one, and sets them up as /dev/ttyUSB0 and /dev/ttyUSB1. Curious. Downloading the software ZIP file from their webpage, and looking through it, the following UDEV rules are provided:
$ cat /etc/udev/rules.d/98-kidekin.rules #SYMLINK+= method works on more systems, if it does not on your system, please switch to the NAME= method. #disable the unused port. #SUBSYSTEM=="tty", ATTRS{interface}=="kidekin_trng", ATTRS{bInterfaceNumber}=="00", NAME="kidekin_dont_use", MODE="0000", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_CANDIDATE}="0" SUBSYSTEM=="tty", ATTRS{interface}=="kidekin_trng", ATTRS{bInterfaceNumber}=="00", SYMLINK+="kidekin_dont_use", MODE="0000", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_CANDIDATE}="0" #connect kidekin TRNG to /dev/random #SUBSYSTEM=="tty", ATTRS{interface}=="kidekin_trng", ATTRS{bInterfaceNumber}=="01", NAME="kidekin_trng", MODE="0777", RUN+="/bin/stty raw -echo -crtscts -F /dev/kidekin_trng speed 3000000", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_CANDIDATE}="0" SUBSYSTEM=="tty", ATTRS{interface}=="kidekin_trng", ATTRS{bInterfaceNumber}=="01", SYMLINK+="kidekin_trng", MODE="0777", RUN+="/bin/stty raw -echo -crtscts -F /dev/kidekin_trng speed 3000000", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_CANDIDATE}="0" SUBSYSTEM=="tty", ATTRS{interface}=="kidekin_trng", ATTRS{bInterfaceNumber}=="01", RUN+="/etc/init.d/rng-tools restart"
This is a bit assuming, and a bit overdoing it IMO, so I simplified it, and setup the following:
SUBSYSTEM=="tty", ATTRS{interface}=="kidekin_trng", ATTRS{bInterfaceNumber}=="01", SYMLINK+="kidekin", MODE="0777", RUN+="/bin/stty raw -echo -crtscts -F /dev/kidekin speed 3000000", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_CANDIDATE}="0"
This avoids setting up a "do not use" symlink for the unnecessary USB device, and changes the symlink of the usable USB device to /dev/kidekin. This also doesn't restart rngd(8), as I'll administer that on my own. At this point, I am ready for testing.
First and foremost, I wanted to test its throughput:
$ dd if=/dev/kidekin count=1G | pv -a > /dev/null [ 282KiB/s]
The device held stable at 282 KBps or roughly 2.2 Mbps. This is 75.2 KBps per dollar for my $30 purchase. Not bad.
The Kidekin is based on astable free running oscillators, or multivibrators. Unfortunately, a security proof does not accompany the device. So, while this may hold up to the suite of randomness tests, the output may not be cryptographically secure, and could also potentially be backdoored, as verifying the hardware is not easily doable. So, let's see if it at least holds up to the randomness tests. I created a 256 MB file, and ran the standard suites of tests:
$ dd if=/dev/kidekin of=entropy.kidekin bs=1M count=256 iflag=fullblock 256+0 records in 256+0 records out 268435456 bytes (268 MB) copied, 928.326 s, 289 kB/s
At this point, I can start my testing. First, let's quantify the amount of entropy per byte, as well as some basic tests with ent(1):
$ ent entropy.kidekin Entropy = 7.999999 bits per byte. Optimum compression would reduce the size of this 268435456 byte file by 0 percent. Chi square distribution for 268435456 samples is 248.92, and randomly would exceed this value 59.56 percent of the times. Arithmetic mean value of data bytes is 127.4924 (127.5 = random). Monte Carlo value for Pi is 3.141825693 (error 0.01 percent). Serial correlation coefficient is -0.000003 (totally uncorrelated = 0.0).
Everything good so far. How about the FIPS 140-2 tests for randomness:
$ rngtest < entropy.kidekin rngtest 2-unofficial-mt.14 Copyright (c) 2004 by Henrique de Moraes Holschuh This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. rngtest: starting FIPS tests... rngtest: entropy source exhausted! rngtest: bits received from input: 2147483648 rngtest: FIPS 140-2 successes: 107292 rngtest: FIPS 140-2 failures: 82 rngtest: FIPS 140-2(2001-10-10) Monobit: 14 rngtest: FIPS 140-2(2001-10-10) Poker: 13 rngtest: FIPS 140-2(2001-10-10) Runs: 26 rngtest: FIPS 140-2(2001-10-10) Long run: 30 rngtest: FIPS 140-2(2001-10-10) Continuous run: 0 rngtest: input channel speed: (min=317.891; avg=7386.982; max=19073.486)Mibits/s rngtest: FIPS tests speed: (min=6.563; avg=109.376; max=114.901)Mibits/s rngtest: Program run time: 19261018 microseconds $ echo $? 1
Again, so far so good. Some failures are expected with random input of this size. 82 failures versus 107292 successes is right on par with the tests. Now the Dieharder battery of tests:
$ dieharder -a < entropy.kidekin #=============================================================================# # dieharder version 3.31.1 Copyright 2003 Robert G. Brown # #=============================================================================# rng_name |rands/second| Seed | mt19937| 8.99e+07 | 722892634| #=============================================================================# test_name |ntup| tsamples |psamples| p-value |Assessment #=============================================================================# diehard_birthdays| 0| 100| 100|0.87388974| PASSED diehard_operm5| 0| 1000000| 100|0.25081726| PASSED diehard_rank_32x32| 0| 40000| 100|0.80329585| PASSED diehard_rank_6x8| 0| 100000| 100|0.87234234| PASSED diehard_bitstream| 0| 2097152| 100|0.27873738| PASSED diehard_opso| 0| 2097152| 100|0.05958924| PASSED diehard_oqso| 0| 2097152| 100|0.10540020| PASSED diehard_dna| 0| 2097152| 100|0.30006047| PASSED diehard_count_1s_str| 0| 256000| 100|0.43809130| PASSED diehard_count_1s_byt| 0| 256000| 100|0.29758303| PASSED diehard_parking_lot| 0| 12000| 100|0.78081639| PASSED diehard_2dsphere| 2| 8000| 100|0.58294587| PASSED diehard_3dsphere| 3| 4000| 100|0.04012616| PASSED diehard_squeeze| 0| 100000| 100|0.97651988| PASSED diehard_sums| 0| 100| 100|0.01875349| PASSED diehard_runs| 0| 100000| 100|0.17566659| PASSED diehard_runs| 0| 100000| 100|0.78887310| PASSED diehard_craps| 0| 200000| 100|0.16369886| PASSED diehard_craps| 0| 200000| 100|0.42148915| PASSED marsaglia_tsang_gcd| 0| 10000000| 100|0.27534860| PASSED marsaglia_tsang_gcd| 0| 10000000| 100|0.45190499| PASSED sts_monobit| 1| 100000| 100|0.88204376| PASSED sts_runs| 2| 100000| 100|0.15277754| PASSED sts_serial| 1| 100000| 100|0.71489026| PASSED sts_serial| 2| 100000| 100|0.85005457| PASSED sts_serial| 3| 100000| 100|0.77631916| PASSED sts_serial| 3| 100000| 100|0.81111751| PASSED sts_serial| 4| 100000| 100|0.72512842| PASSED sts_serial| 4| 100000| 100|0.68758000| PASSED sts_serial| 5| 100000| 100|0.69083583| PASSED sts_serial| 5| 100000| 100|0.09706031| PASSED sts_serial| 6| 100000| 100|0.52758972| PASSED sts_serial| 6| 100000| 100|0.27970465| PASSED sts_serial| 7| 100000| 100|0.07925569| PASSED sts_serial| 7| 100000| 100|0.25874891| PASSED sts_serial| 8| 100000| 100|0.33647659| PASSED sts_serial| 8| 100000| 100|0.80952471| PASSED sts_serial| 9| 100000| 100|0.99948911| WEAK sts_serial| 9| 100000| 100|0.32461849| PASSED sts_serial| 10| 100000| 100|0.69360795| PASSED sts_serial| 10| 100000| 100|0.96022345| PASSED sts_serial| 11| 100000| 100|0.91349333| PASSED sts_serial| 11| 100000| 100|0.95918606| PASSED sts_serial| 12| 100000| 100|0.69821905| PASSED sts_serial| 12| 100000| 100|0.57652285| PASSED sts_serial| 13| 100000| 100|0.28393582| PASSED sts_serial| 13| 100000| 100|0.45849491| PASSED sts_serial| 14| 100000| 100|0.30832853| PASSED sts_serial| 14| 100000| 100|0.89099315| PASSED sts_serial| 15| 100000| 100|0.87022105| PASSED sts_serial| 15| 100000| 100|0.06938123| PASSED sts_serial| 16| 100000| 100|0.79568629| PASSED sts_serial| 16| 100000| 100|0.53218489| PASSED rgb_bitdist| 1| 100000| 100|0.38552808| PASSED rgb_bitdist| 2| 100000| 100|0.79403454| PASSED rgb_bitdist| 3| 100000| 100|0.66811643| PASSED rgb_bitdist| 4| 100000| 100|0.84954470| PASSED rgb_bitdist| 5| 100000| 100|0.90198903| PASSED rgb_bitdist| 6| 100000| 100|0.98808244| PASSED rgb_bitdist| 7| 100000| 100|0.25730860| PASSED rgb_bitdist| 8| 100000| 100|0.43237015| PASSED rgb_bitdist| 9| 100000| 100|0.90916135| PASSED rgb_bitdist| 10| 100000| 100|0.81131338| PASSED rgb_bitdist| 11| 100000| 100|0.31361128| PASSED rgb_bitdist| 12| 100000| 100|0.40786889| PASSED rgb_minimum_distance| 2| 10000| 1000|0.03358258| PASSED rgb_minimum_distance| 3| 10000| 1000|0.99298827| PASSED rgb_minimum_distance| 4| 10000| 1000|0.47721533| PASSED rgb_minimum_distance| 5| 10000| 1000|0.86641982| PASSED rgb_permutations| 2| 100000| 100|0.10084049| PASSED rgb_permutations| 3| 100000| 100|0.99560585| WEAK rgb_permutations| 4| 100000| 100|0.42217190| PASSED rgb_permutations| 5| 100000| 100|0.95466090| PASSED rgb_lagged_sum| 0| 1000000| 100|0.64120688| PASSED rgb_lagged_sum| 1| 1000000| 100|0.22106106| PASSED rgb_lagged_sum| 2| 1000000| 100|0.41244281| PASSED rgb_lagged_sum| 3| 1000000| 100|0.98880097| PASSED rgb_lagged_sum| 4| 1000000| 100|0.78380177| PASSED rgb_lagged_sum| 5| 1000000| 100|0.25533777| PASSED rgb_lagged_sum| 6| 1000000| 100|0.78150371| PASSED rgb_lagged_sum| 7| 1000000| 100|0.53903267| PASSED rgb_lagged_sum| 8| 1000000| 100|0.04436257| PASSED rgb_lagged_sum| 9| 1000000| 100|0.77174302| PASSED rgb_lagged_sum| 10| 1000000| 100|0.54862612| PASSED rgb_lagged_sum| 11| 1000000| 100|0.48691334| PASSED rgb_lagged_sum| 12| 1000000| 100|0.06308057| PASSED rgb_lagged_sum| 13| 1000000| 100|0.42530804| PASSED rgb_lagged_sum| 14| 1000000| 100|0.86907366| PASSED rgb_lagged_sum| 15| 1000000| 100|0.66262930| PASSED rgb_lagged_sum| 16| 1000000| 100|0.85485044| PASSED rgb_lagged_sum| 17| 1000000| 100|0.39817394| PASSED rgb_lagged_sum| 18| 1000000| 100|0.90608610| PASSED rgb_lagged_sum| 19| 1000000| 100|0.94996515| PASSED rgb_lagged_sum| 20| 1000000| 100|0.78715690| PASSED rgb_lagged_sum| 21| 1000000| 100|0.93364519| PASSED rgb_lagged_sum| 22| 1000000| 100|0.84438533| PASSED rgb_lagged_sum| 23| 1000000| 100|0.77439531| PASSED rgb_lagged_sum| 24| 1000000| 100|0.12530311| PASSED rgb_lagged_sum| 25| 1000000| 100|0.79035917| PASSED rgb_lagged_sum| 26| 1000000| 100|0.93286961| PASSED rgb_lagged_sum| 27| 1000000| 100|0.32567247| PASSED rgb_lagged_sum| 28| 1000000| 100|0.39563718| PASSED rgb_lagged_sum| 29| 1000000| 100|0.15628693| PASSED rgb_lagged_sum| 30| 1000000| 100|0.69368810| PASSED rgb_lagged_sum| 31| 1000000| 100|0.00197963| WEAK rgb_lagged_sum| 32| 1000000| 100|0.23325783| PASSED rgb_kstest_test| 0| 10000| 1000|0.18940877| PASSED dab_bytedistrib| 0| 51200000| 1|0.57007834| PASSED dab_dct| 256| 50000| 1|0.76567665| PASSED Preparing to run test 207. ntuple = 0 dab_filltree| 32| 15000000| 1|0.60537852| PASSED dab_filltree| 32| 15000000| 1|0.78894908| PASSED Preparing to run test 208. ntuple = 0 dab_filltree2| 0| 5000000| 1|0.11775507| PASSED dab_filltree2| 1| 5000000| 1|0.34799105| PASSED Preparing to run test 209. ntuple = 0 dab_monobit2| 12| 65000000| 1|0.69182598| PASSED
Finally, a visual check on the data, even though it's safe to assume that it's "true random" given the previous testing:
$ dd if=white.bmp of=entropy.kidekin bs=1 count=54 conv=notrunc 54+0 records in 54+0 records out 54 bytes (54 B) copied, 0.000547208 s, 98.7 kB/s $ gimp entropy.kidekin # convert to grayscale, export as "entropy.png" $ optipng entropy.png ** Processing: entropy.png 512x512 pixels, 8 bits/pixel, grayscale Input IDAT size = 250107 bytes Input file size = 250564 bytes Trying: zc = 9 zm = 8 zs = 0 f = 0 IDAT size = 215319 zc = 9 zm = 8 zs = 1 f = 0 IDAT size = 214467 zc = 1 zm = 8 zs = 2 f = 0 IDAT size = 214467 zc = 9 zm = 8 zs = 3 f = 0 IDAT size = 214467 Selecting parameters: zc = 1 zm = 8 zs = 2 f = 0 IDAT size = 214467 Output IDAT size = 214467 bytes (35640 bytes decrease) Output file size = 214564 bytes (36000 bytes = 14.37% decrease)
And the result is:
My conclusion of the Kidekin TRNG is positive. I love the throughput of the device, loved the price, and aside from the UDEV rule, it is plug-and-play. Unfortunately, the TRNG is a bit on the big side for a physical device, and because it doesn't come with a security proof, and the hardware design is closed, I would be skeptical to trust it for your random numbers directly. Instead, I would recommend adding it the Linux kernel's CSPRNG, and rely on /dev/urandom instead. This is trivial with rngd(8). But, overall, I am very pleased with the device, and which I had actually purchased a second one.
{ 9 } Comments