New feature: export wallet to incognito format

Incognito wallet is 48, 56 or 64 bytes of apparently random data.

  Allows user to hide wallet data in a pre-existing file or on a disk
  partition (preferably filled in advance with random data).

  Can be used to hide wallet securely in unencrypted cloud storage or
  on paper, without revealing the nature of the data.

  Data may be written at a user-specified offset into the file or
  partition, in which case user must remember the offset.

  Each export operation uses a new random init vector to create different
  data each time.  This allows the user to hide wallets at different
  locations on the Net without detection.

  User must remember hash preset in addition to passphrase (though trial
  and error can be used if it's forgotten).

  Fully integrated with address generation and tx signing operations.
This commit is contained in:
The MMGen Project 2014-07-14 18:31:00 +04:00
commit a3c4bd8731
11 changed files with 384 additions and 125 deletions

View file

@ -66,6 +66,9 @@ help_data = {
-b, --from-brain l,p Generate {W} from a user-created password,
i.e. a "brainwallet", using seed length 'l' and
hash preset 'p' (comma-separated)
-g, --from-incognito Generate {W} from an incognito-format wallet
-G, --hidden-incog-data f,o,l Generate {W} from incognito data in file
'f' at offset 'o', with seed length of 'l'
-m, --from-mnemonic Generate {W} from an electrum-like mnemonic
-s, --from-seed Generate {W} from a seed in .{S} format
@ -107,12 +110,14 @@ in all future invocations with that passphrase
)
}
short_opts = ["h","A","d:","e","H","K","l:","p:","P:","q","S",
"v","x","b:","m","s"]
short_opts = ["h","A","d:","e",
"H","K","l:","p:",
"P:","q","S","v","x","b:",
"g","G:","m","s"]
long_opts = ["help","no_addresses","outdir=","echo_passphrase",
"show_hash_presets","no_keyconv","seed_len=","hash_preset=",
"passwd_file=","quiet","stdout","verbose","b16","from_brain=",
"from_mnemonic","from_seed"]
"from_incognito","hidden_incog_data=","from_mnemonic","from_seed"]
if invoked_as == "addrgen":
for i in "A","x": short_opts.remove(i)
@ -123,6 +128,7 @@ opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
if 'show_hash_presets' in opts: show_hash_presets()
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
if 'hidden_incog_data' in opts: opts['from_incognito'] = True
opts['gen_what'] = gen_what
@ -131,9 +137,10 @@ check_opts(opts,long_opts)
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
if len(cmd_args) == 1 and (
'from_mnemonic' in opts or
'from_brain' in opts or
'from_seed' in opts
'from_mnemonic' in opts
or 'from_brain' in opts
or 'from_seed' in opts
or 'from_incognito' in opts
):
infile,addr_list_arg = "",cmd_args[0]
elif len(cmd_args) == 2:
@ -160,6 +167,7 @@ else:
seed = get_seed_retry(infile,opts)
seed_id = make_chksum_8(seed)
addr_data = generate_addrs(seed, addr_list, opts)
addr_data_str = format_addr_data(addr_data, seed_id, opts)

View file

@ -46,9 +46,10 @@ help_data = {
-b, --from-brain l,p Generate keys from a user-created password,
i.e. a "brainwallet", using seed length 'l' and
hash preset 'p'
-w, --use-wallet-dat Get keys from a running bitcoind
-g, --from-incognito Generate keys from an incognito-format wallet
-m, --from-mnemonic Generate keys from an electrum-like mnemonic
-s, --from-seed Generate keys from a seed in .{} format
-w, --use-wallet-dat Get keys from a running bitcoind
Transactions with either mmgen or non-mmgen input addresses may be signed.
For non-mmgen inputs, the bitcoind wallet.dat is used as the key source.
@ -77,10 +78,11 @@ Seed data supplied in files must have the following extensions:
""".format(g.seed_ext,g.wallet_ext,g.seed_ext,g.mn_ext,g.brain_ext)
}
short_opts = "hd:eiIk:P:qVb:msw"
short_opts = "hd:eiIk:P:qVb:wgms"
long_opts = "help","outdir=","echo_passphrase","info","tx_id",\
"keys_from_file=","passwd_file=","quiet","skip_key_preverify",\
"from_brain=","from_mnemonic","from_seed","use_wallet_dat"
"from_brain=","use_wallet_dat",\
"from_incognito","from_mnemonic","from_seed"
opts,infiles = process_opts(sys.argv,help_data,short_opts,long_opts)
if "quiet" in opts: g.quiet = True

View file

@ -35,22 +35,27 @@ help_data = {
-h, --help Print this help message
-d, --outdir d Specify an alternate directory 'd' for output
-e, --echo-passphrase Print passphrase to screen when typing it
-m, --export-mnemonic Export the wallet's mnemonic to file
-P, --passwd-file f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without prompting
-s, --export-seed Export the wallet's seed to file
-S, --stdout Print seed or mnemonic data to standard output
-v, --verbose Produce more verbose output
-g, --export-incognito Export wallet to incognito format
-G, --hide-incog-data f,o Hide incognito data in existing file 'f'
at offset 'o' (comma-separated)
-m, --export-mnemonic Export the wallet's mnemonic to file
-s, --export-seed Export the wallet's seed to file
"""
}
short_opts = "hd:emP:qsSv"
long_opts = "help","outdir=","echo_passphrase","export_mnemonic",\
"passwd_file=","quiet","export_seed","stdout","verbose"
short_opts = "hd:eP:qSvgG:ms"
long_opts = "help","outdir=","echo_passphrase","passwd_file=","quiet",\
"stdout","verbose",\
"export_incognito","hide_incog_data=","export_mnemonic","export_seed"
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
if 'hide_incog_data' in opts: opts['export_incognito'] = True
# Argument sanity checks and processing:
check_opts(opts,long_opts)
@ -61,20 +66,69 @@ check_infile(cmd_args[0])
if 'export_mnemonic' in opts:
qmsg("Exporting mnemonic data to file by user request")
if 'export_seed' in opts:
elif 'export_seed' in opts:
qmsg("Exporting seed data to file by user request")
elif 'export_incognito' in opts:
qmsg("Exporting wallet to incognito format by user request")
d = get_data_from_wallet(cmd_args[0],silent=True)
seed_id,key_id,preset,salt,enc_seed = \
d[1][0], d[1][1], d[2].split(":")[0], d[3], d[4]
passwd = get_mmgen_passphrase("Enter mmgen passphrase: ",opts)
key = make_key(passwd, salt, preset, "main key")
# We don't need the seed; just do this to verify password.
if decrypt_seed(enc_seed, key, seed_id, key_id) == False:
sys.exit(2)
from Crypto import Random
iv = Random.new().read(g.aesctr_iv_len)
iv_id = make_chksum_8(iv)
qmsg("IV ID: %s" % iv_id)
from binascii import hexlify
from hashlib import sha256
# IV is used BOTH to initialize counter and to salt password!
key = make_key(passwd, iv, preset, "wrapper key")
incog_enc = encrypt_seed(salt + enc_seed, key, iv=int(hexlify(iv),16))
if "hide_incog_data" in opts:
fname,offset = opts['hide_incog_data'].split(",") # Already sanity-checked
offset = int(offset)
check_data_fits_file_at_offset(fname,offset,len(iv + incog_enc),"write")
if not g.quiet: confirm_or_exit("","alter file '%s'" % fname)
f = os.open(fname,os.O_RDWR)
os.lseek(f, offset, os.SEEK_SET)
os.write(f, iv + incog_enc)
os.close(f)
qmsg("Data written to file '%s' at offset %s" % (fname,offset),
"Data written to file")
else:
fn = "%s-%s-%s[%s,%s].%s" % (seed_id, key_id, iv_id,
len(enc_seed)*8, preset, g.incog_ext)
export_to_file(fn, iv + incog_enc, "incognito wallet data", opts)
sys.exit()
seed = get_seed_from_wallet(cmd_args[0], opts)
if seed: qmsg("Wallet is OK")
else:
msg("Error opening wallet")
sys.exit(2)
if 'export_mnemonic' in opts:
wl = get_default_wordlist()
from mmgen.mnemonic import get_mnemonic_from_seed
p = True if g.debug else False
mn = get_mnemonic_from_seed(seed, wl, g.default_wl, print_info=p)
fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext)
export_to_file(fn, " ".join(mn)+"\n", "mnemonic data", opts)
write_mnemonic(mn, seed, opts)
if 'export_seed' in opts:
write_seed(seed, opts)
elif 'export_seed' in opts:
from mmgen.bitcoin import b58encode_pad
data = col4(b58encode_pad(seed))
chk = make_chksum_6(b58encode_pad(seed))
fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext)
export_to_file(fn, "%s %s\n" % (chk,data), "seed data", opts)

View file

@ -54,6 +54,7 @@ help_data = {
-b, --from-brain l,p Generate wallet from a user-created passphrase,
i.e. a "brainwallet", using seed length 'l' and
hash preset 'p' (comma-separated)
-g, --from-incognito Generate wallet from an incognito-format wallet
-m, --from-mnemonic Generate wallet from an Electrum-like mnemonic
-s, --from-seed Generate wallet from a seed in .{S} format
@ -93,10 +94,10 @@ in all future invocations with that passphrase.
)
}
short_opts = "hd:eHl:L:p:P:qu:vb:ms"
short_opts = "hd:eHl:L:p:P:qu:vb:gms"
long_opts = "help","outdir=","echo_passphrase","show_hash_presets","seed_len=",\
"label=","hash_preset=","passwd_file=","quiet","usr_randlen=",\
"verbose","from_brain=","from_mnemonic","from_seed"
"label=","hash_preset=","passwd_file=","quiet","usr_randlen=","verbose",\
"from_brain=","from_incognito","from_mnemonic","from_seed"
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'quiet' in opts: g.quiet = True
@ -111,7 +112,7 @@ if len(cmd_args) == 1:
infile = cmd_args[0]
check_infile(infile)
ext = infile.split(".")[-1]
ok_exts = g.seed_ext, g.mn_ext, g.brain_ext
ok_exts = g.seedfile_exts
for e in ok_exts:
if e == ext: break
else:
@ -151,10 +152,13 @@ if g.debug: display_user_random_data(usr_keys,key_timings)
usr_rand_data = sha256(usr_keys).digest() + \
sha256("".join(key_timings)).digest()
for i in 'from_mnemonic','from_brain','from_seed':
for i in 'from_mnemonic','from_brain','from_seed','from_incognito':
if infile or (i in opts):
seed = get_seed_retry(infile,opts)
qmsg(""); break
if "from_incognito" in opts or get_extension(infile) == g.incog_ext:
qmsg(cmessages['incognito'] % make_chksum_8(seed))
else: qmsg("")
break
else:
# Truncate random data for smaller seed lengths
seed = os_rand_data[0] + usr_rand_data

View file

@ -18,7 +18,6 @@
import sys, getopt
import mmgen.config as g
from mmgen.util import msg,check_infile
def usage(hd):
print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
@ -70,6 +69,15 @@ def process_opts(argv,help_data,short_opts,long_opts):
return opts,args
def show_opts_and_cmd_args(opts,cmd_args):
print "Processed options: %s" % repr(opts)
print "Cmd args: %s" % repr(cmd_args)
# Everything below here is MMGen-specific:
from mmgen.util import msg,check_infile
def check_opts(opts,long_opts):
# These must be set to the default values in mmgen.config:
@ -91,6 +99,7 @@ def check_opts(opts,long_opts):
if opt == 'outdir':
what = "output directory"
import re, os, stat
# TODO Non-portable:
d = re.sub(r'/*$','', val)
opts[opt] = d
@ -121,12 +130,61 @@ def check_opts(opts,long_opts):
"%s": illegal character in label. Only ASCII characters are permitted.
""".strip() % ch)
sys.exit(1)
elif opt == 'hide_incog_data' or opt == 'hidden_incog_data':
try:
if opt == 'hide_incog_data':
outfile,offset = val.split(",")
else:
outfile,offset,seed_len = val.split(",")
except:
msg("'%s': invalid %s" % (val,what))
sys.exit(1)
try:
o = int(offset)
except:
msg("'%s': invalid 'o' %s (not an integer)" % (offset,what))
sys.exit(1)
if o < 0:
msg("'%s': invalid 'o' %s (less than zero)" % (offset,what))
sys.exit(1)
if opt == 'hidden_incog_data':
try:
sl = int(seed_len)
except:
msg("'%s': invalid 'l' %s (not an integer)" % (sl,what))
sys.exit(1)
if sl not in g.seed_lens:
msg("'%s': invalid 'l' %s (valid choices: %s)" %
(sl,what," ".join(str(i) for i in g.seed_lens)))
sys.exit(1)
import os, stat
try: mode = os.stat(outfile).st_mode
except:
msg("Unable to stat requested %s '%s'" % (what,outfile))
sys.exit(1)
if not (stat.S_ISREG(mode) or stat.S_ISBLK(mode)):
msg("Requested %s '%s' is not a file or block device" %
(what,outfile))
sys.exit(1)
ac,m = (os.W_OK,"writ") \
if "hide_incog_data" in opts else (os.R_OK,"read")
if not os.access(outfile, ac):
msg("Requested %s '%s' is un%sable by you" % (what,outfile,m))
sys.exit(1)
elif opt == 'from_brain':
try:
l,p = val.split(",")
except:
msg("'%s': invalid %s" % (val,what))
sys.exit(1)
sys.exit(2)
try:
int(l)
@ -162,11 +220,6 @@ def check_opts(opts,long_opts):
if g.debug: print "check_opts(): No test for opt '%s'" % opt
def show_opts_and_cmd_args(opts,cmd_args):
print "Processed options: %s" % repr(opts)
print "Cmd args: %s" % repr(cmd_args)
def set_if_unset_and_typeconvert(opts,opt):
if opt in g.cl_override_vars:

View file

@ -63,6 +63,9 @@ def generate_addrs(seed, addrnums, opts):
while a:
seed = sha512(seed).digest()
i += 1 # round /i/
if g.debug: print "Seed round %s: %s" % (i, hexlify(seed))
if i < a[0]: continue
a.pop(0)
@ -74,6 +77,9 @@ def generate_addrs(seed, addrnums, opts):
sec = sha256(sha256(seed).digest()).hexdigest()
wif = numtowif(int(sec,16))
if g.debug:
print "Privkey round %s:\n hex: %s\n wif: %s" % (i, sec, wif)
el = { 'num': i }
if not 'print_addresses_only' in opts:
@ -140,7 +146,7 @@ def format_addr_data(addrlist, seed_chksum, opts):
# Everything following a hash symbol '#' is a comment and ignored by {}.
# A text label of {} characters or less may be added to the right of each
# address, and it will be appended to the bitcoind wallet label upon import.
# The label may contain printable ASCII symbols.
# The label may contain any printable ASCII symbol.
""".strip().format(g.proj_name_cap,g.max_addr_label_len)
data = []

View file

@ -32,8 +32,9 @@ wallet_ext = "mmdat"
seed_ext = "mmseed"
mn_ext = "mmwords"
brain_ext = "mmbrain"
incog_ext = "mmincog"
seedfile_exts = wallet_ext, seed_ext, mn_ext, brain_ext
seedfile_exts = wallet_ext, seed_ext, mn_ext, brain_ext, incog_ext
addrfile_ext = "addrs"
keyfile_ext = "keys"
@ -53,13 +54,15 @@ http_timeout = 30
keyconv_exec = "keyconv"
from os import getenv
debug = True if getenv("MMGEN_DEBUG") else False
debug = True if getenv("MMGEN_DEBUG") else False
no_license = True if getenv("MMGEN_NOLICENSE") else False
mins_per_block = 8.5
passwd_max_tries = 5
max_randlen,min_randlen = 80,5
usr_randlen = 20
salt_len = 16
aesctr_iv_len = 16
hash_preset = '3'
hash_presets = {
@ -72,6 +75,7 @@ hash_presets = {
'4': [15, 8, 12],
'5': [16, 8, 16],
'6': [17, 8, 20],
'7': [18, 8, 24],
}
mmgen_idx_max_digits = 7

View file

@ -587,7 +587,7 @@ copy of the Program in return for a fee.
def do_license_msg(immed=False):
import mmgen.config as g
if g.quiet: return
if g.quiet or g.no_license: return
msg(gpl['warning'])
prompt = "%s " % gpl['prompt'].strip()

View file

@ -584,7 +584,7 @@ Generated from seed: %s
""".strip() % (" ".join(a)," ".join(b)))
sys.exit(3)
qmsg("Address mappings OK\n")
qmsg("Address mappings OK")
def check_addr_label(label):
@ -671,14 +671,14 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,seeds,opts,gen_pairs=False):
while seed_ids:
if seeds_keys:
seed = seeds[seeds_keys.pop()]
seed = seeds[seeds_keys.pop(0)]
else:
infile = False
if infiles:
infile = infiles.pop()
infile = infiles.pop(0)
seed = get_seed_retry(infile,opts)
elif "from_brain" in opts or "from_mnemonic" in opts \
or "from_seed" in opts:
or "from_seed" in opts or "from_incognito" in opts:
msg("Need data for seed ID %s" % seed_ids[0])
seed = get_seed_retry("",opts)
else:
@ -707,8 +707,14 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,seeds,opts,gen_pairs=False):
else: msg(" for ID %s" % seed_id)
else:
msg("Seed source produced an invalid seed ID (%s)" % seed_id)
if infile:
msg("Invalid input file: %s" % infile)
if "from_incognito" in opts or infile.split(".")[-1] == g.incog_ext:
msg(
"""Incorrect hash preset, password or incognito wallet data
Trying again...""")
infiles.insert(0,infile) # ugly!
elif infile:
msg("Invalid input file '%s'" % infile)
sys.exit(2)
return ret

View file

@ -27,10 +27,14 @@ from mmgen.term import *
def msg(s): sys.stderr.write(s + "\n")
def msg_r(s): sys.stderr.write(s)
def qmsg(s):
if not g.quiet: sys.stderr.write(s + "\n")
def qmsg_r(s):
if not g.quiet: sys.stderr.write(s)
def qmsg(s,alt=""):
if g.quiet:
if alt: sys.stderr.write(alt + "\n")
else: sys.stderr.write(s + "\n")
def qmsg_r(s,alt=""):
if g.quiet:
if alt: sys.stderr.write(alt)
else: sys.stderr.write(s)
def vmsg(s):
if g.verbose: sys.stderr.write(s + "\n")
def vmsg_r(s):
@ -38,6 +42,8 @@ def vmsg_r(s):
def bail(): sys.exit(9)
def get_extension(f): return f.split(".")[-1]
def my_raw_input(prompt,echo=True,allowed_chars=""):
try:
if echo:
@ -96,6 +102,25 @@ def show_hash_presets():
cmessages = {
'null': "",
'incognito_iv_id': """
If you know your IV ID, check it against the value above. If it's
incorrect, then your incognito data is invalid.
""",
'incognito_iv_id_hidden': """
If you know your IV ID, check it against the value above. If it's
incorrect, then your incognito data is invalid or you've supplied
an incorrect offset.
""",
'incognito_key_id': """
Check that the generated seed ID is correct. If it's not, then your
password or hash preset is incorrect or incognito data is corrupted.
""",
'incognito_key_id_hidden': """
Check that the generated seed ID is correct. If it's not, then your
password or hash preset is incorrect or incognito data is corrupted.
If the key ID is correct but the seed ID is not, then you might have
chosen an incorrect seed length.
""",
'unencrypted_secret_keys': """
This program generates secret keys from your {} seed, outputting them in
UNENCRYPTED form. Generate only the key(s) you need and guard them carefully.
@ -136,7 +161,7 @@ def confirm_or_exit(message, question, expect="YES"):
msg("Exiting at user request")
sys.exit(2)
msg("")
vmsg("")
def user_confirm(prompt,default_yes=False,verbose=False):
@ -265,6 +290,9 @@ def get_new_passphrase(what, opts):
def _scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
# Buflen arg is for brainwallets only, which use this function to generate
# the seed directly.
N,r,p = _get_hash_params(hash_preset)
import scrypt
@ -288,7 +316,7 @@ def _get_seed_from_brain_passphrase(words,opts):
return seed
def encrypt_seed(seed, key):
def encrypt_seed(seed, key, iv=1):
"""
Encrypt a seed for an {} deterministic wallet
""".format(g.proj_name_cap)
@ -297,12 +325,14 @@ def encrypt_seed(seed, key):
from Crypto.Cipher import AES
from Crypto.Util import Counter
c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128))
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
enc_seed = c.encrypt(seed)
vmsg_r("Performing a test decryption of the seed...")
c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128))
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
dec_seed = c.decrypt(enc_seed)
if dec_seed == seed: vmsg("done")
@ -366,60 +396,44 @@ def write_to_file(outfile,data,confirm=False):
f.close
def write_seed(seed, opts):
outfile = "%s.%s" % (make_chksum_8(seed).upper(),g.seed_ext)
if 'outdir' in opts:
outfile = "%s/%s" % (opts['outdir'], outfile)
from mmgen.bitcoin import b58encode_pad
data = col4(b58encode_pad(seed))
chk = make_chksum_6(b58encode_pad(seed))
o = "%s %s\n" % (chk,data)
def export_to_file(outfile, data, label, opts):
if 'stdout' in opts:
write_to_stdout(o,"seed data",confirm=True)
write_to_stdout(data, label, confirm=True)
elif not sys.stdout.isatty():
write_to_stdout(o,"seed data",confirm=False)
write_to_stdout(data, label, confirm=False)
else:
write_to_file(outfile,o)
msg("%s data saved to file '%s'" % ("Seed",outfile))
def write_mnemonic(mn, seed, opts):
outfile = "%s.%s" % (make_chksum_8(seed).upper(),g.mn_ext)
if 'outdir' in opts:
outfile = "%s/%s" % (opts['outdir'], outfile)
o = " ".join(mn) + "\n"
if 'stdout' in opts:
write_to_stdout(o,"mnemonic data",confirm=True)
elif not sys.stdout.isatty():
write_to_stdout(o,"mnemonic data",confirm=False)
else:
write_to_file(outfile,o)
msg("%s data saved to file '%s'" % ("Mnemonic",outfile))
if 'outdir' in opts:
outfile = "%s/%s" % (opts['outdir'], outfile)
write_to_file(outfile, data, confirm=False if g.quiet else True)
msg("%s saved to file '%s'" % (label.capitalize(), outfile))
def _display_control_data(label,metadata,hash_preset,salt,enc_seed):
msg("WALLET DATA")
fs = " {:25} {}"
fs = " {:18} {}"
pw_empty = "yes" if metadata[3] == "E" else "no"
from mmgen.bitcoin import b58encode_pad
for i in (
("Label:", label),
("Seed ID:", metadata[0]),
("Key ID:", metadata[1]),
("Seed length:", metadata[2]),
("Scrypt hash params:", "Preset '%s' (%s)" % (hash_preset,
" ".join([str(i) for i in _get_hash_params(hash_preset)]))),
("Passphrase is empty:", pw_empty),
("Seed length:", "%s bits (%s bytes)" %
(metadata[2],int(metadata[2])/8)),
("Scrypt params:", "Preset '%s' (%s)" % (hash_preset,
" ".join([str(i) for i in _get_hash_params(hash_preset)]))),
("Passphrase empty?", pw_empty.capitalize()),
("Timestamp:", "%s UTC" % metadata[4]),
("Salt:", b58encode_pad(salt)),
("Encrypted seed:", b58encode_pad(enc_seed))
): msg(fs.format(*i))
fs = " {:6} {}"
for i in (
("Salt:", ""),
(" b58:", b58encode_pad(salt)),
(" hex:", hexlify(salt)),
("Encrypted seed:", ""),
(" b58:", b58encode_pad(enc_seed)),
(" hex:", hexlify(enc_seed))
): msg(fs.format(*i))
@ -496,8 +510,9 @@ def _compare_checksums(chksum1, desc1, chksum2, desc2):
return True
else:
if g.debug:
msg("ERROR!\nComputed checksum %s (%s) doesn't match checksum %s (%s)" \
% (desc1,chksum1,desc2,chksum2))
print \
"ERROR!\nComputed checksum %s (%s) doesn't match checksum %s (%s)" \
% (desc1,chksum1,desc2,chksum2)
return False
def _is_hex(s):
@ -553,7 +568,7 @@ def _check_chksum_6(chk,val,desc,infile):
msg("Checksum: %s. Computed value: %s" % (chk,comp_chk))
sys.exit(2)
elif g.debug:
msg("%s checksum passed: %s" % (desc.capitalize(),chk))
print "%s checksum passed: %s" % (desc.capitalize(),chk)
def get_data_from_wallet(infile,silent=False):
@ -716,45 +731,143 @@ def get_seed_from_wallet(
return decrypt_seed(enc_seed, key, metadata[0], metadata[1])
def make_key(passwd, salt, hash_preset):
def check_data_fits_file_at_offset(fname,offset,dlen,action):
# TODO: Check for Windows
import os, stat
if stat.S_ISBLK(os.stat(fname).st_mode):
fd = os.open(fname, os.O_RDONLY)
fsize = os.lseek(fd, 0, os.SEEK_END)
os.close(fd)
else:
fsize = os.stat(fname).st_size
vmsg_r("Hashing passphrase. Please wait...")
if fsize < offset + dlen:
msg(
"Destination file has length %s, too short to %s %s bytes of data at offset %s"
% (fsize,action,dlen,offset))
sys.exit(1)
def get_hidden_incog_data(opts):
# Already sanity-checked:
fname,offset,seed_len = opts['hidden_incog_data'].split(",")
qmsg("Getting hidden incog data from file '%s'" % fname)
dlen = g.aesctr_iv_len + g.salt_len + (int(seed_len)/8)
fsize = check_data_fits_file_at_offset(fname,int(offset),dlen,"read")
f = os.open(fname,os.O_RDONLY)
os.lseek(f, int(offset), os.SEEK_SET)
data = os.read(f, dlen)
os.close(f)
qmsg("Data read from file '%s' at offset %s" % (fname,offset),
"Data read from file")
return data
def get_seed_from_incog_wallet(
infile,
opts,
prompt="Enter %s wallet passphrase: " % g.proj_name_cap,
silent=False
):
what = "incognito wallet data"
if "hidden_incog_data" in opts:
d = get_hidden_incog_data(opts)
else:
d = get_data_from_file(infile,what)
# File could be of invalid length, so check:
valid_dlens = [i/8 + g.aesctr_iv_len + g.salt_len for i in g.seed_lens]
if len(d) not in valid_dlens:
qmsg("Invalid incognito file size: %s. Valid sizes (in bytes): %s" %
(len(d), " ".join([str(i) for i in valid_dlens]))
)
return False
iv, enc_incog_data = d[0:g.aesctr_iv_len], d[g.aesctr_iv_len:]
qmsg("IV ID: %s. Check this value if possible." % make_chksum_8(iv))
vmsg(cmessages['incognito_iv_id_hidden' if "hidden_incog_data" in opts
else 'incognito_iv_id'])
passwd = get_mmgen_passphrase(prompt,opts)
msg("Configured hash presets: %s" % " ".join(sorted(g.hash_presets)))
while True:
p = "Enter hash preset for %s wallet (default='%s'): "
hp = my_raw_input(p % (g.proj_name_cap, g.hash_preset))
if not hp:
hp = g.hash_preset; break
elif hp in g.hash_presets:
break
msg("%s: Invalid hash preset" % hp)
from hashlib import sha256
# IV is used BOTH to initialize counter and to salt password!
key = make_key(passwd, iv, hp, "wrapper key")
d = decrypt_seed(enc_incog_data, key, "", "", iv=int(hexlify(iv),16))
if d == False: sys.exit(2)
salt,enc_seed = d[0:g.salt_len], d[g.salt_len:]
key = make_key(passwd, salt, hp, "main key")
vmsg("Key ID: %s" % make_chksum_8(key))
seed = decrypt_seed(enc_seed, key, "", "")
qmsg("Seed ID: %s. Check that this value is correct." % make_chksum_8(seed))
vmsg(cmessages['incognito_key_id_hidden' if "hidden_incog_data" in opts
else 'incognito_key_id'])
return seed
def make_key(passwd, salt, hash_preset, what="key"):
vmsg_r("Generating %s from passphrase. Please wait..." % what)
key = _scrypt_hash_passphrase(passwd, salt, hash_preset)
vmsg("done")
if g.debug: print "Key: %s" % hexlify(key)
return key
def decrypt_seed(enc_seed, key, seed_id, key_id):
def decrypt_seed(enc_seed, key, seed_id, key_id, iv=1):
vmsg_r("Checking key...")
chk = make_chksum_8(key)
if not _compare_checksums(chk, "of key", key_id, "in header"):
msg("Incorrect passphrase")
return False
vmsg("Checking key...")
chk1 = make_chksum_8(key)
if key_id:
if not _compare_checksums(chk1, "of key", key_id, "in header"):
msg("Incorrect passphrase")
return False
vmsg_r("Decrypting seed with key...")
vmsg("Decrypting seed with key...")
from Crypto.Cipher import AES
from Crypto.Util import Counter
c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128))
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
dec_seed = c.decrypt(enc_seed)
chk = make_chksum_8(dec_seed)
if _compare_checksums(chk,"of decrypted seed",seed_id,"in header"):
qmsg("Passphrase is OK")
else:
if not g.debug:
msg_r("Checking key ID...")
chk = make_chksum_8(key)
if _compare_checksums(chk, "of key", key_id, "in header"):
msg("Key ID is correct but decryption of seed failed")
else:
msg("Incorrect passphrase")
chk2 = make_chksum_8(dec_seed)
if seed_id:
if _compare_checksums(chk2,"of decrypted seed",seed_id,"in header"):
qmsg("Passphrase is OK")
else:
if not g.debug:
msg_r("Checking key ID...")
chk1 = make_chksum_8(key)
if _compare_checksums(chk1, "of key", key_id, "in header"):
msg("Key ID is correct but decryption of seed failed")
else:
msg("Incorrect passphrase")
return False
return False
# else:
# qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1))
if g.debug: msg("key: %s" % hexlify(key))
if g.debug: print "Decrypted seed: %s" % hexlify(dec_seed)
return dec_seed
@ -774,9 +887,11 @@ def get_seed(infile,opts,silent=False):
elif ext == g.brain_ext: source = "brainwallet"
elif ext == g.seed_ext: source = "seed"
elif ext == g.wallet_ext: source = "wallet"
elif 'from_mnemonic' in opts: source = "mnemonic"
elif 'from_brain' in opts: source = "brainwallet"
elif 'from_seed' in opts: source = "seed"
elif ext == g.incog_ext: source = "incognito wallet"
elif 'from_mnemonic' in opts: source = "mnemonic"
elif 'from_brain' in opts: source = "brainwallet"
elif 'from_seed' in opts: source = "seed"
elif 'from_incognito' in opts: source = "incognito wallet"
else:
if infile: msg(
"Invalid file extension for file: %s\nValid extensions: '.%s'" %
@ -808,11 +923,17 @@ def get_seed(infile,opts,silent=False):
seed = _get_seed_from_seed_data(words)
elif source == "wallet":
seed = get_seed_from_wallet(infile, opts, silent=silent)
elif source == "incognito wallet":
seed = get_seed_from_incog_wallet(infile, opts, silent=silent)
if infile and not seed and (source == "seed" or source == "mnemonic"):
msg("Invalid %s file: %s" % (source,infile))
if infile and not seed and (
source == "seed" or source == "mnemonic" or source == "incognito wallet"):
msg("Invalid %s file '%s'" % (source,infile))
sys.exit(2)
if g.debug: print "Seed: %s" % hexlify(seed)
return seed
# Repeat if entered data is invalid
@ -863,5 +984,6 @@ def do_pager(text):
break
else: print text+end
if __name__ == "__main__":
print "util.py"

View file

@ -2,17 +2,17 @@
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
@ -22,7 +22,7 @@ bitcoind-walletunlock.py: Unlock a Bitcoin wallet securely
import sys
from mmgen.Opts import *
from mmgen.tx import *
from mmgen.utils import msg, my_getpass, my_raw_input
from mmgen.util import msg, my_getpass, my_raw_input
prog_name = sys.argv[0].split("/")[-1]