Android Bitcoin wallets and PRNGs: a snapshot

In August 2013, Google determined that applications using the Java Cryptography Architecture (JCA) for key generation, signing, or random number generation do not receive cryptographically strong values on Android devices (affected versions: 4.1, 4.2, 4.3) due to improper initialization of the underlying PRNG.

Developers who use JCA for random number generation, key generation, or signing (SecureRandom, KeyGenerator, KeyPairGenerator, KeyAgreement, and Signature) rushed to update their applications to explicitly initialize the PRNG with entropy from /dev/urandom or /dev/random. Google also provided a suggested implementation, aptly called PRNGFixes.

In this blog post I would like to take a snapshot of the most common Android Bitcoin wallets using SecureRandom, and find out how they call it and how they fallback in case of an odd device without /dev/urandom. Finally, I talk about what went wrong with the wallet and why on some devices it was always generating the private key for the same address.

The good ones

schildbach Bitcoin Wallet

It uses a custom LinuxSecureRandom, which implements Google's advice about SecureRandom overriding the standard JVM provided implementation with a Unix-specific one, serving random numbers by reading /dev/urandom, and hard failing if unsuccessful (a very reasonable thing to do, IMHO).


It uses LinuxSecureRandom, overriding the standard JVM provided implementation with a Unix specific one, serving random numbers by reading /dev/urandom, and falling back to the default SecureRandom if unsuccessful.

Update: Reddit user songchenwen says that when Bither can't find /dev/urandom it shows an error to user and closes itself.

The creative one

AirBitz Bitcoin Wallet

It does not use /dev/urandom at all, and it follows a the more (even if bad), the better approach. The Android application builds a seed concatenating Build.MANUFACTURER + Build.DEVICE + Build.SERIAL + System.nanoTime() + a single 4-byte integer from an unpatched SecureRandom() (see code).

Everything from Build is very low entropy - for example, Build.MANUFACTURER is just motorola | htc | samsung...

The generated seed is passed to the Core API, in C++, which adds other things (code): number of seconds * number of microseconds + timestamp + clock ticks + CLOCKS_PER_SEC + getpid() + getppid(). Most of these things have very low entropy (PIDs, the timestamp, CLOCKS_PER_SEC).

Personally, I think this is unnecessarily complex. Just calling a SecureRandom(), preferably reading from /dev/urandom, would be better.

What went wrong with the Android wallet

Developers of the Android wallet wanted to go the extra mile, and not only use a patched SecureRandom with PRNGFixes reading from /dev/urandom, but, apparently, they did not trust it enough, and wanted more entropy.

They decided to use as a provider of high quality randomness, coming from atmospheric noise, and add entropy with the one already in SecureRandom + PRNGFixes, by mixing the original seed with the data coming from using XOR . To do that, they call SecureRandom#setSeed, which is something that should be avoided at all cost, since you are basically saying: my supplied entropy is better than /dev/urandom.

If /dev/urandom is not present (which is weird, but apparently it can happen on some Android devices), their SecureRandom + PRNGFixes implementation does not raise an exception, but falls back to the JVM provided implementation of SecureRandom, which is known to have entropy problems.

If the above happens, the vanilla implementation of SecureRandom#setSeed replaces the entropy entirely. So now the entropy is coming solely from

Guess what, it doesn't stop here. They queried over HTTP, without checking for status codes. This is a terrible thing to do, for two reasons:

  1. Since no HTTPS is used, anybody on the same network could trivially manipulate the data with a man-in-the-middle attack.
  2. No HTTP status code checking. To be fair, the Java code they were using would throw an IOException for any 4xx or 5xx response. But not for redirects.

To fully understand why not checking status codes is bad, let's not play hypothetical here. When decided to switch to HTTPS only, they set up 301 redirects when the HTTP site was queried, with the body set to Apache 2.2.22's default 301 Moved Permanently error page. Guess what? The beginning of that wonderful, always identical, zero-entropy HTML code replaced the oh-so-random bytes the Bitcoin wallet was expecting to come.

On devices where /dev/urandom was not present, always the same seed was fed to SecureRandom. This is equivalent to having xkcd's random number generator. In fact, the wallets always generated the same private key, for address 1Bn9ReEocMG1WEW1qYjuDrdFzEFFDCq43F.

As an emergency patch, for Android devices older than 4.4 they decided to rely on PIDs, milliseconds and UUIDs as a secondary source of entropy, instead of UUIDs, in general, are not required to be hard to guess. Their code was a bit confusing: they generate two UUIDs, and get the most significant bytes of the first and the least significant bytes of the second. That's not helping, and it's weird.

Luckily, that hack didn't last long, and now they are either relying on /dev/urandom alone or the low-entropy-but-still-better-than-none-at-all milliseconds + nanoseconds + PIDs + UID + Build.FINGERPRINT + Build.SERIAL, in the supposedly very rare case of /dev/urandom not being present.