diff --git a/core/src/main/java/org/web3j/crypto/Keys.java b/core/src/main/java/org/web3j/crypto/Keys.java index 44c5f81d..64ba4a75 100644 --- a/core/src/main/java/org/web3j/crypto/Keys.java +++ b/core/src/main/java/org/web3j/crypto/Keys.java @@ -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; @@ -16,6 +15,8 @@ import org.web3j.utils.Numeric; import org.web3j.utils.Strings; +import static org.web3j.crypto.SecureRandomUtils.secureRandom; + /** * Crypto key utilities. @@ -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(); } diff --git a/core/src/main/java/org/web3j/crypto/LinuxSecureRandom.java b/core/src/main/java/org/web3j/crypto/LinuxSecureRandom.java new file mode 100644 index 00000000..63671e3a --- /dev/null +++ b/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 + * BitcoinJ implementation + * + * 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; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/web3j/crypto/SecureRandomUtils.java b/core/src/main/java/org/web3j/crypto/SecureRandomUtils.java new file mode 100644 index 00000000..258edbe4 --- /dev/null +++ b/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 issue. + */ +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() { } +} diff --git a/core/src/main/java/org/web3j/crypto/Wallet.java b/core/src/main/java/org/web3j/crypto/Wallet.java index f231214d..ff3abf90 100644 --- a/core/src/main/java/org/web3j/crypto/Wallet.java +++ b/core/src/main/java/org/web3j/crypto/Wallet.java @@ -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; @@ -21,6 +20,7 @@ import org.web3j.utils.Numeric; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.web3j.crypto.SecureRandomUtils.secureRandom; /** *

Ethereum wallet file management. For reference, refer to @@ -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; @@ -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; } } diff --git a/core/src/test/java/org/web3j/crypto/SecureRandomUtilsTest.java b/core/src/test/java/org/web3j/crypto/SecureRandomUtilsTest.java new file mode 100644 index 00000000..7b572b83 --- /dev/null +++ b/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()); + } +}