In which I try to guess where the recent SecureRandom is not seeded problem is on Android. First, some background…
Android tries to improve app startup time and memory usage by forking all normal apps from a
zygote
process which contains a copy of the dalvik VM preloaded with a selection of classes.
This means that a new app doesn’t need to do this on startup, and most apps can actually end up sharing most of the same physical pages (thanks to the miracle of copy-on-write pages).
RAND_bytes
behaviourOpenSSL’s RAND_bytes
function is OpenSSL’s interface to get random material from the single
CSPRNG within OpenSSL.
On first call, the default CSPRNG seeds itself from a variety of sources
(as available) and sets an initialized
flag. OpenSSL also provides a RAND_cleanup
function
to reset this flag (amongst other things) such that another call to RAND_bytes
will reseed
the generator.
Nothing in the Android codebase calls RAND_cleanup
. Lots of things call RAND_bytes
, some via
the tortuous route of JCA.
What would happen if something in zygote used RAND_bytes
? OpenSSL would consider its CSPRNG
seeded at that point. All apps forked from the zygote would inherit the same CSPRNG state,
and produce the same sequence of random material.
This would produce the observed behaviour of reused DSA k
values in Bitcoin transactions, when
the Bitcoin wallet was started twice from the same zygote (which roughly equates, I think, with
running a wallet, closing it for a while, and running it again within a single phone boot).
Like most callers, Android is not handling errors from RAND_bytes
properly or RAND_load_file
at all.
SecureRandom
interfaceAndroid’s default implementation of the Java SecureRandom
SPI (backed by RAND_bytes
) doesn’t
actually meet the documented or specified behaviour.
If I write new SecureRandom().nextBytes(thing)
I should get the following actions happening behind
the scenes, irrespective of which provider got selected:
new SecureRandom()
: selection and construction of a fresh unseeded generator.nextBytes(thing)
: automatic seeding of the generator from an entropy source.nextBytes(thing)
: filling thing
with some random material.Because Android backs its default SecureRandom
implementation with OpenSSL (which has a single
CSPRNG instance, remember) it skips the second step.
So: an application which diligently creates a new SecureRandom
every so often in the expectation
that it has fresh entropy gets surprised with the same generator every time, without any entropy
being added to it.
That really has to be fixed.