package org.nick.androidkeystore; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import android.os.Build; import android.os.Process; // Based on http://android-developers.blogspot.jp/2013/08/some-securerandom-thoughts.html public class PRNGFixes { private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); private PRNGFixes() { } public static void apply() { applyOpenSSLFix(); } public static void applyOpenSSLFix() throws SecurityException { if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) || (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2)) { // No need to apply the fix return; } try { // Mix in the device- and invocation-specific seed. Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") .getMethod("RAND_seed", byte[].class) .invoke(null, generateSeed()); // Mix output of Linux PRNG into OpenSSL's PRNG int bytesRead = (Integer) Class .forName( "org.apache.harmony.xnet.provider.jsse.NativeCrypto") .getMethod("RAND_load_file", String.class, long.class) .invoke(null, "/dev/urandom", 1024); if (bytesRead != 1024) { throw new IOException( "Unexpected number of bytes read from Linux PRNG: " + bytesRead); } } catch (Exception e) { throw new SecurityException("Failed to seed OpenSSL PRNG", e); } } private static byte[] generateSeed() { try { ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); seedBufferOut.writeLong(System.currentTimeMillis()); seedBufferOut.writeLong(System.nanoTime()); seedBufferOut.writeInt(Process.myPid()); seedBufferOut.writeInt(Process.myUid()); seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); seedBufferOut.close(); return seedBuffer.toByteArray(); } catch (IOException e) { throw new SecurityException("Failed to generate seed", e); } } private static String getDeviceSerialNumber() { // We're using the Reflection API because Build.SERIAL is only available // since API Level 9 (Gingerbread, Android 2.3). try { return (String) Build.class.getField("SERIAL").get(null); } catch (Exception ignored) { return null; } } private static byte[] getBuildFingerprintAndDeviceSerial() { StringBuilder result = new StringBuilder(); String fingerprint = Build.FINGERPRINT; if (fingerprint != null) { result.append(fingerprint); } String serial = getDeviceSerialNumber(); if (serial != null) { result.append(serial); } try { return result.toString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 encoding not supported"); } } }