From 2ec25c4a68e0fc9b5a4f11b9f39a2519d9a4e2f5 Mon Sep 17 00:00:00 2001 From: hank Date: Fri, 7 Feb 2014 12:03:18 -0500 Subject: [PATCH 1/5] Ignores, dependencies --- .gitignore | 4 ++++ build.gradle | 8 +++----- 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c9d47c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +.gradle +.idea +*.iml diff --git a/build.gradle b/build.gradle index fb9bacd..2d095f0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,15 @@ apply plugin: 'java' repositories { - mavenCentral() - maven { - url "http://distribution.bitcoinj.googlecode.com/git/releases/" - } + mavenCentral() + mavenLocal() } dependencies { compile 'com.madgag:scprov-jdk15on:1.47.0.3' compile 'com.google.guava:guava:15.0' - compile 'com.google:bitcoinj:0.10.3' + compile 'com.google:bitcoinj:0.11-SNAPSHOT' compile 'org.bouncycastle:bcprov-jdk16:1.46' testCompile 'org.testng:testng:6.8.7' } From 1dd9bdff547d2631649beac30696bf5bab29a2e0 Mon Sep 17 00:00:00 2001 From: hank Date: Fri, 7 Feb 2014 12:03:26 -0500 Subject: [PATCH 2/5] Switching to SpongyCastle AES --- src/main/java/com/fruitcat/bitcoin/BIP38.java | 7 +- src/main/java/com/fruitcat/bitcoin/Utils.java | 68 +++++++++++++++---- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/fruitcat/bitcoin/BIP38.java b/src/main/java/com/fruitcat/bitcoin/BIP38.java index 0515a00..7cec286 100644 --- a/src/main/java/com/fruitcat/bitcoin/BIP38.java +++ b/src/main/java/com/fruitcat/bitcoin/BIP38.java @@ -21,7 +21,12 @@ package com.fruitcat.bitcoin; -import com.google.bitcoin.core.*; +import com.google.bitcoin.core.Address; +import com.google.bitcoin.core.AddressFormatException; +import com.google.bitcoin.core.Base58; +import com.google.bitcoin.core.DumpedPrivateKey; +import com.google.bitcoin.core.ECKey; +import com.google.bitcoin.core.NetworkParameters; import com.google.bitcoin.params.MainNetParams; import com.lambdaworks.crypto.SCrypt; import org.bouncycastle.asn1.sec.SECNamedCurves; diff --git a/src/main/java/com/fruitcat/bitcoin/Utils.java b/src/main/java/com/fruitcat/bitcoin/Utils.java index 60ad289..911e43a 100644 --- a/src/main/java/com/fruitcat/bitcoin/Utils.java +++ b/src/main/java/com/fruitcat/bitcoin/Utils.java @@ -20,10 +20,19 @@ import com.google.bitcoin.core.AddressFormatException; import com.google.bitcoin.core.Base58; import org.bouncycastle.math.ec.ECPoint; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.DataLengthException; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.engines.AESFastEngine; +import org.spongycastle.crypto.modes.CBCBlockCipher; +import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.GeneralSecurityException; @@ -84,30 +93,65 @@ public static String base58Check(byte [] b) throws NoSuchAlgorithmException { /** * Encrypts plaintext with AES - * @param plaintext + * @param plainTextAsBytes * @param key * @return * @throws GeneralSecurityException */ - public static byte[] AESEncrypt(byte[] plaintext, byte[] key) throws GeneralSecurityException { - Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC"); - Key aesKey = new SecretKeySpec(key, "AES"); - cipher.init(Cipher.ENCRYPT_MODE, aesKey); - return cipher.doFinal(plaintext); + public static byte[] AESEncrypt(byte[] plainTextAsBytes, byte[] key) throws GeneralSecurityException { + try + { + final BufferedBlockCipher cipher = new BufferedBlockCipher(new CBCBlockCipher(new AESFastEngine())); + KeyParameter aesKey = new KeyParameter(key); + + cipher.init(true, aesKey); + + final byte[] encryptedBytes = new byte[cipher.getOutputSize(plainTextAsBytes.length)]; + final int length = cipher.processBytes(plainTextAsBytes, 0, plainTextAsBytes.length, encryptedBytes, 0); + + cipher.doFinal(encryptedBytes, length); + + return encryptedBytes; + } + catch (final InvalidCipherTextException x) + { + throw new GeneralSecurityException("Could not encrypt bytes", x); + } + catch (final DataLengthException x) + { + throw new GeneralSecurityException("Could not encrypt bytes", x); + } } /** * Decrypts ciphertext with AES - * @param ciphertext + * @param cipherBytes * @param key * @return * @throws GeneralSecurityException */ - public static byte[] AESDecrypt(byte[] ciphertext, byte[] key) throws GeneralSecurityException { - Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC"); - Key aesKey = new SecretKeySpec(key, "AES"); - cipher.init(Cipher.DECRYPT_MODE, aesKey); - return cipher.doFinal(ciphertext); + public static byte[] AESDecrypt(byte[] cipherBytes, byte[] key) throws GeneralSecurityException { + try + { + final BufferedBlockCipher cipher = new BufferedBlockCipher(new CBCBlockCipher(new AESFastEngine())); + KeyParameter aesKey = new KeyParameter(key); + cipher.init(false, aesKey); + + final byte[] decryptedBytes = new byte[cipher.getOutputSize(cipherBytes.length)]; + final int length = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0); + + cipher.doFinal(decryptedBytes, length); + + return decryptedBytes; + } + catch (final InvalidCipherTextException x) + { + throw new GeneralSecurityException("Could not decrypt input string", x); + } + catch (final DataLengthException x) + { + throw new GeneralSecurityException("Could not decrypt input string", x); + } } /** From 1345c39924daebccae67a1caf4220004612bcbeb Mon Sep 17 00:00:00 2001 From: hank Date: Fri, 7 Feb 2014 12:19:39 -0500 Subject: [PATCH 3/5] New tests with litecoin netparams All tests pass - used addresses from liteaddress. --- .../java/com/fruitcat/bitcoin/BIP38Test.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/test/java/com/fruitcat/bitcoin/BIP38Test.java b/src/test/java/com/fruitcat/bitcoin/BIP38Test.java index 917c501..1487d51 100644 --- a/src/test/java/com/fruitcat/bitcoin/BIP38Test.java +++ b/src/test/java/com/fruitcat/bitcoin/BIP38Test.java @@ -19,6 +19,7 @@ public class BIP38Test { @Test public void noCompressionNoECMultiply() throws Exception { + BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get()); //test 1 String encryptedKey = "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg"; String key = "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"; @@ -33,6 +34,7 @@ public void noCompressionNoECMultiply() throws Exception { @Test public void compressionNoECMultiply() throws Exception { + BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get()); //test 1 String encryptedKey = "6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo"; String key = "L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP"; @@ -48,6 +50,7 @@ public void compressionNoECMultiply() throws Exception { //EC multiply, no compression, no lot/sequence numbers @Test public void ecMultiplyNoCompressionNoLot() throws Exception { + BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get()); //test 1 String encryptedKey = "6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX"; String key = "5K4caxezwjGCGfnoPTZ8tMcJBLB7Jvyjv4xxeacadhq8nLisLR2"; @@ -70,6 +73,7 @@ public void ecMultiplyNoCompressionNoLot() throws Exception { //EC multiply, no compression, lot/sequence @Test public void ecMultiplyNoCompressionLot() throws Exception { + BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get()); //test 1 String encryptedKey = "6PgNBNNzDkKdhkT6uJntUXwwzQV8Rr2tZcbkDcuC9DZRsS6AtHts4Ypo1j"; String key = "5JLdxTtcTHcfYcmJsNVy1v2PMDx432JPoYcBTVVRHpPaxUrdtf8"; @@ -92,6 +96,7 @@ public void ecMultiplyNoCompressionLot() throws Exception { //round encrypt and decrypt with a random ascii password @Test public void randomRoundTripNoEC() throws Exception { + BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get()); byte[] r = new byte[16]; (new Random()).nextBytes(r); String randomPass = new String(r, "ASCII"); @@ -103,6 +108,7 @@ public void randomRoundTripNoEC() throws Exception { //generate an encrypted key and make sure it looks ok. @Test public void generateEncryptedKey() throws Exception { + BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get()); String k = BIP38.generateEncryptedKey(testPass); String dk = BIP38.decrypt(testPass, k); assertEquals(dk.charAt(0), '5'); @@ -111,9 +117,32 @@ public void generateEncryptedKey() throws Exception { //check confirmation code @Test public void checkConfirmation() throws Exception { + BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get()); byte[] intermediate = Arrays.copyOfRange(Base58.decode(BIP38.intermediatePassphrase(testPass, -1, -1)), 0, 53); GeneratedKey gk = BIP38.encryptedKeyFromIntermediate(intermediate); assert(BIP38.verify(testPass, gk)); assert(!BIP38.verify("garbage", gk)); } + + @Test + public void litecoinRandomRoundTripNoEC() throws Exception { + BIP38.setNetParams(org.litecoin.LitecoinParams.get()); + byte[] r = new byte[16]; + (new Random()).nextBytes(r); + String randomPass = new String(r, "ASCII"); + String key = "6uRPj6B3bPCqf9KZLzc1VWHsmZgXXhx5qdm69BV9hg454LNwGwX"; + String encryptedKey = BIP38.encryptNoEC(randomPass, key, false); + assertEquals(key, (BIP38.decrypt(randomPass, encryptedKey))); + } + + @Test + public void litecoinCompressionNoECMultiply() throws Exception { + BIP38.setNetParams(org.litecoin.LitecoinParams.get()); + // 6PfMZr9CPt4JvcEZnSRRD8F9a2dYJ3H999sLoLJD3Lxh5gF5gC64TY8j1Q + "Satoshi" + // == 6vHEw38JPP6fvNBxVUeb499S7JBfRX5dMGadWrjv3w78UHPD2ac + String encryptedKey = "6PfMZr9CPt4JvcEZnSRRD8F9a2dYJ3H999sLoLJD3Lxh5gF5gC64TY8j1Q"; + String key = "6vHEw38JPP6fvNBxVUeb499S7JBfRX5dMGadWrjv3w78UHPD2ac"; + String decryptedKey = BIP38.decrypt("Satoshi", encryptedKey); + assertEquals(decryptedKey, key); + } } \ No newline at end of file From 345ee92cf225096a6c66526977a10775b8714e22 Mon Sep 17 00:00:00 2001 From: hank Date: Fri, 7 Feb 2014 12:23:13 -0500 Subject: [PATCH 4/5] Fixing logging warning Just had to add an slf4j library to gradle --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2d095f0..4aab5aa 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,8 @@ dependencies { compile 'com.google.guava:guava:15.0' compile 'com.google:bitcoinj:0.11-SNAPSHOT' compile 'org.bouncycastle:bcprov-jdk16:1.46' - testCompile 'org.testng:testng:6.8.7' + testCompile 'org.testng:testng:6.8.7' + testCompile 'org.slf4j:slf4j-simple:1.6.1' } test { From 5172a5f11749a576e0aecb0912c0e161dc20e125 Mon Sep 17 00:00:00 2001 From: hank Date: Fri, 7 Feb 2014 12:29:54 -0500 Subject: [PATCH 5/5] Adding maven install --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 4aab5aa..e61be46 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,11 @@ repositories { mavenLocal() } +allprojects { + apply plugin: 'maven' + group = 'com.fruitcat.bitcoin' + version = '0.1-SNAPSHOT' +} dependencies { compile 'com.madgag:scprov-jdk15on:1.47.0.3'