Skip to content

Commit

Permalink
Added secure random implementation for Android (hyperledger/web3j#146).
Browse files Browse the repository at this point in the history
  • Loading branch information
denis554 committed Sep 13, 2017
1 parent 3ae983f commit 2242275
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 6 deletions.
5 changes: 3 additions & 2 deletions core/src/main/java/org/web3j/crypto/Keys.java
Expand Up @@ -6,7 +6,6 @@
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
Expand All @@ -16,6 +15,8 @@
import org.web3j.utils.Numeric;
import org.web3j.utils.Strings;

import static org.web3j.crypto.SecureRandomUtils.secureRandom;


/**
* Crypto key utilities.
Expand Down Expand Up @@ -47,7 +48,7 @@ static KeyPair createSecp256k1KeyPair() throws NoSuchProviderException,

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", "BC");
ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec("secp256k1");
keyPairGenerator.initialize(ecGenParameterSpec, new SecureRandom());
keyPairGenerator.initialize(ecGenParameterSpec, secureRandom());
return keyPairGenerator.generateKeyPair();
}

Expand Down
104 changes: 104 additions & 0 deletions core/src/main/java/org/web3j/crypto/LinuxSecureRandom.java
@@ -0,0 +1,104 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.web3j.crypto;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.Provider;
import java.security.SecureRandomSpi;
import java.security.Security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* Implementation from
* <a href="https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/crypto/LinuxSecureRandom.java">BitcoinJ implementation</a>
*
* A SecureRandom implementation that is able to override the standard JVM provided implementation, and which simply
* serves random numbers by reading /dev/urandom. That is, it delegates to the kernel on UNIX systems and is unusable on
* other platforms. Attempts to manually set the seed are ignored. There is no difference between seed bytes and
* non-seed bytes, they are all from the same source.
*/
public class LinuxSecureRandom extends SecureRandomSpi {
private static final FileInputStream urandom;

private static class LinuxSecureRandomProvider extends Provider {
public LinuxSecureRandomProvider() {
super("LinuxSecureRandom", 1.0, "A Linux specific random number provider that uses /dev/urandom");
put("SecureRandom.LinuxSecureRandom", LinuxSecureRandom.class.getName());
}
}

private static final Logger log = LoggerFactory.getLogger(LinuxSecureRandom.class);

static {
try {
File file = new File("/dev/urandom");
// This stream is deliberately leaked.
urandom = new FileInputStream(file);
if (urandom.read() == -1)
throw new RuntimeException("/dev/urandom not readable?");
// Now override the default SecureRandom implementation with this one.
int position = Security.insertProviderAt(new LinuxSecureRandomProvider(), 1);

if (position != -1)
log.info("Secure randomness will be read from {} only.", file);
else
log.info("Randomness is already secure.");
} catch (FileNotFoundException e) {
// Should never happen.
log.error("/dev/urandom does not appear to exist or is not openable");
throw new RuntimeException(e);
} catch (IOException e) {
log.error("/dev/urandom does not appear to be readable");
throw new RuntimeException(e);
}
}

private final DataInputStream dis;

public LinuxSecureRandom() {
// DataInputStream is not thread safe, so each random object has its own.
dis = new DataInputStream(urandom);
}

@Override
protected void engineSetSeed(byte[] bytes) {
// Ignore.
}

@Override
protected void engineNextBytes(byte[] bytes) {
try {
dis.readFully(bytes); // This will block until all the bytes can be read.
} catch (IOException e) {
throw new RuntimeException(e); // Fatal error. Do not attempt to recover from this.
}
}

@Override
protected byte[] engineGenerateSeed(int i) {
byte[] bits = new byte[i];
engineNextBytes(bits);
return bits;
}
}
38 changes: 38 additions & 0 deletions core/src/main/java/org/web3j/crypto/SecureRandomUtils.java
@@ -0,0 +1,38 @@
package org.web3j.crypto;

import java.security.SecureRandom;

/**
* Utility class for working with SecureRandom implementation.
*
* This is to address issues with SecureRandom on Android. For more information, refer to the
* following <a href="https://github.com/web3j/web3j/issues/146">issue</a>.
*/
final class SecureRandomUtils {

private static final SecureRandom SECURE_RANDOM;

static {
if (isAndroidRuntime()) {
new LinuxSecureRandom();
}
SECURE_RANDOM = new java.security.SecureRandom();
}

static java.security.SecureRandom secureRandom() {
return SECURE_RANDOM;
}

// Taken from BitcoinJ implementation
// https://github.com/bitcoinj/bitcoinj/blob/3cb1f6c6c589f84fe6e1fb56bf26d94cccc85429/core/src/main/java/org/bitcoinj/core/Utils.java#L573
private static int isAndroid = -1;
static boolean isAndroidRuntime() {
if (isAndroid == -1) {
final String runtime = System.getProperty("java.runtime.name");
isAndroid = (runtime != null && runtime.equals("Android Runtime")) ? 1 : 0;
}
return isAndroid == 1;
}

private SecureRandomUtils() { }
}
6 changes: 2 additions & 4 deletions core/src/main/java/org/web3j/crypto/Wallet.java
Expand Up @@ -3,7 +3,6 @@
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.UUID;
import javax.crypto.BadPaddingException;
Expand All @@ -21,6 +20,7 @@
import org.web3j.utils.Numeric;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.web3j.crypto.SecureRandomUtils.secureRandom;

/**
* <p>Ethereum wallet file management. For reference, refer to
Expand All @@ -47,8 +47,6 @@
*/
public class Wallet {

private static SecureRandom SECURE_RANDOM = new SecureRandom();

private static final int N_LIGHT = 1 << 12;
private static final int P_LIGHT = 6;

Expand Down Expand Up @@ -239,7 +237,7 @@ static void validate(WalletFile walletFile) throws CipherException {

static byte[] generateRandomBytes(int size) {
byte[] bytes = new byte[size];
SECURE_RANDOM.nextBytes(bytes);
secureRandom().nextBytes(bytes);
return bytes;
}
}
20 changes: 20 additions & 0 deletions core/src/test/java/org/web3j/crypto/SecureRandomUtilsTest.java
@@ -0,0 +1,20 @@
package org.web3j.crypto;

import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.web3j.crypto.SecureRandomUtils.isAndroidRuntime;
import static org.web3j.crypto.SecureRandomUtils.secureRandom;

public class SecureRandomUtilsTest {

@Test
public void testSecureRandom() {
secureRandom().nextInt();
}

@Test
public void testIsNotAndroidRuntime() {
assertFalse(isAndroidRuntime());
}
}

0 comments on commit 2242275

Please sign in to comment.