From 577af21b676ebecdd0f3a396ce8a0ab4a0943935 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Wed, 1 Feb 2017 12:42:43 -0500 Subject: [PATCH 1/3] Using LZMA for bin backup decompression to try to solve the compatibility issue --- .../de/bitsharesmunich/graphenej/FileBin.java | 25 ++++++++++++++++--- .../de/bitsharesmunich/graphenej/Main.java | 8 +++--- .../de/bitsharesmunich/graphenej/Test.java | 4 +-- .../de/bitsharesmunich/graphenej/Util.java | 9 ++++--- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/bitsharesmunich/graphenej/FileBin.java b/src/main/java/de/bitsharesmunich/graphenej/FileBin.java index 281a574..b23c685 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/FileBin.java +++ b/src/main/java/de/bitsharesmunich/graphenej/FileBin.java @@ -4,6 +4,10 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.MessageDigest; @@ -42,13 +46,28 @@ public abstract class FileBin { MessageDigest md1 = MessageDigest.getInstance("SHA-512"); finalKey = md1.digest(finalKey); byte[] rawData = Util.decryptAES(rawDataEncripted, Util.byteToString(finalKey).getBytes()); - + + try { + FileOutputStream out = new FileOutputStream("/Users/nelson/Development/Java/Fullerene/src/main/java/de/bitsharesmunich/graphenej/decrypted.bin"); + out.write(rawData); + out.close(); + } catch (FileNotFoundException e) { + System.out.println("FileNotFoundException. Msg: "+e.getMessage()); + } catch (IOException e) { + System.out.println("IOException. Msg: "+e.getMessage()); + } + + byte[] checksum = new byte[4]; System.arraycopy(rawData, 0, checksum, 0, 4); byte[] compressedData = new byte[rawData.length - 4]; System.arraycopy(rawData, 4, compressedData, 0, compressedData.length); - - byte[] wallet_object_bytes = Util.decompress(compressedData, Util.XZ); + + System.out.println("raw: "+Util.bytesToHex(rawData)); + System.out.println("checksum: "+Util.bytesToHex(checksum)); + System.out.println("compressed: "+Util.bytesToHex(compressedData)); + + byte[] wallet_object_bytes = Util.decompress(rawData, Util.LZMA); String wallet_string = new String(wallet_object_bytes, "UTF-8"); JsonObject wallet = new JsonParser().parse(wallet_string).getAsJsonObject(); if (wallet.get("wallet").isJsonArray()) { diff --git a/src/main/java/de/bitsharesmunich/graphenej/Main.java b/src/main/java/de/bitsharesmunich/graphenej/Main.java index a56442e..bf6074d 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Main.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Main.java @@ -17,6 +17,8 @@ public class Main { public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5"; + public static final String BILTHON_83_PASSWORD = System.getenv("BILTHON_83_PASSWORD"); + // Static block information used for transaction serialization tests public static int REF_BLOCK_NUM = 56204; public static int REF_BLOCK_PREFIX = 1614747814; @@ -59,8 +61,8 @@ public class Main { // test.testAccountUpdateSerialization(); // test.testAccountUpdateOperationBroadcast(); // test.testCreateBinFile(); -// test.testImportBinFile(); -// test.testLookupAccounts(); + test.testImportBinFile(); +// test.testLookupAccounts(); // test.testLookupAccounts(); // test.testDecodeMemo(); // test.testGetRelativeAccountHistory(); @@ -74,6 +76,6 @@ public class Main { // test.testGetMarketHistory(); // test.testGetAccountBalances(); // test.testGetAssetHoldersCount(); - test.testSubscription(null); +// test.testSubscription(null); } } diff --git a/src/main/java/de/bitsharesmunich/graphenej/Test.java b/src/main/java/de/bitsharesmunich/graphenej/Test.java index 237cc25..c771ae7 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Test.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Test.java @@ -632,11 +632,11 @@ public class Test { public void testImportBinFile() { try { String current = new File(".").getCanonicalPath(); - File file = new File(current + "/src/main/java/de/bitsharesmunich/graphenej/bts_bilthon_20161218.bin"); + File file = new File(current + "/src/main/java/de/bitsharesmunich/graphenej/bts_bilthon-83_20170131.bin"); Path path = Paths.get(file.getAbsolutePath()); byte[] data = Files.readAllBytes(path); - System.out.println(FileBin.getBrainkeyFromByte(data, "123456")); + String brainKey = FileBin.getBrainkeyFromByte(data, Main.BILTHON_83_PASSWORD); } catch (IOException e) { System.out.println("IOException while trying to open bin file. Msg: "+e.getMessage()); } diff --git a/src/main/java/de/bitsharesmunich/graphenej/Util.java b/src/main/java/de/bitsharesmunich/graphenej/Util.java index c43b684..5897d23 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Util.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Util.java @@ -140,10 +140,11 @@ public class Util { } catch (IOException ex) { Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex); } finally { - try { - in.close(); - } catch (IOException ex) { - Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex); } +// try { +// in.close(); +// } catch (IOException ex) { +// Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex); +// } } return null; } From 0b0d2516750de972ad10ad62247074b6d635dcac Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Wed, 15 Feb 2017 20:28:44 -0500 Subject: [PATCH 2/3] Adding compatibility with the backup files used by the web wallet --- .../bitsharesmunich/graphenej/BrainKey.java | 6 +- .../de/bitsharesmunich/graphenej/FileBin.java | 118 ++++++++--- .../graphenej/Transaction.java | 5 +- .../de/bitsharesmunich/graphenej/Util.java | 76 +++++-- ...Random.java => SecureRandomGenerator.java} | 2 +- .../models/backup/LinkedAccount.java | 31 +++ .../models/backup/PrivateKeyBackup.java | 38 ++++ .../graphenej/models/backup/Wallet.java | 187 ++++++++++++++++++ .../graphenej/models/backup/WalletBackup.java | 49 +++++ 9 files changed, 464 insertions(+), 48 deletions(-) rename src/main/java/de/bitsharesmunich/graphenej/crypto/{Random.java => SecureRandomGenerator.java} (91%) create mode 100644 src/main/java/de/bitsharesmunich/graphenej/models/backup/LinkedAccount.java create mode 100644 src/main/java/de/bitsharesmunich/graphenej/models/backup/PrivateKeyBackup.java create mode 100644 src/main/java/de/bitsharesmunich/graphenej/models/backup/Wallet.java create mode 100644 src/main/java/de/bitsharesmunich/graphenej/models/backup/WalletBackup.java diff --git a/src/main/java/de/bitsharesmunich/graphenej/BrainKey.java b/src/main/java/de/bitsharesmunich/graphenej/BrainKey.java index c6b1873..39710c4 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/BrainKey.java +++ b/src/main/java/de/bitsharesmunich/graphenej/BrainKey.java @@ -1,8 +1,6 @@ package de.bitsharesmunich.graphenej; -import de.bitsharesmunich.graphenej.crypto.AndroidRandomSource; -import de.bitsharesmunich.graphenej.crypto.Random; -import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener; +import de.bitsharesmunich.graphenej.crypto.SecureRandomGenerator; import org.bitcoinj.core.DumpedPrivateKey; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.NetworkParameters; @@ -44,7 +42,7 @@ public class BrainKey { String[] wordArray = words.split(","); ArrayList suggestedBrainKey = new ArrayList(); assert (wordArray.length == DICT_WORD_COUNT); - SecureRandom secureRandom = Random.getSecureRandom(); + SecureRandom secureRandom = SecureRandomGenerator.getSecureRandom(); int index; for (int i = 0; i < BRAINKEY_WORD_COUNT; i++) { index = secureRandom.nextInt(DICT_WORD_COUNT - 1); diff --git a/src/main/java/de/bitsharesmunich/graphenej/FileBin.java b/src/main/java/de/bitsharesmunich/graphenej/FileBin.java index b23c685..34364f9 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/FileBin.java +++ b/src/main/java/de/bitsharesmunich/graphenej/FileBin.java @@ -1,28 +1,112 @@ package de.bitsharesmunich.graphenej; +import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import de.bitsharesmunich.graphenej.models.backup.WalletBackup; import org.bitcoinj.core.ECKey; /** - * Class to manage the Bin Files + * Class to manage the backup files * * @author Henry Varona + * @author Nelson R. PĂ©rez */ public abstract class FileBin { + public static final int PUBLIC_KEY_LENGTH = 33; + + /** + * Method that receives as input both the bytes from the bin backup and the string used to encrypt the + * data contained in it. + * + * The procedure of deserializing the wallet backup involves first decrypting the data and then decompressing + * it using the LZMA algorithm. Once this two steps are performed, the resulting byte sequence represents + * a JSON-formatted object with one or more wallet and private keys information. + * + * @param input: Input bytes + * @param password: Password used to derive the encryption key + * @return: An instance of the WalletBackup class. + */ + public static WalletBackup deserializeWalletBackup(byte[] input, String password){ + try{ + byte[] publicKey = new byte[PUBLIC_KEY_LENGTH]; + byte[] rawDataEncripted = new byte[input.length - PUBLIC_KEY_LENGTH]; + + System.arraycopy(input, 0, publicKey, 0, PUBLIC_KEY_LENGTH); + System.arraycopy(input, PUBLIC_KEY_LENGTH, rawDataEncripted, 0, rawDataEncripted.length); + + MessageDigest md = MessageDigest.getInstance("SHA-256"); + + ECKey randomECKey = ECKey.fromPublicOnly(publicKey); + byte[] finalKey = randomECKey.getPubKeyPoint().multiply(ECKey.fromPrivate(md.digest(password.getBytes("UTF-8"))).getPrivKey()).normalize().getXCoord().getEncoded(); + MessageDigest md1 = MessageDigest.getInstance("SHA-512"); + finalKey = md1.digest(finalKey); + byte[] decryptedData = Util.decryptAES(rawDataEncripted, Util.bytesToHex(finalKey).getBytes()); + + byte[] checksum = new byte[4]; + System.arraycopy(decryptedData, 0, checksum, 0, 4); + byte[] compressedData = new byte[decryptedData.length - 4]; + System.arraycopy(decryptedData, 4, compressedData, 0, compressedData.length); + + byte[] decompressedData = Util.decompress(compressedData, Util.LZMA); + String walletString = new String(decompressedData, "UTF-8"); + System.out.println("Wallet str: "+walletString); + return new GsonBuilder().create().fromJson(walletString, WalletBackup.class); + }catch(NoSuchAlgorithmException e){ + System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage()); + } catch (UnsupportedEncodingException e) { + System.out.println("UnsupportedEncodingException. Msg: "+e.getMessage()); + } + return null; + } + + public static byte[] serializeWalletBackup(WalletBackup walletBackup, String password){ + SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance(); + //randomStrengthener.addEntropySource(new AndroidRandomSource()); + SecureRandom secureRandom = randomStrengthener.generateAndSeedRandomNumberGenerator(); + + try{ + String json = new GsonBuilder().create().toJson(walletBackup, WalletBackup.class); + byte[] compressed = Util.compress(json.getBytes(), Util.LZMA); + System.out.println("json: "+json); + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] checksum = md.digest(compressed); + byte[] checksummed = new byte[compressed.length + 4]; + + System.arraycopy(checksum, 0, checksummed, 0, 4); + System.arraycopy(compressed, 0, checksummed, 4, compressed.length); + byte[] randomKey = new byte[32]; + secureRandom.nextBytes(randomKey); + ECKey randomECKey = ECKey.fromPrivate(md.digest(randomKey)); + byte[] randPubKey = randomECKey.getPubKey(); + byte[] finalKey = randomECKey.getPubKeyPoint().multiply(ECKey.fromPrivate(md.digest(password.getBytes("UTF-8"))).getPrivKey()).normalize().getXCoord().getEncoded(); + MessageDigest md1 = MessageDigest.getInstance("SHA-512"); + finalKey = md1.digest(finalKey); + checksummed = Util.encryptAES(checksummed, Util.byteToString(finalKey).getBytes()); + + byte[] finalPayload = new byte[checksummed.length + randPubKey.length]; + System.arraycopy(randPubKey, 0, finalPayload, 0, randPubKey.length); + System.arraycopy(checksummed, 0, finalPayload, randPubKey.length, checksummed.length); + + return finalPayload; + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage()); + } catch (UnsupportedEncodingException e) { + System.out.println("UnsupportedEncodingException. Msg: "+e.getMessage()); + } + return null; + } + /** * Method to get the brainkey fron an input of bytes * @@ -30,7 +114,11 @@ public abstract class FileBin { * @param password the pin code * @return the brainkey file, or null if the file or the password are * incorrect + * + * @deprecated use {@link #deserializeWalletBackup(byte[], String)} instead, as it is a more complete method + * that will return a WalletBackup class instance. */ + @Deprecated public static String getBrainkeyFromByte(byte[] input, String password) { try { byte[] publicKey = new byte[33]; @@ -47,27 +135,12 @@ public abstract class FileBin { finalKey = md1.digest(finalKey); byte[] rawData = Util.decryptAES(rawDataEncripted, Util.byteToString(finalKey).getBytes()); - try { - FileOutputStream out = new FileOutputStream("/Users/nelson/Development/Java/Fullerene/src/main/java/de/bitsharesmunich/graphenej/decrypted.bin"); - out.write(rawData); - out.close(); - } catch (FileNotFoundException e) { - System.out.println("FileNotFoundException. Msg: "+e.getMessage()); - } catch (IOException e) { - System.out.println("IOException. Msg: "+e.getMessage()); - } - - byte[] checksum = new byte[4]; System.arraycopy(rawData, 0, checksum, 0, 4); byte[] compressedData = new byte[rawData.length - 4]; System.arraycopy(rawData, 4, compressedData, 0, compressedData.length); - System.out.println("raw: "+Util.bytesToHex(rawData)); - System.out.println("checksum: "+Util.bytesToHex(checksum)); - System.out.println("compressed: "+Util.bytesToHex(compressedData)); - - byte[] wallet_object_bytes = Util.decompress(rawData, Util.LZMA); + byte[] wallet_object_bytes = Util.decompress(compressedData, Util.XZ); String wallet_string = new String(wallet_object_bytes, "UTF-8"); JsonObject wallet = new JsonParser().parse(wallet_string).getAsJsonObject(); if (wallet.get("wallet").isJsonArray()) { @@ -106,7 +179,10 @@ public abstract class FileBin { * @param password The pin code * @param accountName The Account Name * @return The array byte of the file, or null if an error happens + * + * @deprecated use {@link #serializeWalletBackup(WalletBackup, String)} instead. */ + @Deprecated public static byte[] getBytesFromBrainKey(String BrainKey, String password, String accountName) { try { @@ -157,6 +233,4 @@ public abstract class FileBin { } return null; } - - } diff --git a/src/main/java/de/bitsharesmunich/graphenej/Transaction.java b/src/main/java/de/bitsharesmunich/graphenej/Transaction.java index e7d64cf..a4c0068 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Transaction.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Transaction.java @@ -33,7 +33,6 @@ public class Transaction implements ByteSerializable, JsonSerializable { public static final String KEY_EXTENSIONS = "extensions"; public static final String KEY_REF_BLOCK_NUM = "ref_block_num"; public static final String KEY_REF_BLOCK_PREFIX = "ref_block_prefix"; - public static final String TIME_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; private ECKey privateKey; private BlockData blockData; @@ -205,7 +204,7 @@ public class Transaction implements ByteSerializable, JsonSerializable { // Formatting expiration time Date expirationTime = new Date(blockData.getExpiration() * 1000); - SimpleDateFormat dateFormat = new SimpleDateFormat(TIME_DATE_FORMAT); + SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); // Adding expiration @@ -260,7 +259,7 @@ public class Transaction implements ByteSerializable, JsonSerializable { int refBlockNum = jsonObject.get(KEY_REF_BLOCK_NUM).getAsInt(); long refBlockPrefix = jsonObject.get(KEY_REF_BLOCK_PREFIX).getAsLong(); String expiration = jsonObject.get(KEY_EXPIRATION).getAsString(); - SimpleDateFormat dateFormat = new SimpleDateFormat(TIME_DATE_FORMAT); + SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); Date expirationDate = dateFormat.parse(expiration, new ParsePosition(0)); BlockData blockData = new BlockData(refBlockNum, refBlockPrefix, expirationDate.getTime()); diff --git a/src/main/java/de/bitsharesmunich/graphenej/Util.java b/src/main/java/de/bitsharesmunich/graphenej/Util.java index 5897d23..98fd493 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Util.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Util.java @@ -1,11 +1,8 @@ package de.bitsharesmunich.graphenej; -import org.tukaani.xz.FinishableOutputStream; -import org.tukaani.xz.LZMA2Options; -import org.tukaani.xz.LZMAInputStream; -import org.tukaani.xz.LZMAOutputStream; -import org.tukaani.xz.XZInputStream; -import org.tukaani.xz.XZOutputStream; +import com.google.common.primitives.Bytes; +import org.tukaani.xz.*; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -33,6 +30,17 @@ public class Util { public static final int LZMA = 0; public static final int XZ = 1; + /** + * AES encryption key length in bytes + */ + public static final int KEY_LENGTH = 32; + + /** + * Time format used across the platform + */ + public static final String TIME_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; + + /** * Converts an hexadecimal string to its corresponding byte[] value. * @param s: String with hexadecimal numbers representing a byte array. @@ -94,10 +102,10 @@ public class Util { }else if(which == Util.XZ){ out = new XZOutputStream(output, options); } - byte[] buf = new byte[inputBytes.length]; + byte[] inputBuffer = new byte[inputBytes.length]; int size; - while ((size = input.read(buf)) != -1) { - out.write(buf, 0, size); + while ((size = input.read(inputBuffer)) != -1) { + out.write(inputBuffer, 0, size); } out.finish(); return output.toByteArray(); @@ -124,27 +132,51 @@ public class Util { public static byte[] decompress(byte[] inputBytes, int which) { InputStream in = null; try { + System.out.println("Bytes: "+Util.bytesToHex(inputBytes)); ByteArrayInputStream input = new ByteArrayInputStream(inputBytes); - ByteArrayOutputStream output = new ByteArrayOutputStream(2048); + ByteArrayOutputStream output = new ByteArrayOutputStream(16*2048); if(which == XZ) { in = new XZInputStream(input); }else if(which == LZMA){ in = new LZMAInputStream(input); } int size; - while ((size = in.read()) != -1) { - output.write(size); + try{ + while ((size = in.read()) != -1) { + output.write(size); + } + }catch(CorruptedInputException e){ + // Taking property byte + byte[] properties = Arrays.copyOfRange(inputBytes, 0, 1); + // Taking dict size bytes + byte[] dictSize = Arrays.copyOfRange(inputBytes, 1, 5); + // Taking uncompressed size bytes + byte[] uncompressedSize = Arrays.copyOfRange(inputBytes, 5, 13); + + // Reversing bytes in header + byte[] header = Bytes.concat(properties, Util.revertBytes(dictSize), Util.revertBytes(uncompressedSize)); + byte[] payload = Arrays.copyOfRange(inputBytes, 13, inputBytes.length); + + // Trying again + input = new ByteArrayInputStream(Bytes.concat(header, payload)); + output = new ByteArrayOutputStream(2048); + if(which == XZ) { + in = new XZInputStream(input); + }else if(which == LZMA){ + in = new LZMAInputStream(input); + } + try{ + while ((size = in.read()) != -1) { + output.write(size); + } + }catch(CorruptedInputException ex){ + System.out.println("CorruptedInputException. Msg: "+ex.getMessage()); + } } in.close(); return output.toByteArray(); } catch (IOException ex) { Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex); - } finally { -// try { -// in.close(); -// } catch (IOException ex) { -// Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex); -// } } return null; } @@ -178,6 +210,14 @@ public class Util { return ByteBuffer.allocate(Long.SIZE / 8).putLong(Long.reverseBytes(input)).array(); } + public static byte[] revertBytes(byte[] array){ + byte[] reverted = new byte[array.length]; + for(int i = 0; i < reverted.length; i++){ + reverted[i] = array[array.length - i - 1]; + } + return reverted; + } + /** * Function to encrypt a message with AES * @param input data to encrypt diff --git a/src/main/java/de/bitsharesmunich/graphenej/crypto/Random.java b/src/main/java/de/bitsharesmunich/graphenej/crypto/SecureRandomGenerator.java similarity index 91% rename from src/main/java/de/bitsharesmunich/graphenej/crypto/Random.java rename to src/main/java/de/bitsharesmunich/graphenej/crypto/SecureRandomGenerator.java index 33dacc2..5c7a3fa 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/crypto/Random.java +++ b/src/main/java/de/bitsharesmunich/graphenej/crypto/SecureRandomGenerator.java @@ -5,7 +5,7 @@ import java.security.SecureRandom; /** * Created by nelson on 12/20/16. */ -public class Random { +public class SecureRandomGenerator { public static SecureRandom getSecureRandom(){ SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance(); diff --git a/src/main/java/de/bitsharesmunich/graphenej/models/backup/LinkedAccount.java b/src/main/java/de/bitsharesmunich/graphenej/models/backup/LinkedAccount.java new file mode 100644 index 0000000..ff29243 --- /dev/null +++ b/src/main/java/de/bitsharesmunich/graphenej/models/backup/LinkedAccount.java @@ -0,0 +1,31 @@ +package de.bitsharesmunich.graphenej.models.backup; + +/** + * Class used to represent an entry in the "linked_accounts" field of the JSON-formatted backup file. + * Created by nelson on 2/15/17. + */ +public class LinkedAccount { + private String name; + private String chainId; + + public LinkedAccount(String name, String chainId){ + this.name = name; + this.chainId = chainId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getChainId() { + return chainId; + } + + public void setChainId(String chainId) { + this.chainId = chainId; + } +} diff --git a/src/main/java/de/bitsharesmunich/graphenej/models/backup/PrivateKeyBackup.java b/src/main/java/de/bitsharesmunich/graphenej/models/backup/PrivateKeyBackup.java new file mode 100644 index 0000000..68d2f8b --- /dev/null +++ b/src/main/java/de/bitsharesmunich/graphenej/models/backup/PrivateKeyBackup.java @@ -0,0 +1,38 @@ +package de.bitsharesmunich.graphenej.models.backup; + +import de.bitsharesmunich.graphenej.Address; +import de.bitsharesmunich.graphenej.Util; +import org.bitcoinj.core.ECKey; + +/** + * Class used to represent an entry in the "private_keys" array field in the JSON-formatted + * backup file. + * + * Created by nelson on 2/14/17. + */ +public class PrivateKeyBackup { + public String encrypted_key; + public String pubkey; + public int brainkey_sequence; + public int id; + + public PrivateKeyBackup(byte[] privateKey, int brainkeySequence, int id, byte[] encryptionKey){ + this.encrypted_key = encryptPrivateKey(privateKey, encryptionKey); + this.brainkey_sequence = brainkeySequence; + this.id = id; + deriveAddress(privateKey); + } + + public byte[] decryptPrivateKey(byte[] encryptionKey){ + return Util.decryptAES(Util.hexToBytes(encrypted_key), encryptionKey); + } + + public String encryptPrivateKey(byte[] data, byte[] encryptionKey){ + return Util.bytesToHex(Util.encryptAES(data, encryptionKey)); + } + + private void deriveAddress(byte[] privateKey){ + Address address = new Address(ECKey.fromPublicOnly(ECKey.fromPrivate(privateKey).getPubKey())); + this.pubkey = address.toString(); + } +} diff --git a/src/main/java/de/bitsharesmunich/graphenej/models/backup/Wallet.java b/src/main/java/de/bitsharesmunich/graphenej/models/backup/Wallet.java new file mode 100644 index 0000000..528af52 --- /dev/null +++ b/src/main/java/de/bitsharesmunich/graphenej/models/backup/Wallet.java @@ -0,0 +1,187 @@ +package de.bitsharesmunich.graphenej.models.backup; + +import de.bitsharesmunich.graphenej.Chains; +import de.bitsharesmunich.graphenej.Util; +import de.bitsharesmunich.graphenej.crypto.SecureRandomGenerator; + +import java.security.SecureRandom; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * This class holds data deserialized from a wallet in the backup file. + * + * Created by nelson on 2/14/17. + */ +public class Wallet { + private String public_name; + private String password_pubkey; + private String encryption_key; + private String encrypted_brainkey; + private String brainkey_pubkey; + private int brainkey_sequence; + private String brainkey_backup_date; + private String created; + private String last_modified; + private String chain_id; + private String id; + private String backup_date; + + /** + * No args constructor + */ + public Wallet(){} + + public Wallet(String name){ + this.public_name = name; + this.id = name; + } + + /** + * Wallet constructor that takes a few arguments. + * @param name: The name of this wallet. + * @param brainKey: The brain key to be used. + * @param brainkeySequence: The brain key sequence. + * @param chainId: The chain id + * @param password: Password used to encrypt all sensitive data. + */ + public Wallet(String name, String brainKey, int brainkeySequence, String chainId, String password){ + this(name); + SecureRandom secureRandom = SecureRandomGenerator.getSecureRandom(); + byte[] decryptedKey = new byte[Util.KEY_LENGTH]; + secureRandom.nextBytes(decryptedKey); + this.encryption_key = Util.bytesToHex(Util.encryptAES(decryptedKey, password.getBytes())); + this.encrypted_brainkey = Util.bytesToHex(Util.encryptAES(decryptedKey, brainKey.getBytes())); + this.brainkey_sequence = brainkeySequence; + this.chain_id = chainId; + + //TODO: Find out how to fill "password_pubkey" and "brainkey_pubkey" fields. + + Date now = new Date(); + SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT); + this.created = dateFormat.format(now); + this.last_modified = created; + this.backup_date = created; + this.brainkey_backup_date = created; + } + + /** + * Method that will return the decrypted version of the "encrypted_brainkey" field. + * @param password: Password used to encrypt the encryption key. + * @return: The brainkey in its plaintext version. + */ + public String decryptBrainKey(String password){ + byte[] decryptedKey = getEncryptionKey(password); + byte[] encryptedBrainKey = Util.hexToBytes(encrypted_brainkey); + return new String(Util.decryptAES(encryptedBrainKey, decryptedKey)); + } + + /** + * Decrypts the encryption key, which is also provided in an encrypted form. + * @param password: The password used to encrypt the encryption key. + * @return: The encryption key. + */ + public byte[] getEncryptionKey(String password){ + return Util.decryptAES(Util.hexToBytes(encryption_key), password.getBytes()); + } + + public String getPrivateName() { + return public_name; + } + + public void setPrivateName(String privateName) { + this.public_name = privateName; + } + + public String getPasswordPubkey() { + return password_pubkey; + } + + public void setPasswordPubkey(String password_pubkey) { + this.password_pubkey = password_pubkey; + } + + /** + * Gets the cyphertext version of the encryption key. + * @return: Encryption key in its cyphertext version. + */ + public String getEncryptionKey() { + return encryption_key; + } + + public void setEncryptionKey(String encryption_key) { + this.encryption_key = encryption_key; + } + + public String getEncryptedBrainkey() { + return encrypted_brainkey; + } + + public void setEncryptedBrainkey(String encrypted_brainkey) { + this.encrypted_brainkey = encrypted_brainkey; + } + + public String getBrainkeyPubkey() { + return brainkey_pubkey; + } + + public void setBrainkeyPubkey(String brainkey_pubkey) { + this.brainkey_pubkey = brainkey_pubkey; + } + + public int getBrainkeySequence() { + return brainkey_sequence; + } + + public void setBrainkeySequence(int brainkey_sequence) { + this.brainkey_sequence = brainkey_sequence; + } + + public String getBrainkeyBackup_date() { + return brainkey_backup_date; + } + + public void setBrainkeyBackupDate(String brainkey_backup_date) { + this.brainkey_backup_date = brainkey_backup_date; + } + + public String getCreated() { + return created; + } + + public void setCreated(String created) { + this.created = created; + } + + public String getLastModified() { + return last_modified; + } + + public void setLastModified(String last_modified) { + this.last_modified = last_modified; + } + + public String getChainId() { + return chain_id; + } + + public void setChainId(String chain_id) { + this.chain_id = chain_id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBackupDate() { + return backup_date; + } + + public void setBackupDate(String backup_date) { + this.backup_date = backup_date; + } +} diff --git a/src/main/java/de/bitsharesmunich/graphenej/models/backup/WalletBackup.java b/src/main/java/de/bitsharesmunich/graphenej/models/backup/WalletBackup.java new file mode 100644 index 0000000..536c261 --- /dev/null +++ b/src/main/java/de/bitsharesmunich/graphenej/models/backup/WalletBackup.java @@ -0,0 +1,49 @@ +package de.bitsharesmunich.graphenej.models.backup; + +import java.util.List; + +/** + * This class is used to represent the JSON-formatted version of the file backup containing one or more + * wallets and keys. + * + * Created by nelson on 2/14/17. + */ +public class WalletBackup { + private Wallet[] wallet; + private PrivateKeyBackup[] private_keys; + private LinkedAccount[] linked_accounts; + + public WalletBackup(List wallets, List privateKeys, List linkedAccounts){ + this.wallet = wallets.toArray(new Wallet[wallets.size()]); + this.private_keys = privateKeys.toArray(new PrivateKeyBackup[privateKeys.size()]); + this.linked_accounts = linkedAccounts.toArray(new LinkedAccount[linkedAccounts.size()]); + } + + public Wallet[] getWallets(){ + return wallet; + } + + public PrivateKeyBackup[] getPrivateKeys(){ + return private_keys; + } + + public LinkedAccount[] getLinkedAccounts(){ + return linked_accounts; + } + + public Wallet getWallet(int index){ + return wallet[index]; + } + + public PrivateKeyBackup getPrivateKeyBackup(int index){ + return private_keys[index]; + } + + public int getWalletCount(){ + return wallet.length; + } + + public int getKeyCount(){ + return private_keys.length; + } +} From 5ec7fec23f15225daeb55c084be88958bf452069 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Wed, 15 Feb 2017 20:39:05 -0500 Subject: [PATCH 3/3] Adding some test files --- .../de/bitsharesmunich/graphenej/Main.java | 10 +- .../de/bitsharesmunich/graphenej/Test.java | 140 +++++++++++++++++- 2 files changed, 147 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/bitsharesmunich/graphenej/Main.java b/src/main/java/de/bitsharesmunich/graphenej/Main.java index bf6074d..14e0ed8 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Main.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Main.java @@ -19,6 +19,10 @@ public class Main { public static final String BILTHON_83_PASSWORD = System.getenv("BILTHON_83_PASSWORD"); + public static final String BILTHON_25_PASSWORD = System.getenv("BILTHON_25_PASSWORD"); + + public static final String BILTHON_11_BRAIN_KEY = System.getenv("BILTHON_11_BRAIN_KEY"); + // Static block information used for transaction serialization tests public static int REF_BLOCK_NUM = 56204; public static int REF_BLOCK_PREFIX = 1614747814; @@ -61,7 +65,11 @@ public class Main { // test.testAccountUpdateSerialization(); // test.testAccountUpdateOperationBroadcast(); // test.testCreateBinFile(); - test.testImportBinFile(); +// test.testImportBinFile(); + test.testExportBinFile(); +// test.testLzmaCompression(); +// test.testLzmaDecompression(); +// test.testSimpleDecompression(); // test.testLookupAccounts(); // test.testLookupAccounts(); // test.testDecodeMemo(); diff --git a/src/main/java/de/bitsharesmunich/graphenej/Test.java b/src/main/java/de/bitsharesmunich/graphenej/Test.java index c771ae7..8322619 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Test.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Test.java @@ -1,7 +1,12 @@ package de.bitsharesmunich.graphenej; +import com.google.common.primitives.Bytes; import de.bitsharesmunich.graphenej.interfaces.SubscriptionListener; import de.bitsharesmunich.graphenej.models.*; +import de.bitsharesmunich.graphenej.models.backup.LinkedAccount; +import de.bitsharesmunich.graphenej.models.backup.PrivateKeyBackup; +import de.bitsharesmunich.graphenej.models.backup.Wallet; +import de.bitsharesmunich.graphenej.models.backup.WalletBackup; import de.bitsharesmunich.graphenej.objects.Memo; import com.google.common.primitives.UnsignedLong; import com.google.gson.Gson; @@ -16,6 +21,7 @@ import com.neovisionaries.ws.client.*; import de.bitsharesmunich.graphenej.api.*; import org.bitcoinj.core.*; import org.spongycastle.crypto.digests.RIPEMD160Digest; +import org.tukaani.xz.*; import javax.net.ssl.SSLContext; import java.io.*; @@ -23,6 +29,7 @@ import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.logging.Level; @@ -632,13 +639,142 @@ public class Test { public void testImportBinFile() { try { String current = new File(".").getCanonicalPath(); - File file = new File(current + "/src/main/java/de/bitsharesmunich/graphenej/bts_bilthon-83_20170131.bin"); + File file = new File(current + "/src/main/java/de/bitsharesmunich/graphenej/bts_bilthon-25_20170214.bin"); Path path = Paths.get(file.getAbsolutePath()); byte[] data = Files.readAllBytes(path); + byte[] publicKey = new byte[FileBin.PUBLIC_KEY_LENGTH]; + System.arraycopy(data, 0, publicKey, 0, FileBin.PUBLIC_KEY_LENGTH); - String brainKey = FileBin.getBrainkeyFromByte(data, Main.BILTHON_83_PASSWORD); + MessageDigest md = MessageDigest.getInstance("SHA-256"); + ECKey randomECKey = ECKey.fromPublicOnly(publicKey); + byte[] finalKey = randomECKey.getPubKeyPoint().multiply(ECKey.fromPrivate(md.digest(Main.BILTHON_25_PASSWORD.getBytes("UTF-8"))).getPrivKey()).normalize().getXCoord().getEncoded(); + + WalletBackup walletBackup = FileBin.deserializeWalletBackup(data, Main.BILTHON_25_PASSWORD); + System.out.println("Number of wallets: "+walletBackup.getWalletCount()); + String brainKeyString = walletBackup.getWallet(0).decryptBrainKey(Main.BILTHON_25_PASSWORD); + System.out.println("Brain key: "+brainKeyString); + BrainKey brainKey = new BrainKey(brainKeyString, 1); + byte[] privateKey = brainKey.getPrivateKey().getPrivKeyBytes(); + System.out.println("Brainkey derived private....: " + Util.bytesToHex(privateKey)); + + byte[] privateKey2 = walletBackup.getPrivateKeyBackup(0).decryptPrivateKey(walletBackup.getWallet(0).getEncryptionKey(Main.BILTHON_25_PASSWORD)); + System.out.println("Encrypted private key.......: "+Util.bytesToHex(privateKey2)); + + Address addr1 = new Address(ECKey.fromPublicOnly(ECKey.fromPrivate(privateKey).getPubKey())); + Address addr2 = new Address(ECKey.fromPublicOnly(ECKey.fromPrivate(privateKey2).getPubKey())); + Address addr3 = new Address(ECKey.fromPublicOnly(publicKey)); + System.out.println("Addr1: "+addr1.toString()); + System.out.println("Addr2: "+addr2.toString()); + System.out.println("Addr3: "+addr3.toString()); } catch (IOException e) { System.out.println("IOException while trying to open bin file. Msg: "+e.getMessage()); + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgorithmException while trying to open bin file. Msg: "+e.getMessage()); + } + } + + public void testExportBinFile(){ + String password = "123456"; + BrainKey brainKey = new BrainKey(Main.BILTHON_11_BRAIN_KEY, 0); + Wallet wallet = new Wallet("bilthon-11", brainKey.getBrainKey(), brainKey.getSequenceNumber(), Chains.BITSHARES.CHAIN_ID, password); + byte[] privateKey = brainKey.getPrivateKey().getPrivKeyBytes(); + PrivateKeyBackup privateKeyBackup = new PrivateKeyBackup(privateKey, brainKey.getSequenceNumber(), 1, wallet.getEncryptionKey(password)); + LinkedAccount linkedAccount = new LinkedAccount("bilthon-11", Chains.BITSHARES.CHAIN_ID); + + ArrayList walletList = new ArrayList<>(); + walletList.add(wallet); + ArrayList keyList = new ArrayList<>(); + keyList.add(privateKeyBackup); + ArrayList linkedAccounts = new ArrayList<>(); + linkedAccounts.add(linkedAccount); + WalletBackup backup = new WalletBackup(walletList, keyList, linkedAccounts); + byte[] serialized = FileBin.serializeWalletBackup(backup, password); + System.out.println("Serialized: "+Util.bytesToHex(serialized)); + try { + String current = new File(".").getCanonicalPath(); + String fullPath = current + "/scwall_bithon_11.bin"; + System.out.println("Full path: "+fullPath); + File file = new File(fullPath); + FileOutputStream out = new FileOutputStream(file); + out.write(serialized); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void testLzmaCompression(){ + String data = "A long time ago in a galaxy far, far away..."; + byte[] compressed = Util.compress(data.getBytes(), Util.LZMA); + + try { + String current = new File(".").getCanonicalPath(); + File file = new File(current + "/src/main/java/de/bitsharesmunich/graphenej/java_compressed_1.4.lzma"); + FileOutputStream out = new FileOutputStream(file); + System.out.println("Writing "+compressed.length+" bytes"); + out.write(compressed); + out.close(); + }catch(IOException e){ + System.out.println("IOException. Msg: "+e.getMessage()); + } + } + + public void testSimpleDecompression(){ + try{ + String current = new File(".").getCanonicalPath(); +// File file = new File(current + "/src/main/java/de/bitsharesmunich/graphenej/node_compressed_1.2.lzma"); + File file = new File(current + "/src/main/java/de/bitsharesmunich/graphenej/decrypted.bin"); + Path path = Paths.get(file.getAbsolutePath()); + byte[] data = Files.readAllBytes(path); + byte[] decompressed = Util.decompress(data, Util.LZMA); + System.out.println("Decompressed.......: "+Util.bytesToHex(decompressed)); + String message = new String(decompressed); + System.out.println("Decompressed msg...: "+message); + } catch (IOException e) { + System.out.println("IOException. Msg: "+e.getMessage()); + } + } + + public void testLzmaDecompression(){ + try { + String current = new File(".").getCanonicalPath(); + File file = new File(current + "/src/main/java/de/bitsharesmunich/graphenej/java_compressed_1.4.lzma"); + Path path = Paths.get(file.getAbsolutePath()); + byte[] data = Files.readAllBytes(path); + System.out.println("Compressed bytes...: " + Util.bytesToHex(data)); + + InputStream in = null; + byte[] decompressed; + byte[] properties = Arrays.copyOfRange(data, 0, 1); + byte[] dictSize = Arrays.copyOfRange(data, 1, 5); + byte[] uncompressedSize = Arrays.copyOfRange(data, 5, 13); + byte[] header = Bytes.concat(properties, Util.revertBytes(dictSize), Util.revertBytes(uncompressedSize)); + byte[] payload = Arrays.copyOfRange(data, 13, data.length); + System.out.println("Header.............: "+Util.bytesToHex(header)); + System.out.println("Payload............: "+Util.bytesToHex(payload)); + ByteArrayInputStream input = new ByteArrayInputStream(Bytes.concat(header, payload)); + ByteArrayOutputStream output = new ByteArrayOutputStream(2 * 2048); +// in = new LZMAInputStream(input, 44, (byte) 0x5d, 65536); + in = new LZMAInputStream(input); + int size; + try{ + while ((size = in.read()) != -1) { + output.write(size); + } + }catch(IOException e){ + System.out.println("IOException detected. End of stream reached. Msg: "+e.getMessage()); + } + in.close(); + decompressed = output.toByteArray(); + + System.out.println("Decompressed bytes.: " + Util.bytesToHex(decompressed)); + String decompressedString = new String(decompressed); + System.out.println("Decompressed: " + decompressedString); + } catch (CorruptedInputException e) { + System.out.println("CorruptedInputException. Msg: " + e.getMessage()); + } catch (UnsupportedOptionsException e){ + System.out.println("UnsupportedOptionsException. Msg: "+e.getMessage()); + } catch(IOException e){ + System.out.println("IOException. Msg: "+e.getMessage()); } }