diff --git a/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java b/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java index 98ed9d9a..38e83919 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java +++ b/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java @@ -17,6 +17,7 @@ package com.google.bitcoin.crypto; import com.google.bitcoin.core.Sha256Hash; +import com.google.common.base.Joiner; import org.spongycastle.util.encoders.Hex; import java.io.BufferedReader; @@ -25,9 +26,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -92,11 +91,10 @@ public class MnemonicCode { // used as a pseudo-random function. Desired length of the // derived key is 512 bits (= 64 bytes). // - String pass = joinStringList(words); - String salt = new String("mnemonic" + passphrase); + String pass = Joiner.on(' ').join(words); + String salt = "mnemonic" + passphrase; - byte[] hash = PBKDF2SHA512.derive(pass, salt, PBKDF2_ROUNDS, 64); - return hash; + return PBKDF2SHA512.derive(pass, salt, PBKDF2_ROUNDS, 64); } /** @@ -116,7 +114,7 @@ public class MnemonicCode { // Find the words index in the wordlist. int ndx = Collections.binarySearch(this.wordList, word); if (ndx < 0) - throw new MnemonicException.MnemonicWordException("\"" + word + "\" invalid", word); + throw new MnemonicException.MnemonicWordException(word); // Set the next 11 bits to the value of the index. for (int ii = 0; ii < 11; ++ii) @@ -139,9 +137,9 @@ public class MnemonicCode { boolean[] hashBits = bytesToBits(hash); // Check all the checksum bits. - for (int ii = 0; ii < checksumLengthBits; ++ii) - if (concatBits[entropyLengthBits + ii] != hashBits[ii]) - throw new MnemonicException.MnemonicChecksumException("checksum error"); + for (int i = 0; i < checksumLengthBits; ++i) + if (concatBits[entropyLengthBits + i] != hashBits[i]) + throw new MnemonicException.MnemonicChecksumException(); return entropy; } @@ -164,10 +162,8 @@ public class MnemonicCode { // We append these bits to the end of the initial entropy. boolean[] concatBits = new boolean[entropyBits.length + checksumLengthBits]; - for (int ii = 0; ii < entropyBits.length; ++ii) - concatBits[ii] = entropyBits[ii]; - for (int ii = 0; ii < checksumLengthBits; ++ii) - concatBits[entropyBits.length + ii] = hashBits[ii]; + System.arraycopy(entropyBits, 0, concatBits, 0, entropyBits.length); + System.arraycopy(hashBits, 0, concatBits, entropyBits.length, checksumLengthBits); // Next we take these concatenated bits and split them into // groups of 11 bits. Each group encodes number from 0-2047 @@ -176,14 +172,14 @@ public class MnemonicCode { ArrayList words = new ArrayList(); int nwords = concatBits.length / 11; - for (int ii = 0; ii < nwords; ++ii) { - int ndx = 0; - for (int jj = 0; jj < 11; ++jj) { - ndx <<= 1; - if (concatBits[(ii * 11) + jj]) - ndx |= 0x1; + for (int i = 0; i < nwords; ++i) { + int index = 0; + for (int j = 0; j < 11; ++j) { + index <<= 1; + if (concatBits[(i * 11) + j]) + index |= 0x1; } - words.add(this.wordList.get(ndx)); + words.add(this.wordList.get(index)); } return words; @@ -192,29 +188,15 @@ public class MnemonicCode { /** * Check to see if a mnemonic word list is valid. */ - public void check(List words) throws MnemonicException.MnemonicLengthException, MnemonicException.MnemonicWordException, MnemonicException.MnemonicChecksumException { + public void check(List words) throws MnemonicException { toEntropy(words); } private static boolean[] bytesToBits(byte[] data) { boolean[] bits = new boolean[data.length * 8]; - for (int ii = 0; ii < data.length; ++ii) - for (int jj = 0; jj < 8; ++jj) - bits[(ii * 8) + jj] = (data[ii] & (1 << (7 - jj))) != 0; + for (int i = 0; i < data.length; ++i) + for (int j = 0; j < 8; ++j) + bits[(i * 8) + j] = (data[i] & (1 << (7 - j))) != 0; return bits; } - - static private String joinStringList(List list) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (String item : list) - { - if (first) - first = false; - else - sb.append(" "); - sb.append(item); - } - return sb.toString(); - } } diff --git a/core/src/main/java/com/google/bitcoin/crypto/MnemonicException.java b/core/src/main/java/com/google/bitcoin/crypto/MnemonicException.java index 6245696a..61630242 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/MnemonicException.java +++ b/core/src/main/java/com/google/bitcoin/crypto/MnemonicException.java @@ -21,15 +21,13 @@ package com.google.bitcoin.crypto; */ @SuppressWarnings("serial") public class MnemonicException extends Exception { + public MnemonicException() { + super(); + } + public MnemonicException(String msg) { super(msg); } - public MnemonicException(Exception ex) { - super(ex); - } - public MnemonicException(String msg, Exception ex) { - super(msg, ex); - } /** * Thrown when an argument to MnemonicCode is the wrong length. @@ -38,26 +36,14 @@ public class MnemonicException extends Exception { public MnemonicLengthException(String msg) { super(msg); } - public MnemonicLengthException(Exception ex) { - super(ex); - } - public MnemonicLengthException(String msg, Exception ex) { - super(msg, ex); - } } /** * Thrown when a list of MnemonicCode words fails the checksum check. */ public static class MnemonicChecksumException extends MnemonicException { - public MnemonicChecksumException(String msg) { - super(msg); - } - public MnemonicChecksumException(Exception ex) { - super(ex); - } - public MnemonicChecksumException(String msg, Exception ex) { - super(msg, ex); + public MnemonicChecksumException() { + super(); } } @@ -66,22 +52,11 @@ public class MnemonicException extends Exception { */ public static class MnemonicWordException extends MnemonicException { /** Contains the word that was not found in the word list. */ - public String badWord; + public final String badWord; - public MnemonicWordException(String msg, String badWord) { - super(msg); + public MnemonicWordException(String badWord) { + super(); this.badWord = badWord; } - public MnemonicWordException(String badWord, Exception ex) { - super(ex); - this.badWord = badWord; - } - public MnemonicWordException(String msg, String badWord, Exception ex) { - super(msg, ex); - this.badWord = badWord; - } - public String getBadWord() { - return badWord; - } } } diff --git a/core/src/main/java/com/google/bitcoin/crypto/PBKDF2SHA512.java b/core/src/main/java/com/google/bitcoin/crypto/PBKDF2SHA512.java index 02d5dc58..957498ac 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/PBKDF2SHA512.java +++ b/core/src/main/java/com/google/bitcoin/crypto/PBKDF2SHA512.java @@ -19,214 +19,94 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * - * ----------------------------------------------------------------------------- - * - * This is a clean-room implementation of PBKDF2 using RFC 2898 as a reference. - * - * RFC 2898: - * http://tools.ietf.org/html/rfc2898#section-5.2 - * - * This code passes all RFC 6070 test vectors: - * http://tools.ietf.org/html/rfc6070 - * - * The function "nativeDerive()" is supplied as an example of the native Java - * PBKDF2WithHmacSHA1 implementation. It is used for benchmarking and - * comparison only. - * - * The functions "fromHex()" and "toHex()" came from some message board - * somewhere. No license was included. - * - * http://cryptofreek.org/2012/11/29/pbkdf2-pure-java-implementation/ - * Modified to use SHA-512 - Ken Sedgwick ken@bonsai.com */ package com.google.bitcoin.crypto; - + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.spec.KeySpec; -import java.util.Formatter; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -public class PBKDF2SHA512 -{ - /* START RFC 2898 IMPLEMENTATION */ - public static byte[] derive(String P, String S, int c, int dkLen) - { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - try - { - int hLen = 20; - - if (dkLen > ((Math.pow(2, 32)) - 1) * hLen) - { - System.out.println("derived key too long"); - } - else - { - int l = (int) Math.ceil((double) dkLen / (double) hLen); - // int r = dkLen - (l-1)*hLen; - - for (int i = 1; i <= l; i++) - { - byte[] T = F(P, S, c, i); - baos.write(T); + +/** + *

This is a clean-room implementation of PBKDF2 using RFC 2898 as a reference.

+ * + *

RFC 2898: http://tools.ietf.org/html/rfc2898#section-5.2

+ * + *

This code passes all RFC 6070 test vectors: http://tools.ietf.org/html/rfc6070

+ * + *

http://cryptofreek.org/2012/11/29/pbkdf2-pure-java-implementation/
+ * Modified to use SHA-512 - Ken Sedgwick ken@bonsai.com

+ */ +public class PBKDF2SHA512 { + public static byte[] derive(String P, String S, int c, int dkLen) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try { + int hLen = 20; + + if (dkLen > ((Math.pow(2, 32)) - 1) * hLen) { + throw new IllegalArgumentException("derived key too long"); + } else { + int l = (int) Math.ceil((double) dkLen / (double) hLen); + // int r = dkLen - (l-1)*hLen; + + for (int i = 1; i <= l; i++) { + byte[] T = F(P, S, c, i); + baos.write(T); + } + } + } catch (Exception e) { + throw new RuntimeException(e); } - } + + byte[] baDerived = new byte[dkLen]; + System.arraycopy(baos.toByteArray(), 0, baDerived, 0, baDerived.length); + + return baDerived; } - catch (Exception e) - { - e.printStackTrace(); - } - - byte[] baDerived = new byte[dkLen]; - System.arraycopy(baos.toByteArray(), 0, baDerived, 0, baDerived.length); - - return baDerived; - } - - private static byte[] F(String P, String S, int c, int i) throws Exception - { - byte[] U_LAST = null; - byte[] U_XOR = null; - - SecretKeySpec key = new SecretKeySpec(P.getBytes("UTF-8"), "HmacSHA512"); - Mac mac = Mac.getInstance(key.getAlgorithm()); - mac.init(key); - - for (int j = 0; j < c; j++) - { - if (j == 0) - { - byte[] baS = S.getBytes("UTF-8"); - byte[] baI = INT(i); - byte[] baU = new byte[baS.length + baI.length]; - - System.arraycopy(baS, 0, baU, 0, baS.length); - System.arraycopy(baI, 0, baU, baS.length, baI.length); - - U_XOR = mac.doFinal(baU); - U_LAST = U_XOR; - mac.reset(); - } - else - { - byte[] baU = mac.doFinal(U_LAST); - mac.reset(); - - for (int k = 0; k < U_XOR.length; k++) - { - U_XOR[k] = (byte) (U_XOR[k] ^ baU[k]); + + private static byte[] F(String P, String S, int c, int i) throws Exception { + byte[] U_LAST = null; + byte[] U_XOR = null; + + SecretKeySpec key = new SecretKeySpec(P.getBytes("UTF-8"), "HmacSHA512"); + Mac mac = Mac.getInstance(key.getAlgorithm()); + mac.init(key); + + for (int j = 0; j < c; j++) { + if (j == 0) { + byte[] baS = S.getBytes("UTF-8"); + byte[] baI = INT(i); + byte[] baU = new byte[baS.length + baI.length]; + + System.arraycopy(baS, 0, baU, 0, baS.length); + System.arraycopy(baI, 0, baU, baS.length, baI.length); + + U_XOR = mac.doFinal(baU); + U_LAST = U_XOR; + mac.reset(); + } else { + byte[] baU = mac.doFinal(U_LAST); + mac.reset(); + + for (int k = 0; k < U_XOR.length; k++) { + U_XOR[k] = (byte) (U_XOR[k] ^ baU[k]); + } + + U_LAST = baU; + } } - - U_LAST = baU; - } + + return U_XOR; } - - return U_XOR; - } - - private static byte[] INT(int i) - { - ByteBuffer bb = ByteBuffer.allocate(4); - bb.order(ByteOrder.BIG_ENDIAN); - bb.putInt(i); - - return bb.array(); - } - /* END RFC 2898 IMPLEMENTATION */ - - /* START HELPER FUNCTIONS */ - private static String toHex(byte[] ba) - { - String strHex = null; - - if (ba != null) - { - StringBuilder sb = new StringBuilder(ba.length * 2); - Formatter formatter = new Formatter(sb); - - for (byte b : ba) - { - formatter.format("%02x", b); - } - - formatter.close(); - strHex = sb.toString().toLowerCase(); + + private static byte[] INT(int i) { + ByteBuffer bb = ByteBuffer.allocate(4); + bb.order(ByteOrder.BIG_ENDIAN); + bb.putInt(i); + + return bb.array(); } - - return strHex; - } - - private static byte[] nativeDerive(String strPassword, String strSalt, int nIterations, int nKeyLen) - { - byte[] baDerived = null; - - try - { - SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - KeySpec ks = new PBEKeySpec(strPassword.toCharArray(), strSalt.getBytes("UTF-8"), nIterations, nKeyLen * 8); - SecretKey s = f.generateSecret(ks); - baDerived = s.getEncoded(); - } - catch (Exception e) - { - e.printStackTrace(); - } - - return baDerived; - } - /* END HELPER FUNCTIONS */ - - public static void runTestVector(String P, String S, int c, int dkLen, String strExpectedDk) - { - System.out.println("Input:"); - System.out.println(" P = \"" + P + "\""); - System.out.println(" S = \"" + S + "\""); - System.out.println(" c = " + c); - System.out.println(" dkLen = " + dkLen); - System.out.println(); - - long nStartDk = System.nanoTime(); - byte[] DK = derive(P, S, c, dkLen); - long nStopDk = System.nanoTime(); - - long nStartDkNative = System.nanoTime(); - byte[] DK_NATIVE = nativeDerive(P, S, c, dkLen); - long nStopDkNative = System.nanoTime(); - - System.out.println("Output:"); - System.out.println(" DK = " + toHex(DK)); - System.out.println(" DK_NATIVE = " + toHex(DK_NATIVE)); - System.out.println(" DK_EXPECTED = " + strExpectedDk.replaceAll(" ", "")); - System.out.println(); - - System.out.println("Duration [my implementation]: " + (nStopDk - nStartDk) + " ns" ); - System.out.println("Duration [native implementation]: " + (nStopDkNative - nStartDkNative) + " ns" ); - - System.out.println("---------------------------------------------------------------"); - System.out.println(); - } - - public static void RFC6070() - { - runTestVector("password", "salt", 1, 20, "0c 60 c8 0f 96 1f 0e 71 f3 a9 b5 24 af 60 12 06 2f e0 37 a6"); - runTestVector("password", "salt", 2, 20, "ea 6c 01 4d c7 2d 6f 8c cd 1e d9 2a ce 1d 41 f0 d8 de 89 57"); - runTestVector("password", "salt", 4096, 20, "4b 00 79 01 b7 65 48 9a be ad 49 d9 26 f7 21 d0 65 a4 29 c1"); - runTestVector("password", "salt", 16777216, 20, "ee fe 3d 61 cd 4d a4 e4 e9 94 5b 3d 6b a2 15 8c 26 34 e9 84"); - runTestVector("passwordPASSWORDpassword", "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 25, "3d 2e ec 4f e4 1c 84 9b 80 c8 d8 36 62 c0 e4 4a 8b 29 1a 96 4c f2 f0 70 38"); - runTestVector("pass\0word", "sa\0lt", 4096, 16, "56 fa 6a a7 55 48 09 9d cc 37 d7 f0 34 25 e0 c3"); - } - - public static void main(String[] args) - { - RFC6070(); - } }