diff --git a/README.md b/README.md index 349419a..23f56a1 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ A Java implementation of the BIP-0038 Draft: [Passphrase-protected private key]( ## Usage -`BIP38.encryptEC(password, lot, sequence)` generates an encrypted key starting with 6P. -If you don't care about lot/sequence (if you don't know what they are, you don't care), - just use -1 for the lot. That will cause those parameters to be ignored. +`BIP38.generateEncryptedKey(password)` generates an encrypted key starting with "6P". `BIP38.encryptNoEC(password, encodedKey, isCompressed)` encrypts a known key. @@ -16,7 +14,7 @@ If you don't care about lot/sequence (if you don't know what they are, you don't Key generation: - `encrypt("hello", -1, -1)` + `generateEncryptedKey("hello")` might produce diff --git a/src/main/java/com/fruitcat/bitcoin/BIP38.java b/src/main/java/com/fruitcat/bitcoin/BIP38.java index 353415d..3368d50 100644 --- a/src/main/java/com/fruitcat/bitcoin/BIP38.java +++ b/src/main/java/com/fruitcat/bitcoin/BIP38.java @@ -34,10 +34,7 @@ import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.GeneralSecurityException; -import java.security.Key; -import java.security.SecureRandom; -import java.security.Security; +import java.security.*; import java.util.Arrays; public class BIP38 { @@ -51,16 +48,27 @@ public class BIP38 { /** * Generates an encrypted key with EC multiplication. * Only uncompressed format for now. - * if lot is less than 0, lot and sequence are ignored. + * * @param password * @return * @throws UnsupportedEncodingException * @throws GeneralSecurityException * @throws AddressFormatException */ - public static String encryptEC(String password, int lot, int sequence) throws UnsupportedEncodingException, GeneralSecurityException, AddressFormatException { + public static String generateEncryptedKey(String password) throws UnsupportedEncodingException, GeneralSecurityException, AddressFormatException { + + byte[] intermediate = Arrays.copyOfRange(Base58.decode(intermediatePassphrase(password, -1, -1)), 0, 53); + return encryptedKeyFromIntermediate(intermediate, -1); + } - byte[] intermediate = Base58.decode(intermediatePassphrase(password, lot, sequence)); + /** + * if lot is less than 0, lot and sequence are ignored. + * @param intermediate + * @param lot + * @return + * @throws GeneralSecurityException + */ + public static String encryptedKeyFromIntermediate(byte[] intermediate, int lot) throws GeneralSecurityException { byte flagByte = (lot > 0) ? (byte) 4 : (byte) 0; //uncompressed byte[] ownerEntropy = new byte[8]; @@ -89,7 +97,7 @@ public static String encryptEC(String password, int lot, int sequence) throws Un byte[] m2 = new byte[16]; for (int i = 0; i < 16; i++) { m1[i] = (byte) (seedB[i] ^ derivedHalf1[i]); - m2[i] = (byte) (m2[i] ^ derivedHalf1[16 + i]); + } Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC"); @@ -98,6 +106,9 @@ public static String encryptEC(String password, int lot, int sequence) throws Un byte[] encryptedPart1 = cipher.doFinal(m1); System.arraycopy(encryptedPart1, 8, m2, 0, 8); System.arraycopy(seedB, 16, m2, 8, 8); + for (int i = 0 ; i < 16; i ++) { + m2[i] = (byte) (m2[i] ^ derivedHalf1[16 + i]); + } byte[] encryptedPart2 = cipher.doFinal(m2); byte[] encryptedPrivateKey = new byte[39]; @@ -236,14 +247,14 @@ public static String decryptEC(String password, byte[] encryptedKey) throws Unsu byte[] m2 = cipher.doFinal(encryptedPart2); byte[] encryptedPart1 = new byte[16]; + System.arraycopy(encryptedKey, 15, encryptedPart1, 0, 8); + byte[] seedB = new byte[24]; for (int i = 0; i < 16; i++) { m2[i] = (byte) (m2[i] ^ derivedHalf1[16 + i]); } - System.arraycopy(m2, 0, encryptedPart1, 8, 8); - System.arraycopy(encryptedKey, 15, encryptedPart1, 0, 8); byte[] m1 = cipher.doFinal(encryptedPart1); @@ -276,7 +287,6 @@ public static String encryptNoEC(String encodedPrivateKey, String password, bool DumpedPrivateKey dk = new DumpedPrivateKey(MainNetParams.get(), encodedPrivateKey); ECKey key = dk.getKey(); - System.out.println(key.getPrivateKeyEncoded(MainNetParams.get())); byte[] keyBytes = key.getPrivKeyBytes(); String address = key.toAddress(MainNetParams.get()).toString(); byte[] tmp = address.getBytes("ASCII"); @@ -345,8 +355,8 @@ public static String decryptNoEC(String password, byte[] encryptedKey) throws Un // generate a key, decrypt it, print the decrypted key and the address. public static void main(String args[]) throws Exception { - String encryptedKey = encryptEC("hello", 1, 1); - System.out.println(encryptedKey); + String encryptedKey = generateEncryptedKey("hello"); + System.out.println("key is:" + encryptedKey); String key = decrypt("hello", encryptedKey); DumpedPrivateKey dk = new DumpedPrivateKey(MainNetParams.get(), key); ECKey k = dk.getKey(); diff --git a/src/main/java/com/fruitcat/bitcoin/Utils.java b/src/main/java/com/fruitcat/bitcoin/Utils.java index 07d6e30..bba4435 100644 --- a/src/main/java/com/fruitcat/bitcoin/Utils.java +++ b/src/main/java/com/fruitcat/bitcoin/Utils.java @@ -61,7 +61,8 @@ public static byte[] sha256ripe160(byte[] data) throws NoSuchAlgorithmException } //for debugging - protected static void pb(byte [] x) { + protected static void pb(String name, byte [] x) { + System.out.print(name + ": "); for (byte b : x) { int l = b >= 0 ? b : 256 + b; System.out.print(l + " "); diff --git a/src/test/java/com/fruitcat/bitcoin/BIP38Test.java b/src/test/java/com/fruitcat/bitcoin/BIP38Test.java index a784dd8..68f62bd 100644 --- a/src/test/java/com/fruitcat/bitcoin/BIP38Test.java +++ b/src/test/java/com/fruitcat/bitcoin/BIP38Test.java @@ -1,7 +1,10 @@ package com.fruitcat.bitcoin; +import com.google.bitcoin.core.Base58; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; + +import java.util.Arrays; import java.util.Random; /** @@ -14,7 +17,7 @@ public class BIP38Test { //EC multiply, no compression, no lot/sequence numbers @Test - public void decryptECNoCompressionNoLotTest() throws Exception { + public void decryptECNoCompressionNoLot() throws Exception { String encryptedKey = "6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX"; String key = "5K4caxezwjGCGfnoPTZ8tMcJBLB7Jvyjv4xxeacadhq8nLisLR2"; String decryptedKey = BIP38.decrypt(testPass, encryptedKey); @@ -36,9 +39,16 @@ public void randomRoundTripNoEC() throws Exception { byte[] r = new byte[16]; (new Random()).nextBytes(r); String randomPass = new String(r, "ASCII"); - System.out.println(randomPass); String key = "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"; String encryptedKey = BIP38.encryptNoEC(key, randomPass, false); assertEquals(key, (BIP38.decrypt(randomPass, encryptedKey))); } + + //generate an encrypted key and make sure it looks ok. + @Test + public void generateEncryptedKey() throws Exception { + String k = BIP38.generateEncryptedKey(testPass); + String dk = BIP38.decrypt(testPass, k); + assertEquals(dk.charAt(0), '5'); + } }