Merge pull request #170 from biafra23/ethereumj/master

EtherSaleWallet
This commit is contained in:
skype me: roman.mandeleil 2014-12-11 10:28:03 +01:00
commit c9ea4c07d4
5 changed files with 251 additions and 0 deletions

View File

@ -0,0 +1,61 @@
package org.ethereum.wallet;
import javax.xml.bind.DatatypeConverter;
public class EtherSaleWallet {
private String encseed;
private String ethaddr;
private String email;
private String btcaddr;
public String getEncseed() {
return encseed;
}
public byte[] getEncseedBytes() {
return DatatypeConverter.parseHexBinary(encseed);
}
public void setEncseed(String encseed) {
this.encseed = encseed;
}
public String getEthaddr() {
return ethaddr;
}
public byte[] getEthaddrBytes() {
return DatatypeConverter.parseHexBinary(ethaddr);
}
public void setEthaddr(String ethaddr) {
this.ethaddr = ethaddr;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getBtcaddr() {
return btcaddr;
}
public void setBtcaddr(String btcaddr) {
this.btcaddr = btcaddr;
}
@Override
public String toString() {
return "EtherSaleWallet{" +
"encseed='" + encseed + '\'' +
", ethaddr='" + ethaddr + '\'' +
", email='" + email + '\'' +
", btcaddr='" + btcaddr + '\'' +
'}';
}
}

View File

@ -0,0 +1,82 @@
package org.ethereum.wallet;
import org.spongycastle.crypto.*;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.digests.SHA3Digest;
import org.spongycastle.crypto.engines.AESEngine;
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.BlockCipherPadding;
import org.spongycastle.crypto.paddings.PKCS7Padding;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import java.util.Arrays;
public class EtherSaleWalletDecoder {
public static final int HASH_ITERATIONS = 2000;
public static final int DERIVED_KEY_BIT_COUNT = 128;
public static final int IV_LENGTH = 16;
private EtherSaleWallet etherSaleWallet;
public EtherSaleWalletDecoder(final EtherSaleWallet wallet) {
etherSaleWallet = wallet;
}
public byte[] getPrivateKey(final String password) throws InvalidCipherTextException {
byte[] passwordHash = generatePasswordHash(password);
byte[] decryptedSeed = decryptSeed(passwordHash, etherSaleWallet.getEncseedBytes());
return hashSeed(decryptedSeed);
}
/* VisibleForTesting */
protected byte[] generatePasswordHash(final String password) {
char[] chars = password.toCharArray();
byte[] salt = password.getBytes();
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest());
generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(chars), salt, HASH_ITERATIONS);
return ((KeyParameter) generator.generateDerivedParameters(DERIVED_KEY_BIT_COUNT)).getKey();
}
private byte[] hashSeed(final byte[] seed) {
ExtendedDigest md = new SHA3Digest(256);
md.update(seed, 0, seed.length);
byte[] result = new byte[md.getDigestSize()];
md.doFinal(result, 0);
return result;
}
protected byte[] decryptSeed(byte[] pbkdf2PasswordHash, byte[] encseedBytesWithIV) throws InvalidCipherTextException {
// first 16 bytes are the IV (0-15)
byte[] ivBytes = Arrays.copyOf(encseedBytesWithIV, IV_LENGTH);
// use bytes 16 to the end for encrypted seed
byte[] encData = Arrays.copyOfRange(encseedBytesWithIV, IV_LENGTH, encseedBytesWithIV.length);
// setup cipher parameters with key and IV
KeyParameter keyParam = new KeyParameter(pbkdf2PasswordHash);
CipherParameters params = new ParametersWithIV(keyParam, ivBytes);
// setup AES cipher in CBC mode with PKCS7 padding
BlockCipherPadding padding = new PKCS7Padding();
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), padding);
cipher.reset();
cipher.init(false, params);
// create a temporary buffer to decode into (it'll include padding)
byte[] buffer = new byte[cipher.getOutputSize(encData.length)];
int length = cipher.processBytes(encData, 0, encData.length, buffer, 0);
length += cipher.doFinal(buffer, length);
// remove padding
byte[] result = new byte[length];
System.arraycopy(buffer, 0, result, 0, length);
return result;
}
}

View File

@ -0,0 +1,96 @@
package org.ethereum.wallet;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.ethereum.crypto.ECKey;
import org.junit.Before;
import org.junit.Test;
import org.spongycastle.crypto.InvalidCipherTextException;
import org.spongycastle.util.encoders.Hex;
import java.io.IOException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class EtherSaleWalletDecoderTest {
private ObjectMapper mapper = new ObjectMapper();
private EtherSaleWalletDecoder walletDecoder;
private EtherSaleWallet etherSaleWallet;
@Before
public void setUp() throws IOException {
etherSaleWallet = mapper.readValue(getClass().getResourceAsStream("/wallet/ethersalewallet.json"), EtherSaleWallet.class);
walletDecoder = new EtherSaleWalletDecoder(etherSaleWallet);
}
@Test
public void shouldGeneratePasswordHash() throws InvalidKeySpecException, NoSuchAlgorithmException {
byte[] result = walletDecoder.generatePasswordHash("foobar");
String resultString = Hex.toHexString(result);
assertThat(resultString.toLowerCase(), is("d11d0652ee9ca94500ba8301903c8f6a"));
}
@Test
public void shouldGeneratePasswordHashWithUmlauts() throws InvalidKeySpecException, NoSuchAlgorithmException {
byte[] result = walletDecoder.generatePasswordHash("öäüß");
String resultString = Hex.toHexString(result);
assertThat(resultString.toLowerCase(), is("67162c127acd9ac55a75a8c5367b9a1a"));
}
@Test
public void shouldGeneratePasswordHashWithUnicode() throws InvalidKeySpecException, NoSuchAlgorithmException {
byte[] result = walletDecoder.generatePasswordHash("");
String resultString = Hex.toHexString(result);
assertThat(resultString.toLowerCase(), is("47204606123eae746a633c632904d94f"));
}
@Test
public void shouldDecryptSeed() throws InvalidCipherTextException {
byte[] result = walletDecoder.decryptSeed(walletDecoder.generatePasswordHash("foobar"), etherSaleWallet.getEncseedBytes());
String resultString = Hex.toHexString(result);
assertThat(resultString.toLowerCase(), is("37343165366130323566656533363039626262613564366430373038353964643534623862646231653232333431363133653462623832643333313537663035"));
}
@Test
public void shouldGetPrivateKey() throws InvalidCipherTextException {
byte[] result = walletDecoder.getPrivateKey("foobar");
String resultString = Hex.toHexString(result);
assertThat(resultString.toLowerCase(), is("74ef8a796480dda87b4bc550b94c408ad386af0f65926a392136286784d63858"));
}
@Test(expected = InvalidCipherTextException.class)
public void shouldRejectWrongPassword() throws InvalidCipherTextException {
walletDecoder.getPrivateKey("foo");
}
@Test(expected = InvalidCipherTextException.class)
public void shouldRejectWrongPasswordSameLength() throws InvalidCipherTextException {
walletDecoder.getPrivateKey("barfoo");
}
@Test
public void ethereumAddressShouldMatchPrivateKey() throws InvalidCipherTextException {
BigInteger privKey = new BigInteger(walletDecoder.getPrivateKey("foobar"));
byte[] addr = ECKey.fromPrivate(privKey).getAddress();
assertThat(Hex.toHexString(etherSaleWallet.getEthaddrBytes()), is(Hex.toHexString(addr)));
}
@Test(expected = InvalidCipherTextException.class)
public void shouldHandleBrokenWallet() throws IOException, InvalidCipherTextException {
EtherSaleWallet brokenEtherSaleWallet = mapper.readValue(getClass().getResourceAsStream("/wallet/ethersalewallet_broken.json"), EtherSaleWallet.class);
EtherSaleWalletDecoder walletDecoder = new EtherSaleWalletDecoder(brokenEtherSaleWallet);
walletDecoder.getPrivateKey("foobar");
}
}

View File

@ -0,0 +1,6 @@
{
"encseed" : "957e46d54c10da45351554eb60d731b164043743dfb212ccf1827491a01cf344b390e4ac5af640e4f54ff28b046e48dc84094b99011d72ca79f2da9aa2792f4d2b8545455ca8dbba15d69048b3f95eccfed2d19427abcb2c9483e7491163eb1b",
"ethaddr" : "ba73facb4f8291f09f27f90fe1213537b910065e",
"email" : "foo@bar.com",
"btcaddr" : "1JEQr5LHrY8yVmWFaB31BVa9Y6sLQ9Kg41"
}

View File

@ -0,0 +1,6 @@
{
"encseed" : "957e46d54c10da45351554eb60d731b164043743dfb212ccf1827491a01cf344b390e4ac5af640e4f54ff28b046e48dc84094b99011d72ca79f2da9aa2792f4d2b8545455ca8dbba15d69048b3f95eccfed2d19427abcb2c9483e7491163eb1c",
"ethaddr" : "ba73facb4f8291f09f27f90fe1213537b910065e",
"email" : "foo@bar.com",
"btcaddr" : "1JEQr5LHrY8yVmWFaB31BVa9Y6sLQ9Kg41"
}