3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-15 11:45:51 +00:00

Option to decrypt private keys and seed on the fly if printing a wallet dump of an encrypted wallet.

This commit is contained in:
Andreas Schildbach 2017-08-11 11:14:30 +02:00
parent 49e6af3bd7
commit ca033e3368
9 changed files with 64 additions and 31 deletions

View File

@ -1224,15 +1224,15 @@ public class ECKey implements EncryptableItem {
@Override @Override
public String toString() { public String toString() {
return toString(false, null); return toString(false, null, null);
} }
/** /**
* Produce a string rendering of the ECKey INCLUDING the private key. * Produce a string rendering of the ECKey INCLUDING the private key.
* Unless you absolutely need the private key it is better for security reasons to just use {@link #toString()}. * Unless you absolutely need the private key it is better for security reasons to just use {@link #toString()}.
*/ */
public String toStringWithPrivate(NetworkParameters params) { public String toStringWithPrivate(@Nullable KeyParameter aesKey, NetworkParameters params) {
return toString(true, params); return toString(true, aesKey, params);
} }
public String getPrivateKeyAsHex() { public String getPrivateKeyAsHex() {
@ -1247,13 +1247,14 @@ public class ECKey implements EncryptableItem {
return getPrivateKeyEncoded(params).toString(); return getPrivateKeyEncoded(params).toString();
} }
private String toString(boolean includePrivate, NetworkParameters params) { private String toString(boolean includePrivate, @Nullable KeyParameter aesKey, NetworkParameters params) {
final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(this).omitNullValues(); final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(this).omitNullValues();
helper.add("pub HEX", getPublicKeyAsHex()); helper.add("pub HEX", getPublicKeyAsHex());
if (includePrivate) { if (includePrivate) {
ECKey decryptedKey = isEncrypted() ? decrypt(checkNotNull(aesKey)) : this;
try { try {
helper.add("priv HEX", getPrivateKeyAsHex()); helper.add("priv HEX", decryptedKey.getPrivateKeyAsHex());
helper.add("priv WIF", getPrivateKeyAsWiF(params)); helper.add("priv WIF", decryptedKey.getPrivateKeyAsWiF(params));
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
// TODO: Make hasPrivKey() work for deterministic keys and fix this. // TODO: Make hasPrivKey() work for deterministic keys and fix this.
} catch (Exception e) { } catch (Exception e) {
@ -1271,7 +1272,8 @@ public class ECKey implements EncryptableItem {
return helper.toString(); return helper.toString();
} }
public void formatKeyWithAddress(boolean includePrivateKeys, StringBuilder builder, NetworkParameters params) { public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParameter aesKey, StringBuilder builder,
NetworkParameters params) {
final Address address = toAddress(params); final Address address = toAddress(params);
builder.append(" addr:"); builder.append(" addr:");
builder.append(address.toString()); builder.append(address.toString());
@ -1282,7 +1284,7 @@ public class ECKey implements EncryptableItem {
builder.append("\n"); builder.append("\n");
if (includePrivateKeys) { if (includePrivateKeys) {
builder.append(" "); builder.append(" ");
builder.append(toStringWithPrivate(params)); builder.append(toStringWithPrivate(aesKey, params));
builder.append("\n"); builder.append("\n");
} }
} }

View File

@ -615,13 +615,14 @@ public class DeterministicKey extends ECKey {
} }
@Override @Override
public void formatKeyWithAddress(boolean includePrivateKeys, StringBuilder builder, NetworkParameters params) { public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParameter aesKey, StringBuilder builder,
NetworkParameters params) {
final Address address = toAddress(params); final Address address = toAddress(params);
builder.append(" addr:").append(address); builder.append(" addr:").append(address);
builder.append(" hash160:").append(Utils.HEX.encode(getPubKeyHash())); builder.append(" hash160:").append(Utils.HEX.encode(getPubKeyHash()));
builder.append(" (").append(getPathAsString()).append(")\n"); builder.append(" (").append(getPathAsString()).append(")\n");
if (includePrivateKeys) { if (includePrivateKeys) {
builder.append(" ").append(toStringWithPrivate(params)).append("\n"); builder.append(" ").append(toStringWithPrivate(aesKey, params)).append("\n");
} }
} }
} }

View File

@ -1311,16 +1311,19 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public String toString(boolean includePrivateKeys, NetworkParameters params) { public String toString(boolean includePrivateKeys, @Nullable KeyParameter aesKey, NetworkParameters params) {
final DeterministicKey watchingKey = getWatchingKey(); final DeterministicKey watchingKey = getWatchingKey();
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
if (seed != null) { if (seed != null) {
if (seed.isEncrypted()) { if (includePrivateKeys) {
builder.append("Seed is encrypted\n"); DeterministicSeed decryptedSeed = seed.isEncrypted()
} else if (includePrivateKeys) { ? seed.decrypt(getKeyCrypter(), DEFAULT_PASSPHRASE_FOR_MNEMONIC, aesKey) : seed;
final List<String> words = seed.getMnemonicCode(); final List<String> words = decryptedSeed.getMnemonicCode();
builder.append("Seed as words: ").append(Utils.SPACE_JOINER.join(words)).append('\n'); builder.append("Seed as words: ").append(Utils.SPACE_JOINER.join(words)).append('\n');
builder.append("Seed as hex: ").append(seed.toHexString()).append('\n'); builder.append("Seed as hex: ").append(decryptedSeed.toHexString()).append('\n');
} else {
if (seed.isEncrypted())
builder.append("Seed is encrypted\n");
} }
builder.append("Seed birthday: ").append(seed.getCreationTimeSeconds()).append(" [") builder.append("Seed birthday: ").append(seed.getCreationTimeSeconds()).append(" [")
.append(Utils.dateTimeFormat(seed.getCreationTimeSeconds() * 1000)).append("]\n"); .append(Utils.dateTimeFormat(seed.getCreationTimeSeconds() * 1000)).append("]\n");
@ -1329,13 +1332,14 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
.append(Utils.dateTimeFormat(watchingKey.getCreationTimeSeconds() * 1000)).append("]\n"); .append(Utils.dateTimeFormat(watchingKey.getCreationTimeSeconds() * 1000)).append("]\n");
} }
builder.append("Key to watch: ").append(watchingKey.serializePubB58(params)).append('\n'); builder.append("Key to watch: ").append(watchingKey.serializePubB58(params)).append('\n');
formatAddresses(includePrivateKeys, params, builder); formatAddresses(includePrivateKeys, aesKey, params, builder);
return builder.toString(); return builder.toString();
} }
protected void formatAddresses(boolean includePrivateKeys, NetworkParameters params, StringBuilder builder) { protected void formatAddresses(boolean includePrivateKeys, @Nullable KeyParameter aesKey, NetworkParameters params,
StringBuilder builder) {
for (ECKey key : getKeys(false, true)) for (ECKey key : getKeys(false, true))
key.formatKeyWithAddress(includePrivateKeys, builder, params); key.formatKeyWithAddress(includePrivateKeys, aesKey, builder, params);
} }
/** The number of signatures required to spend coins received by this keychain. */ /** The number of signatures required to spend coins received by this keychain. */

View File

@ -785,16 +785,16 @@ public class KeyChainGroup implements KeyBag {
} }
} }
public String toString(boolean includePrivateKeys) { public String toString(boolean includePrivateKeys, @Nullable KeyParameter aesKey) {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
if (basic != null) { if (basic != null) {
List<ECKey> keys = basic.getKeys(); List<ECKey> keys = basic.getKeys();
Collections.sort(keys, ECKey.AGE_COMPARATOR); Collections.sort(keys, ECKey.AGE_COMPARATOR);
for (ECKey key : keys) for (ECKey key : keys)
key.formatKeyWithAddress(includePrivateKeys, builder, params); key.formatKeyWithAddress(includePrivateKeys, aesKey, builder, params);
} }
for (DeterministicKeyChain chain : chains) for (DeterministicKeyChain chain : chains)
builder.append(chain.toString(includePrivateKeys, params)).append('\n'); builder.append(chain.toString(includePrivateKeys, aesKey, params)).append('\n');
return builder.toString(); return builder.toString();
} }

View File

@ -28,6 +28,7 @@ import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypter; import org.bitcoinj.crypto.KeyCrypter;
import org.bitcoinj.script.Script; import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptBuilder;
import org.spongycastle.crypto.params.KeyParameter;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -234,7 +235,8 @@ public class MarriedKeyChain extends DeterministicKeyChain {
} }
@Override @Override
protected void formatAddresses(boolean includePrivateKeys, NetworkParameters params, StringBuilder builder2) { protected void formatAddresses(boolean includePrivateKeys, @Nullable KeyParameter aesKey, NetworkParameters params,
StringBuilder builder2) {
for (DeterministicKeyChain followingChain : followingKeyChains) for (DeterministicKeyChain followingChain : followingKeyChains)
builder2.append("Following chain: ").append(followingChain.getWatchingKey().serializePubB58(params)) builder2.append("Following chain: ").append(followingChain.getWatchingKey().serializePubB58(params))
.append('\n'); .append('\n');

View File

@ -3183,20 +3183,29 @@ public class Wallet extends BaseTaggableObject
@Override @Override
public String toString() { public String toString() {
return toString(false, true, true, null); return toString(false, null, true, true, null);
} }
/**
* @deprecated Use {@link #toString(boolean, KeyParameter, boolean, boolean, AbstractBlockChain)} instead.
*/
@Deprecated
public String toString(boolean includePrivateKeys, boolean includeTransactions, boolean includeExtensions,
@Nullable AbstractBlockChain chain) {
return toString(includePrivateKeys, includeTransactions, includeExtensions, chain);
}
/** /**
* Formats the wallet as a human readable piece of text. Intended for debugging, the format is not meant to be * Formats the wallet as a human readable piece of text. Intended for debugging, the format is not meant to be
* stable or human readable. * stable or human readable.
* @param includePrivateKeys Whether raw private key data should be included. * @param includePrivateKeys Whether raw private key data should be included.
* @param key for decrypting private key data for if the wallet is encrypted.
* @param includeTransactions Whether to print transaction data. * @param includeTransactions Whether to print transaction data.
* @param includeExtensions Whether to print extension data. * @param includeExtensions Whether to print extension data.
* @param chain If set, will be used to estimate lock times for block timelocked transactions. * @param chain If set, will be used to estimate lock times for block timelocked transactions.
*/ */
public String toString(boolean includePrivateKeys, boolean includeTransactions, boolean includeExtensions, public String toString(boolean includePrivateKeys, @Nullable KeyParameter aesKey, boolean includeTransactions,
@Nullable AbstractBlockChain chain) { boolean includeExtensions, @Nullable AbstractBlockChain chain) {
lock.lock(); lock.lock();
keyChainGroupLock.lock(); keyChainGroupLock.lock();
try { try {
@ -3226,7 +3235,7 @@ public class Wallet extends BaseTaggableObject
final Date keyRotationTime = getKeyRotationTime(); final Date keyRotationTime = getKeyRotationTime();
if (keyRotationTime != null) if (keyRotationTime != null)
builder.append("Key rotation time: ").append(Utils.dateTimeFormat(keyRotationTime)).append('\n'); builder.append("Key rotation time: ").append(Utils.dateTimeFormat(keyRotationTime)).append('\n');
builder.append(keyChainGroup.toString(includePrivateKeys)); builder.append(keyChainGroup.toString(includePrivateKeys, aesKey));
if (!watchedScripts.isEmpty()) { if (!watchedScripts.isEmpty()) {
builder.append("\nWatched scripts:\n"); builder.append("\nWatched scripts:\n");

View File

@ -317,7 +317,7 @@ public class ECKeyTest {
ECKey key = ECKey.fromPrivate(BigInteger.TEN).decompress(); // An example private key. ECKey key = ECKey.fromPrivate(BigInteger.TEN).decompress(); // An example private key.
NetworkParameters params = MainNetParams.get(); NetworkParameters params = MainNetParams.get();
assertEquals("ECKey{pub HEX=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, isEncrypted=false, isPubKeyOnly=false}", key.toString()); assertEquals("ECKey{pub HEX=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, isEncrypted=false, isPubKeyOnly=false}", key.toString());
assertEquals("ECKey{pub HEX=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, priv HEX=000000000000000000000000000000000000000000000000000000000000000a, priv WIF=5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreBoNWTw6, isEncrypted=false, isPubKeyOnly=false}", key.toStringWithPrivate(params)); assertEquals("ECKey{pub HEX=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, priv HEX=000000000000000000000000000000000000000000000000000000000000000a, priv WIF=5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreBoNWTw6, isEncrypted=false, isPubKeyOnly=false}", key.toStringWithPrivate(null, params));
} }
@Test @Test

View File

@ -1447,7 +1447,21 @@ public class WalletTool {
// there just for the dump case. // there just for the dump case.
if (chainFileName.exists()) if (chainFileName.exists())
setup(); setup();
System.out.println(wallet.toString(options.has("dump-privkeys"), true, true, chain));
final boolean dumpPrivkeys = options.has("dump-privkeys");
if (dumpPrivkeys && wallet.isEncrypted()) {
if (password != null) {
final KeyParameter aesKey = passwordToKey(true);
if (aesKey == null)
return; // Error message already printed.
System.out.println(wallet.toString(true, aesKey, true, true, chain));
} else {
System.err.println("Can't dump privkeys, wallet is encrypted.");
return;
}
} else {
System.out.println(wallet.toString(dumpPrivkeys, null, true, true, chain));
}
} }
private static void setCreationTime() { private static void setCreationTime() {

View File

@ -4,8 +4,9 @@ Usage: wallet-tool --flags action-name
wallet-tool action-name --flags wallet-tool action-name --flags
>>> ACTIONS >>> ACTIONS
dump Loads and prints the given wallet in textual form to stdout. Private keys are only printed dump Loads and prints the given wallet in textual form to stdout. Private keys and seed are only
if --dump-privkeys is specified. printed if --dump-privkeys is specified. If the wallet is encrypted, also specify the --password
option to dump the private keys and seed.
raw-dump Prints the wallet as a raw protobuf with no parsing or sanity checking applied. raw-dump Prints the wallet as a raw protobuf with no parsing or sanity checking applied.
create Makes a new wallet in the file specified by --wallet. create Makes a new wallet in the file specified by --wallet.
Will complain and require --force if the wallet already exists. Will complain and require --force if the wallet already exists.