Permalink
Browse files

added noEC encryption/decryption and basic tests

  • Loading branch information...
1 parent a4fb2ab commit 0a9b2db4437a6094558221fe3a62b09720289e56 @dbasch committed Jan 17, 2014
Showing with 60 additions and 27 deletions.
  1. +9 −5 README.md
  2. +6 −0 build.gradle
  3. +45 −22 src/main/java/com/fruitcat/bitcoin/BIP38.java
View
@@ -8,9 +8,9 @@ A Java implementation of the BIP-0038 Draft: [Passphrase-protected private key](
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.decrypt(password, encrypted key)` yields the decrypted get.
+`BIP38.encryptNoEC(password, encodedKey, isCompressed)` encrypts a known key.
- Currently only encryptEC and decryptEC have been tested (and barely).
+`BIP38.decrypt(password, encryptedKey)` yields the decrypted get.
## Example:
@@ -36,9 +36,13 @@ If you don't care about lot/sequence (if you don't know what they are, you don't
## To do
- * implement the remaining functionality from the spec (e.g. generate compressed keys)
- * write some tests.
- * make the code cleaner.
+ * implement the remaining functionality from the spec.
+ * write more tests.
+
+## Note for OSX
+
+If you get an exception about an illegal key size, you probably need to install
+the [Unlimited Strength Jurisdiction Policy Files](http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html)
## Tips
View
@@ -13,6 +13,12 @@ dependencies {
compile 'com.google.guava:guava:15.0'
compile 'com.google:bitcoinj:0.10.3'
compile 'org.bouncycastle:bcprov-jdk16:1.46'
+ testCompile 'org.testng:testng:6.8.7'
+}
+
+test {
+ useTestNG()
+
}
apply plugin:'application'
@@ -183,12 +183,12 @@ public static String intermediatePassphrase(String password, int lot, int sequen
public static String decrypt(String password, String encryptedKey) throws
AddressFormatException, GeneralSecurityException, UnsupportedEncodingException {
byte[] encryptedKeyBytes = Base58.decode(encryptedKey);
- int l = encryptedKeyBytes.length;
String result;
- switch (l) {
- case 43: result = decryptEC(password, encryptedKeyBytes);
+ byte ec = encryptedKeyBytes[1];
+ switch (ec) {
+ case 0x43: result = decryptEC(password, encryptedKeyBytes);
break;
- case 53: result = decryptNoEC(password, encryptedKeyBytes);
+ case 0x42: result = decryptNoEC(password, encryptedKeyBytes);
break;
default: throw new RuntimeException("Invalid Key");
}
@@ -264,24 +264,26 @@ public static String decryptEC(String password, byte[] encryptedKey) throws Unsu
}
/**
- * Encrypts a key without using EC multiplication. - UNTESTED, probably doesn't work.
+ * Encrypts a key without using EC multiplication.
* @param encodedPrivateKey
* @param password
* @param compressed
- * @param params
* @return
* @throws GeneralSecurityException
* @throws UnsupportedEncodingException
* @throws AddressFormatException
*/
- public static String encryptNoEC(String encodedPrivateKey, String password, boolean compressed, NetworkParameters params)
+ public static String encryptNoEC(String encodedPrivateKey, String password, boolean isCompressed)
throws GeneralSecurityException, UnsupportedEncodingException, AddressFormatException {
- byte[] keyBytes = Base58.decode(encodedPrivateKey);
- ECKey key = new ECKey(keyBytes, null);
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] d1 = digest.digest(key.toAddress(params).getHash160());
- byte[] hash = digest.digest(d1);
+ 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");
+ byte[] hash = Utils.doubleHash(tmp, 0, tmp.length);
byte[] addressHash = Arrays.copyOfRange(hash, 0, 4);
byte[] scryptKey = SCrypt.scrypt(password.getBytes("UTF8"), addressHash, 16384, 8, 8, 64);
byte[] derivedHalf1 = Arrays.copyOfRange(scryptKey, 0, 32);
@@ -293,30 +295,51 @@ public static String encryptNoEC(String encodedPrivateKey, String password, bool
k1[i] = (byte) (keyBytes[i] ^ derivedHalf1[i]);
k2[i] = (byte) (keyBytes[i+16] ^ derivedHalf1[i+16]);
}
+
Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
- Key aesKey1 = new SecretKeySpec(k1, "AES");
- Key aesKey2 = new SecretKeySpec(k2, "AES");
+ Key aesKey = new SecretKeySpec(derivedHalf2, "AES");
- cipher.init(Cipher.ENCRYPT_MODE, aesKey1);
- byte[] encryptedHalf1 = cipher.doFinal(derivedHalf2);
- cipher.init(Cipher.ENCRYPT_MODE, aesKey2);
- byte[] encryptedHalf2 = cipher.doFinal(derivedHalf2);
+ cipher.init(Cipher.ENCRYPT_MODE, aesKey);
+ byte[] encryptedHalf1 = cipher.doFinal(k1);
+ byte[] encryptedHalf2 = cipher.doFinal(k2);
byte[] encryptedPrivateKey = new byte[43];
encryptedPrivateKey[0] = 0x01;
encryptedPrivateKey[1] = 0x42;
- encryptedPrivateKey[2] = (byte) (compressed ? 0xe0 : 0xc0);
+ encryptedPrivateKey[2] = (byte) (isCompressed ? 0xe0 : 0xc0);
System.arraycopy(addressHash, 0, encryptedPrivateKey, 3, 4);
System.arraycopy(encryptedHalf1, 0, encryptedPrivateKey, 7, 16);
System.arraycopy(encryptedHalf2, 0, encryptedPrivateKey, 23, 16);
- System.arraycopy(addressHash, 0, encryptedPrivateKey, 39, 4);
+ byte[] checksum = Utils.doubleHash(encryptedPrivateKey, 0, 39);
+ System.arraycopy(checksum, 0, encryptedPrivateKey, 39, 4);
return Base58.encode(encryptedPrivateKey);
}
- public static String decryptNoEC(String password, byte[] encryptedKey) {
- throw new RuntimeException("not implemented yet");
+ public static String decryptNoEC(String password, byte[] encryptedKey) throws UnsupportedEncodingException, GeneralSecurityException{
+
+ byte[] addressHash = Arrays.copyOfRange(encryptedKey, 3, 7);
+ byte[] scryptKey = SCrypt.scrypt(password.getBytes("UTF8"), addressHash, 16384, 8, 8, 64);
+ byte[] derivedHalf1 = Arrays.copyOfRange(scryptKey, 0, 32);
+ byte[] derivedHalf2 = Arrays.copyOfRange(scryptKey, 32, 64);
+
+ Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
+ Key aesKey = new SecretKeySpec(derivedHalf2, "AES");
+ cipher.init(Cipher.DECRYPT_MODE, aesKey);
+ byte[] encryptedHalf1 = Arrays.copyOfRange(encryptedKey, 7, 23);
+ byte[] encryptedHalf2 = Arrays.copyOfRange(encryptedKey, 23, 39);
+ byte[] k1 = cipher.doFinal(encryptedHalf1);
+ byte[] k2 = cipher.doFinal(encryptedHalf2);
+
+ byte[] keyBytes = new byte[32];
+ for (int i = 0; i < 16; i++) {
+ keyBytes[i] = (byte) (k1[i] ^ derivedHalf1[i]);
+ keyBytes[i+16] = (byte) (k2[i] ^ derivedHalf1[i+16]);
+ }
+ boolean compressed = (keyBytes[2] & (byte)0xe0) == 0;
+ ECKey k = new ECKey(new BigInteger(1, keyBytes), null, compressed);
+ return k.getPrivateKeyEncoded(MainNetParams.get()).toString();
}
// generate a key, decrypt it, print the decrypted key and the address.

0 comments on commit 0a9b2db

Please sign in to comment.