Merge branch 'feat_bin_backup_compatibility'

This commit is contained in:
Nelson R. Perez 2017-02-15 20:39:22 -05:00
commit 1cc56ef846
11 changed files with 616 additions and 34 deletions

View file

@ -1,8 +1,6 @@
package de.bitsharesmunich.graphenej; package de.bitsharesmunich.graphenej;
import de.bitsharesmunich.graphenej.crypto.AndroidRandomSource; import de.bitsharesmunich.graphenej.crypto.SecureRandomGenerator;
import de.bitsharesmunich.graphenej.crypto.Random;
import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener;
import org.bitcoinj.core.DumpedPrivateKey; import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
@ -44,7 +42,7 @@ public class BrainKey {
String[] wordArray = words.split(","); String[] wordArray = words.split(",");
ArrayList<String> suggestedBrainKey = new ArrayList<String>(); ArrayList<String> suggestedBrainKey = new ArrayList<String>();
assert (wordArray.length == DICT_WORD_COUNT); assert (wordArray.length == DICT_WORD_COUNT);
SecureRandom secureRandom = Random.getSecureRandom(); SecureRandom secureRandom = SecureRandomGenerator.getSecureRandom();
int index; int index;
for (int i = 0; i < BRAINKEY_WORD_COUNT; i++) { for (int i = 0; i < BRAINKEY_WORD_COUNT; i++) {
index = secureRandom.nextInt(DICT_WORD_COUNT - 1); index = secureRandom.nextInt(DICT_WORD_COUNT - 1);

View file

@ -1,24 +1,112 @@
package de.bitsharesmunich.graphenej; package de.bitsharesmunich.graphenej;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener; import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import de.bitsharesmunich.graphenej.models.backup.WalletBackup;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
/** /**
* Class to manage the Bin Files * Class to manage the backup files
* *
* @author Henry Varona * @author Henry Varona
* @author Nelson R. Pérez
*/ */
public abstract class FileBin { 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 * Method to get the brainkey fron an input of bytes
* *
@ -26,7 +114,11 @@ public abstract class FileBin {
* @param password the pin code * @param password the pin code
* @return the brainkey file, or null if the file or the password are * @return the brainkey file, or null if the file or the password are
* incorrect * 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) { public static String getBrainkeyFromByte(byte[] input, String password) {
try { try {
byte[] publicKey = new byte[33]; byte[] publicKey = new byte[33];
@ -87,7 +179,10 @@ public abstract class FileBin {
* @param password The pin code * @param password The pin code
* @param accountName The Account Name * @param accountName The Account Name
* @return The array byte of the file, or null if an error happens * @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) { public static byte[] getBytesFromBrainKey(String BrainKey, String password, String accountName) {
try { try {
@ -138,6 +233,4 @@ public abstract class FileBin {
} }
return null; return null;
} }
} }

View file

@ -17,6 +17,12 @@ public class Main {
public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5"; public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5";
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 // Static block information used for transaction serialization tests
public static int REF_BLOCK_NUM = 56204; public static int REF_BLOCK_NUM = 56204;
public static int REF_BLOCK_PREFIX = 1614747814; public static int REF_BLOCK_PREFIX = 1614747814;
@ -60,7 +66,11 @@ public class Main {
// test.testAccountUpdateOperationBroadcast(); // test.testAccountUpdateOperationBroadcast();
// test.testCreateBinFile(); // test.testCreateBinFile();
// test.testImportBinFile(); // test.testImportBinFile();
// test.testLookupAccounts(); test.testExportBinFile();
// test.testLzmaCompression();
// test.testLzmaDecompression();
// test.testSimpleDecompression();
// test.testLookupAccounts();
// test.testLookupAccounts(); // test.testLookupAccounts();
// test.testDecodeMemo(); // test.testDecodeMemo();
// test.testGetRelativeAccountHistory(); // test.testGetRelativeAccountHistory();
@ -74,6 +84,6 @@ public class Main {
// test.testGetMarketHistory(); // test.testGetMarketHistory();
// test.testGetAccountBalances(); // test.testGetAccountBalances();
// test.testGetAssetHoldersCount(); // test.testGetAssetHoldersCount();
test.testSubscription(null); // test.testSubscription(null);
} }
} }

View file

@ -1,7 +1,12 @@
package de.bitsharesmunich.graphenej; package de.bitsharesmunich.graphenej;
import com.google.common.primitives.Bytes;
import de.bitsharesmunich.graphenej.interfaces.SubscriptionListener; import de.bitsharesmunich.graphenej.interfaces.SubscriptionListener;
import de.bitsharesmunich.graphenej.models.*; 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 de.bitsharesmunich.graphenej.objects.Memo;
import com.google.common.primitives.UnsignedLong; import com.google.common.primitives.UnsignedLong;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -16,6 +21,7 @@ import com.neovisionaries.ws.client.*;
import de.bitsharesmunich.graphenej.api.*; import de.bitsharesmunich.graphenej.api.*;
import org.bitcoinj.core.*; import org.bitcoinj.core.*;
import org.spongycastle.crypto.digests.RIPEMD160Digest; import org.spongycastle.crypto.digests.RIPEMD160Digest;
import org.tukaani.xz.*;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import java.io.*; import java.io.*;
@ -23,6 +29,7 @@ import java.lang.reflect.Type;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.*; import java.util.*;
import java.util.logging.Level; import java.util.logging.Level;
@ -632,13 +639,142 @@ public class Test {
public void testImportBinFile() { public void testImportBinFile() {
try { try {
String current = new File(".").getCanonicalPath(); 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-25_20170214.bin");
Path path = Paths.get(file.getAbsolutePath()); Path path = Paths.get(file.getAbsolutePath());
byte[] data = Files.readAllBytes(path); byte[] data = Files.readAllBytes(path);
byte[] publicKey = new byte[FileBin.PUBLIC_KEY_LENGTH];
System.arraycopy(data, 0, publicKey, 0, FileBin.PUBLIC_KEY_LENGTH);
System.out.println(FileBin.getBrainkeyFromByte(data, "123456")); 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) { } catch (IOException e) {
System.out.println("IOException while trying to open bin file. Msg: "+e.getMessage()); 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<Wallet> walletList = new ArrayList<>();
walletList.add(wallet);
ArrayList<PrivateKeyBackup> keyList = new ArrayList<>();
keyList.add(privateKeyBackup);
ArrayList<LinkedAccount> 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());
} }
} }

View file

@ -33,7 +33,6 @@ public class Transaction implements ByteSerializable, JsonSerializable {
public static final String KEY_EXTENSIONS = "extensions"; 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_NUM = "ref_block_num";
public static final String KEY_REF_BLOCK_PREFIX = "ref_block_prefix"; 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 ECKey privateKey;
private BlockData blockData; private BlockData blockData;
@ -205,7 +204,7 @@ public class Transaction implements ByteSerializable, JsonSerializable {
// Formatting expiration time // Formatting expiration time
Date expirationTime = new Date(blockData.getExpiration() * 1000); 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")); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
// Adding expiration // Adding expiration
@ -260,7 +259,7 @@ public class Transaction implements ByteSerializable, JsonSerializable {
int refBlockNum = jsonObject.get(KEY_REF_BLOCK_NUM).getAsInt(); int refBlockNum = jsonObject.get(KEY_REF_BLOCK_NUM).getAsInt();
long refBlockPrefix = jsonObject.get(KEY_REF_BLOCK_PREFIX).getAsLong(); long refBlockPrefix = jsonObject.get(KEY_REF_BLOCK_PREFIX).getAsLong();
String expiration = jsonObject.get(KEY_EXPIRATION).getAsString(); 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")); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
Date expirationDate = dateFormat.parse(expiration, new ParsePosition(0)); Date expirationDate = dateFormat.parse(expiration, new ParsePosition(0));
BlockData blockData = new BlockData(refBlockNum, refBlockPrefix, expirationDate.getTime()); BlockData blockData = new BlockData(refBlockNum, refBlockPrefix, expirationDate.getTime());

View file

@ -1,11 +1,8 @@
package de.bitsharesmunich.graphenej; package de.bitsharesmunich.graphenej;
import org.tukaani.xz.FinishableOutputStream; import com.google.common.primitives.Bytes;
import org.tukaani.xz.LZMA2Options; import org.tukaani.xz.*;
import org.tukaani.xz.LZMAInputStream;
import org.tukaani.xz.LZMAOutputStream;
import org.tukaani.xz.XZInputStream;
import org.tukaani.xz.XZOutputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@ -33,6 +30,17 @@ public class Util {
public static final int LZMA = 0; public static final int LZMA = 0;
public static final int XZ = 1; 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. * Converts an hexadecimal string to its corresponding byte[] value.
* @param s: String with hexadecimal numbers representing a byte array. * @param s: String with hexadecimal numbers representing a byte array.
@ -94,10 +102,10 @@ public class Util {
}else if(which == Util.XZ){ }else if(which == Util.XZ){
out = new XZOutputStream(output, options); out = new XZOutputStream(output, options);
} }
byte[] buf = new byte[inputBytes.length]; byte[] inputBuffer = new byte[inputBytes.length];
int size; int size;
while ((size = input.read(buf)) != -1) { while ((size = input.read(inputBuffer)) != -1) {
out.write(buf, 0, size); out.write(inputBuffer, 0, size);
} }
out.finish(); out.finish();
return output.toByteArray(); return output.toByteArray();
@ -124,26 +132,51 @@ public class Util {
public static byte[] decompress(byte[] inputBytes, int which) { public static byte[] decompress(byte[] inputBytes, int which) {
InputStream in = null; InputStream in = null;
try { try {
System.out.println("Bytes: "+Util.bytesToHex(inputBytes));
ByteArrayInputStream input = new ByteArrayInputStream(inputBytes); ByteArrayInputStream input = new ByteArrayInputStream(inputBytes);
ByteArrayOutputStream output = new ByteArrayOutputStream(2048); ByteArrayOutputStream output = new ByteArrayOutputStream(16*2048);
if(which == XZ) { if(which == XZ) {
in = new XZInputStream(input); in = new XZInputStream(input);
}else if(which == LZMA){ }else if(which == LZMA){
in = new LZMAInputStream(input); in = new LZMAInputStream(input);
} }
int size; int size;
while ((size = in.read()) != -1) { try{
output.write(size); 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(); in.close();
return output.toByteArray(); return output.toByteArray();
} catch (IOException ex) { } catch (IOException ex) {
Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, 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; return null;
} }
@ -177,6 +210,14 @@ public class Util {
return ByteBuffer.allocate(Long.SIZE / 8).putLong(Long.reverseBytes(input)).array(); 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 * Function to encrypt a message with AES
* @param input data to encrypt * @param input data to encrypt

View file

@ -5,7 +5,7 @@ import java.security.SecureRandom;
/** /**
* Created by nelson on 12/20/16. * Created by nelson on 12/20/16.
*/ */
public class Random { public class SecureRandomGenerator {
public static SecureRandom getSecureRandom(){ public static SecureRandom getSecureRandom(){
SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance(); SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance();

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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<Wallet> wallets, List<PrivateKeyBackup> privateKeys, List<LinkedAccount> 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;
}
}