Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build
.gradle
.idea
*.iml
16 changes: 10 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
apply plugin: 'java'

repositories {
mavenCentral()
maven {
url "http://distribution.bitcoinj.googlecode.com/git/releases/"
}
mavenCentral()
mavenLocal()
}

allprojects {
apply plugin: 'maven'
group = 'com.fruitcat.bitcoin'
version = '0.1-SNAPSHOT'
}

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'
testCompile 'org.testng:testng:6.8.7'
testCompile 'org.slf4j:slf4j-simple:1.6.1'
}

test {
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/com/fruitcat/bitcoin/BIP38.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
68 changes: 56 additions & 12 deletions src/main/java/com/fruitcat/bitcoin/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down
29 changes: 29 additions & 0 deletions src/test/java/com/fruitcat/bitcoin/BIP38Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand All @@ -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";
Expand All @@ -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";
Expand All @@ -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");
Expand All @@ -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');
Expand All @@ -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);
}
}