NIST SP 800-22 Test Report

Overview

NIST SP 800-22 is a statistical test suite for (P)RNGs. It hosts several tests aimed at validating RNGs, primarily through hypothesis testing. You can read about the specifics of those tests in their own paper and documentation.

While the SP 800-22 is not as rigorous as some other test suites (such as the diehard tests for example), it is a strong test to expose fundamental flaws in RNGs by testing some basic statistics such as monobit frequency, block frequency, (non)overlapping templates, etc. As such, my usage of this test suite is mainly as a preliminary run aimed at exposing fundamental flaws in the generator.

Methodology

While NIST provides their own implementation of their test suite, it has some limintations. For example it only supports single threading which means tests on large data take a long time. Additionally, there are some known bugs and areas of improvement, so several people have made improved implementations of the test suite. I was made aware of this myself when I encountered such bugs – in particular, when I tried running the test suite on bitstreams produced by state-of-the-art CSPRNGs which are not known by any one to have any defects that differentiate their output from true random data (and they have been subject to intense scrutiny by crytography experts no less), I found that some tests were still consistently failing. In particular I found this to be the case with the Overlapping Template test, in which both my generator and CSPRNG output consistently gave low (< .05) p-values (sometimes much lower).

As a result I chose to use this well-known improved implementation of the test suite. You can read their README for some specifics on the improvements over their original that they made. Unfortunately even this implementation has some defects it seems, which I’ll discuss below. Since the original implementation gave results that indicated failure/non-randomness even on CSPRNG output (and therefore giving evidence of some flawed tests), I carried over the same suspicion to this improved version. Because of this I ran the test suite both on data from my generator and also from CSPRNG output, so that a baseline could be established, and the test results for my generator could be compared to those of a generator that is known to be a strong RNG. Therefore the results section will go over results of the test suite on Goobits generated random data, as well as provide a comparison against a known baseline.

Results

Goobits

Here is the overall summary that STS gives for a test consisting of 10,000 bitstreams, each of length 2^20 (a bit over a million, which is STS’s default value):

    185/188 tests passed successfully both the analyses.
 - The "Frequency" test passed both the analyses.
 - The "Block Frequency" test passed both the analyses.
 - The "Cumulative Sums" (forward) test passed both the analyses.
   The "Cumulative Sums" (backward) test passed both the analyses.
 - The "Runs" test passed both the analyses.
 - The "Longest Run of Ones" test passed both the analyses.
 - The "Binary Matrix Rank" test FAILED the uniformity analysis.
 - The "Discrete Fourier Transform" test FAILED both the analyses.
 - 147/148 of the "Non-overlapping Template Matching" tests passed both the analyses.
   1/148 of the "Non-overlapping Template Matching" tests FAILED the proportion analysis.
 - The "Overlapping Template Matching" test passed both the analyses.
 - The "Maurer's Universal Statistical" test passed both the analyses.
 - The "Approximate Entropy" test passed both the analyses.
 - 8/8 of the "Random Excursions" tests passed both the analyses.
 - 18/18 of the "Random Excursions Variant" tests passed both the analyses.
 - The "Serial" (first) test passed both the analyses.
   The "Serial" (second) test passed both the analyses.
 - The "Linear Complexity" test passed both the analyses.

However, this summary is not nearly as in depth as the individual fail counts and p-values for each of the tests, which are shown here:

Test                              Success   Fail  Total   Fail %  p-value Flag
------------------------------------------------------------------------------
ApproximateEntropy                   9876    124  10000    1.24%   0.010893 
BlockFrequency                       9887    113  10000    1.13%   0.106060 
CumulativeSums                      19776    224  20000    1.12%   0.049412 
DFT                                  9867    133  10000    1.33%   0.000883 <-- UNUSUAL
Frequency                            9881    119  10000    1.19%   0.034189 
LinearComplexity                     9888    112  10000    1.12%   0.124870 
LongestRun                           9909     91  10000    0.91%   0.829873 
NonOverlappingTemplate            1464981  15019 1480000   1.01%   0.035766 
OverlappingTemplate                  9886    114  10000    1.14%   0.089435 
RandomExcursions                    49771    485  50256    0.97%   0.790216 
RandomExcursionsVariant            112055   1021 113076    0.90%   0.999590 
Rank                                 9898    102  10000    1.02%   0.433772 
Runs                                 9900    100  10000    1.00%   0.513499 
Serial                              19816    184  20000    0.92%   0.880473 
Universal                            9906     94  10000    0.94%   0.740165 
TOTAL                             1765297  18035 1783332   1.0113% 0.065202

This table lists each of the tests, and the success/fail/total count for each of them. Here, each test is a test on 2^20 (or about one million) bits, and a failure of a single test represents a p-value under the 0.01 threshold. Therefore the expected fail-rate is exactly 1%, i.e. under the null hypothesis of random data we expect to see 1% failure rates. The p-value column gives the p-value for this binomial statistic, i.e., it gives the probability that we would see at least as many failrues as we do under the null hypothesis of random data. As you can see, all tests have fail-rates relatively close to the expected 1% failure rate, and only one test has a p-value that is statistically significant regarding this binomial statistic, which is for the DFT test.

In a vacuum, these results are not particularly indicative of a defect in the generator, but they are slightly concering. The p-value 0.000883 for DFT is quite low and is below the 0.001 threshold, which is quite significant. Similarly, we see a trend towards low p-values for many of the test, and the p-value for the total failure count across all runs is 0.065202. This is not extremely low and certainly not below any reasonable statistical significant threshold, but something that catches your eye nonetheless. All in all, the overall results do not point towards anything alarming, except for the low DFT p-value, and for the fact that six of the tests have p-values less than 0.1, whereas we should only expect 1.5 tests to meet this treshold on average for random data. Also, only 185/188 overall tests passed seems a bit low, so depending on the methodology to compute those pass/fail rates, that could be something worth investigating as well. So to get a better picture we will compare against a baseline in the next section.

CSPRNG

Now that we’ve established test results on data from the Goobits generator, we’ll run the same tests on the output of a CSPRNG that is known to be very strong. I use the python secrets module to generate the output, which draws directly from the OS’s CSPRNG, which in my case is Linux’s /dev/random which uses ChaCha20. We’ll use this as a baseline, and it will contextualize the Goobits results a bit more.

While generating random data from Goobits is slow (about 1kb/s), it is trivial to generate GBs of random output from CSPRNGs as they are very fast. As such, I am able to run tests on even more data than the 10,000 bitstreams that I did on Goobits. So, I ran the test suite on five times the data as Goobits, in the form of four tests on 10,000 bitstreams each. First, here are the “overall” results of each:

185/188 tests passed successfully both the analyses.
185/188 tests passed successfully both the analyses.
185/188 tests passed successfully both the analyses.
185/188 tests passed successfully both the analyses.
184/188 tests passed successfully both the analyses.

As you can see, 185/188 test passed is the norm, with one even dipping below. So our slightly concerning result of 185/188 passed on Goobits data is no longer conserning at all: we’ve confirmed that a strong generator performs exactly the same. We can easily conclude that this is a property of the test implementation and this failure rate is to be expected even on random data.

Now for the more in depth results:

------------------------ RUN 0 -----------------------------------------------
Test                              Success   Fail  Total   Fail %  p-value Flag
------------------------------------------------------------------------------
ApproximateEntropy                   9886    114   10000    1.14% 0.089435 
BlockFrequency                       9896    104   10000    1.04% 0.357146 
CumulativeSums                      19817    183   20000    0.92% 0.894445 
DFT                                  9884    116   10000    1.16% 0.062223 
Frequency                            9909     91   10000    0.91% 0.829873 
LinearComplexity                     9906     94   10000    0.94% 0.740165 
LongestRun                           9895    105   10000    1.05% 0.320865 
NonOverlappingTemplate            1465099  14901 1480000    1.01% 0.203075 
OverlappingTemplate                  9909     91   10000    0.91% 0.829873 
RandomExcursions                    49995    549   50544    1.09% 0.028324 
RandomExcursionsVariant            112523   1201  113724    1.06% 0.030518 
Rank                                 9896    104   10000    1.04% 0.357146 
Runs                                 9897    103   10000    1.03% 0.394889 
Serial                              19790    210   20000    1.05% 0.247787 
Universal                            9876    124   10000    1.24% 0.010893
TOTAL                             1766178  18090 1784268  1.0139% 0.031862
------------------------ RUN 1 -----------------------------------------------
Test                              Success   Fail  Total   Fail %  p-value Flag
------------------------------------------------------------------------------
ApproximateEntropy                   9894    106   10000    1.06% 0.286326 
BlockFrequency                       9887    113   10000    1.13% 0.106060 
CumulativeSums                      19829    171   20000    0.85% 0.983782 
DFT                                  9880    120   10000    1.20% 0.027597 
Frequency                            9906     94   10000    0.94% 0.740165 
LinearComplexity                     9890    110   10000    1.10% 0.169394 
LongestRun                           9896    104   10000    1.04% 0.357146 
NonOverlappingTemplate            1465108  14892 1480000    1.01% 0.224677 
OverlappingTemplate                  9893    107   10000    1.07% 0.253758 
RandomExcursions                    49579    573   50152    1.14% 0.000897 <-- UNUSUAL
RandomExcursionsVariant            111836   1006  112842    0.89% 0.999909 
Rank                                 9878    122   10000    1.22% 0.017592 
Runs                                 9896    104   10000    1.04% 0.357146 
Serial                              19795    205   20000    1.03% 0.370626 
Universal                            9886    114   10000    1.14% 0.089435
TOTAL                             1765053  17941 1782994  1.0062% 0.202553
------------------------ RUN 2 -----------------------------------------------
Test                              Success   Fail  Total   Fail %  p-value Flag
------------------------------------------------------------------------------
ApproximateEntropy                   9910     90   10000    0.90% 0.854899 
BlockFrequency                       9897    103   10000    1.03% 0.394889 
CumulativeSums                      19780    220   20000    1.10% 0.084441 
DFT                                  9877    123   10000    1.23% 0.013894 
Frequency                            9886    114   10000    1.14% 0.089435 
LinearComplexity                     9883    117   10000    1.17% 0.051337 
LongestRun                           9890    110   10000    1.10% 0.169394 
NonOverlappingTemplate            1465082  14918 1480000    1.01% 0.165826 
OverlappingTemplate                  9913     87   10000    0.87% 0.914972 
RandomExcursions                    49224    544   49768    1.09% 0.020611 
RandomExcursionsVariant            110837   1141  111978    1.02% 0.265882 
Rank                                 9916     84   10000    0.84% 0.954481 
Runs                                 9913     87   10000    0.87% 0.914972 
Serial                              19806    194   20000    0.97% 0.674641 
Universal                            9886    114   10000    1.14% 0.089435
TOTAL                             1763700  18046 1781746  1.0128% 0.043207
------------------------ RUN 3 -----------------------------------------------
Test                              Success   Fail  Total   Fail %  p-value Flag
------------------------------------------------------------------------------
ApproximateEntropy                   9900    100   10000    1.00% 0.513499 
BlockFrequency                       9904     96   10000    0.96% 0.669714 
CumulativeSums                      19806    194   20000    0.97% 0.674641 
DFT                                  9892    108   10000    1.08% 0.223340 
Frequency                            9904     96   10000    0.96% 0.669714 
LinearComplexity                     9894    106   10000    1.06% 0.286326 
LongestRun                           9907     93   10000    0.93% 0.772395 
NonOverlappingTemplate            1465103  14897 1480000    1.01% 0.212518 
OverlappingTemplate                  9890    110   10000    1.10% 0.169394 
RandomExcursions                    49487    545   50032    1.09% 0.024740 
RandomExcursionsVariant            111511   1061  112572    0.94% 0.975458 
Rank                                 9891    109   10000    1.09% 0.195194 
Runs                                 9911     89   10000    0.89% 0.877397 
Serial                              19813    187   20000    0.94% 0.831132 
Universal                            9883    117   10000    1.17% 0.051337 
TOTAL                             1764696  17908 1782604  1.0046% 0.269620
------------------------ RUN 4 -----------------------------------------------

Test                              Success   Fail  Total   Fail %  p-value Flag
------------------------------------------------------------------------------
ApproximateEntropy                   9891    109   10000    1.09% 0.195194 
BlockFrequency                       9893    107   10000    1.07% 0.253758 
CumulativeSums                      19817    183   20000    0.92% 0.894445 
DFT                                  9886    114   10000    1.14% 0.089435 
Frequency                            9914     86   10000    0.86% 0.930242 
LinearComplexity                     9902     98   10000    0.98% 0.593206 
LongestRun                           9910     90   10000    0.90% 0.854899 
NonOverlappingTemplate            1465071  14929 1480000    1.01% 0.144250 
OverlappingTemplate                  9884    116   10000    1.16% 0.062223 
RandomExcursions                    49342    594   49936    1.19% 0.000019 <-- UNUSUAL
RandomExcursionsVariant            111296   1060  112356    0.94% 0.973463 
Rank                                 9911     89   10000    0.89% 0.877397 
Runs                                 9907     93   10000    0.93% 0.772395 
Serial                              19793    207   20000    1.03% 0.318805 
Universal                            9878    122   10000    1.22% 0.017592 
TOTAL                             1764295  17997 1782292  1.0098% 0.095795

And the overall failure rate across all of these five runs is 0.010095, with a p-value of 0.002312.

Contextualizing Goobits Results with the CSPRNG results

Looking at the CSPRNG results, essentially all of the concerning elements of the Goobits results dissappear. First, the 185/188 overall tests was shown to be the norm and not an outlier. Second, the low p-value for the DFT test looks not to be an anomaly, as we saw two even lower p-values across the CSPRNG tests, with one being a whole order of magnitude lower at 0.000019. And finally, the overall high failure rate and corresponding low p-value for Goobits now looks more like a slight miscalibration of the test suite than something caused by Goobits. Specifically, just as we saw a fail rate closer to 1.01% for Goobits, we see the same trend with the CSPRNG results at 1.0095%. And more importantly, the p-value for the overall failure rate of the CSPRNG is actually lower than that of Goobits: 0.002312 compared to .06 (the latter of which is not even below any conventional 0.01 or even 0.05 threshold, while the former is).

All in all, there does not seem to be a statistically significant difference from the results of the Goobits generator vs the CSPRNG ones; in fact, on the key metric of overall failed tests Goobits performs slightly better (in terms of p-value, not raw fail-rate). While I could go in circles testing more and more CSPRNG data with this test suite, the already 50,000 bitstreams of one million bits are already quite strong evidence of this trend towards a slightly higher than expected fail-rate and lower p-values, and it seems it is likely due to a small miscalibration of the test since we see these results with a state-of-the-art CSPRNG.

Conclusion

I ran the improved STS randomness test on 10,000 bitstreams of about one million bits each of Goobits-generated data. Taken at face value the results are not particularly indicative of a generator defect, as all p-values but one are not below any statistically significant threshold. But the one failed p-value and the overall trend towards lower (< 0.1) p-values do raise some concern. However, after running the test suite on a state-of-the-art CSPRNG that is known to be a strong generator (and whose output is not known to be distinguishable from random), these concerns essentially vanish. The low p-value for the DFT test sits alongside two equally or more extreme p-values on the CSPRNG data, and the overall trend of low p-values / high test failure-rate is replicated in the CSPRNG results as well. In fact, the overall failure-rate p-value for the CSPRNG data (albeit with 5x as much data, which will drive any test miscalibration to more severe p-values) is actually an order of magnitude lower than that of the Goobits results. Overall, the Goobits results are not statistically distinguishable from the CSPRNG results, and hence this test suite did not provide any evidence of non-randomness or a defect in the Goobits generator.