From f5065b215bca182e129a1c3fda2e8bfa671a1792 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Wed, 23 Nov 2016 09:26:25 -0500 Subject: [PATCH] Introducing the crypto package with utilities to generate random numbers --- .gitignore | 2 + .../com/luminiasoft/bitshares/BrainKey.java | 65 +++---- .../java/com/luminiasoft/bitshares/Main.java | 12 +- .../java/com/luminiasoft/bitshares/Test.java | 16 ++ .../bitshares/crypto/AndroidRandomSource.java | 61 +++++++ .../bitshares/crypto/EntropySource.java | 19 ++ .../bitshares/crypto/RandomSource.java | 26 +++ .../crypto/SecureRandomStrengthener.java | 162 ++++++++++++++++++ 8 files changed, 329 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/luminiasoft/bitshares/crypto/AndroidRandomSource.java create mode 100644 src/main/java/com/luminiasoft/bitshares/crypto/EntropySource.java create mode 100644 src/main/java/com/luminiasoft/bitshares/crypto/RandomSource.java create mode 100644 src/main/java/com/luminiasoft/bitshares/crypto/SecureRandomStrengthener.java diff --git a/.gitignore b/.gitignore index c29a02a..8afe977 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ atlassian-ide-plugin.xml # Logs # ---- /*.log + +src/main/java/com/luminiasoft/bitshares/mycelium/* diff --git a/src/main/java/com/luminiasoft/bitshares/BrainKey.java b/src/main/java/com/luminiasoft/bitshares/BrainKey.java index ac623f8..7703128 100644 --- a/src/main/java/com/luminiasoft/bitshares/BrainKey.java +++ b/src/main/java/com/luminiasoft/bitshares/BrainKey.java @@ -1,30 +1,35 @@ -package com.luminiasoft.bitshares; - -import org.bitcoinj.core.ECKey; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Created by nelson on 11/19/16. - */ -public class BrainKey { - - private ECKey mPrivateKey; - - public BrainKey(String words, int sequence) { - String encoded = String.format("%s %d", words, sequence); - try { - MessageDigest md = MessageDigest.getInstance("SHA-512"); - byte[] bytes = md.digest(encoded.getBytes("UTF-8")); - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - byte[] result = sha256.digest(bytes); - mPrivateKey = ECKey.fromPrivate(result); - } catch (NoSuchAlgorithmException e) { - System.out.println("NoSuchAlgotithmException. Msg: " + e.getMessage()); - } catch (UnsupportedEncodingException e) { - System.out.println("UnsupportedEncodingException. Msg: " + e.getMessage()); - } - } -} +package com.luminiasoft.bitshares; + +import org.bitcoinj.core.Base58; +import org.bitcoinj.core.DumpedPrivateKey; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.NetworkParameters; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Created by nelson on 11/19/16. + */ +public class BrainKey { + + private ECKey mPrivateKey; + + public BrainKey(String words, int sequence) { + String encoded = String.format("%s %d", words, sequence); + try { + MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + mPrivateKey = ECKey.fromPrivate(sha256.digest(sha512.digest(encoded.getBytes("UTF-8")))); + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgotithmException. Msg: " + e.getMessage()); + } catch (UnsupportedEncodingException e) { + System.out.println("UnsupportedEncodingException. Msg: " + e.getMessage()); + } + } + + public ECKey getPrivateKey(){ + return this.mPrivateKey; + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/Main.java b/src/main/java/com/luminiasoft/bitshares/Main.java index 1ee53dc..fc5fd3e 100644 --- a/src/main/java/com/luminiasoft/bitshares/Main.java +++ b/src/main/java/com/luminiasoft/bitshares/Main.java @@ -6,13 +6,17 @@ import java.io.IOException; public class Main { // Brain key from Nelson's app referencing the bilthon-83 account - public static final String BRAIN_KEY = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH"; +// public static final String BRAIN_KEY = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH"; // WIF from Nelson's app referencing the bilthon-83 account - public static final String WIF = "5J96pne45qWM1WpektoeazN6k9Mt93jQ7LyueRxFfEMTiy6yxjM"; +// public static final String WIF = "5J96pne45qWM1WpektoeazN6k9Mt93jQ7LyueRxFfEMTiy6yxjM"; + + // Brain key from an empty account created by the cli_wallet + public static final String BRAIN_KEY = "TWIXT SERMO TRILLI AUDIO PARDED PLUMET BIWA REHUNG MAUDLE VALVULA OUTBURN FEWNESS ALIENER UNTRACE PRICH TROKER"; + + // WIF from an emty account created by the cli_wallet + public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5"; - // WIF from the cli_wallet instance -// public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5"; public static final String EXTERNAL_SIGNATURE = "1f36c41acb774fcbc9c231b5895ec9701d6872729098d8ea56d78dda72a6b54252694db85d7591de5751b7aea06871da15d63a1028758421607ffc143e53ef3306"; // Static block information used for transaction serialization tests diff --git a/src/main/java/com/luminiasoft/bitshares/Test.java b/src/main/java/com/luminiasoft/bitshares/Test.java index e143343..c031fca 100644 --- a/src/main/java/com/luminiasoft/bitshares/Test.java +++ b/src/main/java/com/luminiasoft/bitshares/Test.java @@ -504,6 +504,22 @@ public class Test { */ public void testBrainKeyOperations(){ BrainKey brainKey = new BrainKey(Main.BRAIN_KEY, 0); + ECKey key = brainKey.getPrivateKey(); + String wif = key.getPrivateKeyAsWiF(NetworkParameters.fromID(NetworkParameters.ID_MAINNET)); + System.out.println("wif compressed: "+wif); + String wif2 = key.decompress().getPrivateKeyAsWiF(NetworkParameters.fromID(NetworkParameters.ID_MAINNET)); + System.out.println("wif decompressed: "+wif2); + + byte[] pubKey1 = key.decompress().getPubKey(); + System.out.println("decompressed public key: "+Base58.encode(pubKey1)); + byte[] pubKey2 = key.getPubKey(); + System.out.println("compressed public key: "+Base58.encode(pubKey2)); + + System.out.println("pub key compressed : "+Util.bytesToHex(pubKey1)); + System.out.println("pub key uncompressed : "+Util.bytesToHex(pubKey2)); + + byte[] pubKey3 = key.getPubKeyPoint().getEncoded(true); + System.out.println("pub key compressed : "+Base58.encode(pubKey3)); } } diff --git a/src/main/java/com/luminiasoft/bitshares/crypto/AndroidRandomSource.java b/src/main/java/com/luminiasoft/bitshares/crypto/AndroidRandomSource.java new file mode 100644 index 0000000..5cf1ca1 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/crypto/AndroidRandomSource.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013, 2014 Megion Research and Development GmbH + * + * Licensed under the Microsoft Reference Source License (MS-RSL) + * + * This license governs use of the accompanying software. If you use the software, you accept this license. + * If you do not accept the license, do not use the software. + * + * 1. Definitions + * The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law. + * "You" means the licensee of the software. + * "Your company" means the company you worked for when you downloaded the software. + * "Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes + * of debugging your products, maintaining your products, or enhancing the interoperability of your products with the + * software, and specifically excludes the right to distribute the software outside of your company. + * "Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor + * under this license. + * + * 2. Grant of Rights + * (A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, + * worldwide, royalty-free copyright license to reproduce the software for reference use. + * (B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, + * worldwide, royalty-free patent license under licensed patents for reference use. + * + * 3. Limitations + * (A) No Trademark License- This license does not grant you any rights to use the Licensor’s name, logo, or trademarks. + * (B) If you begin patent litigation against the Licensor over patents that you think may apply to the software + * (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically. + * (C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, + * guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot + * change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, + * fitness for a particular purpose and non-infringement. + */ + +package com.luminiasoft.bitshares.crypto; + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + + +public class AndroidRandomSource implements RandomSource { + + @Override + public synchronized void nextBytes(byte[] bytes) { + // On Android we use /dev/urandom for providing random data + File file = new File("/dev/urandom"); + if (!file.exists()) { + throw new RuntimeException("Unable to generate random bytes on this Android device"); + } + try { + FileInputStream stream = new FileInputStream(file); + DataInputStream dis = new DataInputStream(stream); + dis.readFully(bytes); + dis.close(); + } catch (IOException e) { + throw new RuntimeException("Unable to generate random bytes on this Android device", e); + } + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/crypto/EntropySource.java b/src/main/java/com/luminiasoft/bitshares/crypto/EntropySource.java new file mode 100644 index 0000000..bb16c03 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/crypto/EntropySource.java @@ -0,0 +1,19 @@ +package com.luminiasoft.bitshares.crypto; + +import java.nio.ByteBuffer; + +/** + * A simple interface that can be used to retrieve entropy from any source. + * + * @author owlstead + */ +public interface EntropySource { + /** + * Retrieves the entropy. + * The position of the ByteBuffer must be advanced to the limit by any users calling this method. + * The values of the bytes between the position and limit should be set to zero by any users calling this method. + * + * @return entropy within the position and limit of the given buffer + */ + ByteBuffer provideEntropy(); +} \ No newline at end of file diff --git a/src/main/java/com/luminiasoft/bitshares/crypto/RandomSource.java b/src/main/java/com/luminiasoft/bitshares/crypto/RandomSource.java new file mode 100644 index 0000000..91e38d6 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/crypto/RandomSource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2013, 2014 Megion Research & Development GmbH + * + * 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 com.luminiasoft.bitshares.crypto; + +public interface RandomSource { + /** + * Generates a user specified number of random bytes + * + * @param bytes + * The array to fill with random bytes + */ + void nextBytes(byte[] bytes); +} diff --git a/src/main/java/com/luminiasoft/bitshares/crypto/SecureRandomStrengthener.java b/src/main/java/com/luminiasoft/bitshares/crypto/SecureRandomStrengthener.java new file mode 100644 index 0000000..54ddff2 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/crypto/SecureRandomStrengthener.java @@ -0,0 +1,162 @@ +package com.luminiasoft.bitshares.crypto; + +import java.nio.ByteBuffer; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.LinkedList; +import java.util.List; + +/** + * A strengthener that can be used to generate and re-seed random number + * generators that do not seed themselves appropriately. + * + * @author owlstead + */ +public class SecureRandomStrengthener { + private static final String DEFAULT_PSEUDO_RANDOM_NUMBER_GENERATOR = "SHA1PRNG"; + + private static final EntropySource mTimeEntropySource = new EntropySource() { + + final ByteBuffer timeBuffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE + * 2); + + @Override + public ByteBuffer provideEntropy() { + this.timeBuffer.clear(); + this.timeBuffer.putLong(System.currentTimeMillis()); + this.timeBuffer.putLong(System.nanoTime()); + this.timeBuffer.flip(); + return this.timeBuffer; + } + }; + + private final String algorithm; + private final List entropySources = new LinkedList(); + private final MessageDigest digest; + private final ByteBuffer seedBuffer; + + /** + * Generates an instance of a {@link SecureRandomStrengthener} that + * generates and re-seeds instances of {@code "SHA1PRNG"}. + * + * @return the strengthener, never null + */ + public static SecureRandomStrengthener getInstance() { + return new SecureRandomStrengthener( + DEFAULT_PSEUDO_RANDOM_NUMBER_GENERATOR); + } + + /** + * Generates an instance of a {@link SecureRandomStrengthener} that + * generates instances of the given argument. Note that the availability of + * the given algorithm arguments in not tested until generation. + * + * @param algorithm + * the algorithm indicating the {@link SecureRandom} instance to + * use + * @return the strengthener, never null + */ + public static SecureRandomStrengthener getInstance(final String algorithm) { + return new SecureRandomStrengthener(algorithm); + } + + private SecureRandomStrengthener(final String algorithm) { + if (algorithm == null || algorithm.length() == 0) { + throw new IllegalArgumentException( + "Please provide a PRNG algorithm string such as SHA1PRNG"); + } + + this.algorithm = algorithm; + try { + this.digest = MessageDigest.getInstance("SHA1"); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalStateException( + "MessageDigest to create seed not available", e); + } + this.seedBuffer = ByteBuffer.allocate(this.digest.getDigestLength()); + } + + /** + * Add an entropy source, which will be called for each generation and + * re-seeding of the given random number generator. + * + * @param source + * the source of entropy + */ + public void addEntropySource(final EntropySource source) { + if (source == null) { + throw new IllegalArgumentException( + "EntropySource should not be null"); + } + this.entropySources.add(source); + } + + /** + * Generates and seeds a random number generator of the configured + * algorithm. Calls the {@link EntropySource#provideEntropy()} method of all + * added sources of entropy. + * + * @return the random number generator + */ + public SecureRandom generateAndSeedRandomNumberGenerator() { + final SecureRandom secureRandom; + try { + secureRandom = SecureRandom.getInstance(this.algorithm); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalStateException("PRNG is not available", e); + } + + reseed(secureRandom); + return secureRandom; + } + + /** + * Re-seeds the random number generator. Calls the + * {@link EntropySource#provideEntropy()} method of all added sources of + * entropy. + * + * @param secureRandom + * the random number generator to re-seed + */ + public void reseed(final SecureRandom secureRandom) { + this.seedBuffer.clear(); + secureRandom.nextBytes(this.seedBuffer.array()); + + for (final EntropySource source : this.entropySources) { + final ByteBuffer entropy = source.provideEntropy(); + if (entropy == null) { + continue; + } + + final ByteBuffer wipeBuffer = entropy.duplicate(); + this.digest.update(entropy); + wipe(wipeBuffer); + } + + this.digest.update(mTimeEntropySource.provideEntropy()); + this.digest.update(this.seedBuffer); + this.seedBuffer.clear(); + // remove data from seedBuffer so it won't be retrievable + + // reuse + + try { + this.digest.digest(this.seedBuffer.array(), 0, + this.seedBuffer.capacity()); + } catch (final DigestException e) { + throw new IllegalStateException( + "DigestException should not be thrown", e); + } + secureRandom.setSeed(this.seedBuffer.array()); + + wipe(this.seedBuffer); + } + + private void wipe(final ByteBuffer buf) { + while (buf.hasRemaining()) { + buf.put((byte) 0); + } + } +} \ No newline at end of file