New 'crypto.py' module for higher-level functions from 'util.py'.

'mmgen-keygen', 'mmgen-txsign': encrypted keylist support
This commit is contained in:
The MMGen Project 2014-08-04 22:07:47 +04:00
commit 26aa2eb64b
14 changed files with 682 additions and 626 deletions

View file

@ -19,7 +19,7 @@
"""
mmgen-addrgen: Generate a list or range of addresses from a mmgen
deterministic wallet.
Call as 'btc-keygen' to allow key generation as well.
Call as 'btc-keygen' to allow key generation.
"""
import sys
@ -28,6 +28,7 @@ import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.util import *
from mmgen.crypto import *
from mmgen.addr import *
from mmgen.tx import make_addr_data_chksum
@ -149,7 +150,7 @@ seed_id = make_chksum_8(seed)
for l in (
('flat_list', 'no_addresses'),
('flat_list', 'b16'),
): check_incompatible_opts(opts,l)
): warn_incompatible_opts(opts,l)
opts['gen_what'] = \
("addrs") if what == "addresses" else (
@ -163,6 +164,17 @@ addr_data_str = format_addr_data(
outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs))
if 'flat_list' in opts:
confirm = False if g.quiet else True
outfile = "%s.%s" % (outfile_base,g.keylist_ext)
if (user_confirm("Encrypt key list?")):
enc_data = mmgen_encrypt(addr_data_str,"",opts)
outfile += "."+g.mmenc_ext
write_to_file(outfile,enc_data,opts,"encrypted key list",confirm,True)
else:
write_to_file(outfile,addr_data_str,opts,"key list",confirm,True)
sys.exit()
# Output data:
if 'stdout' in opts:
confirm = True if (what == "keys" and not g.quiet) else False

View file

@ -56,7 +56,7 @@ else:
if 'addrlist' in opts:
lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
remove_comments=True)
trim_comments=True)
addr_data += [(None,l) for l in lines]
from mmgen.bitcoin import verify_addr

View file

@ -23,6 +23,7 @@ mmgen-passchg: Change a mmgen deterministic wallet's passphrase, label or
import sys
from mmgen.Opts import *
from mmgen.util import *
from mmgen.crypto import *
import mmgen.config as g
help_data = {

View file

@ -86,8 +86,8 @@ help_data = {
}
opts,cmd_args = parse_opts(sys.argv,help_data)
from mmgen.Opts import check_incompatible_opts
check_incompatible_opts(opts,('json','keys','addrs','keysforaddrs'))
from mmgen.Opts import warn_incompatible_opts
warn_incompatible_opts(opts,('json','keys','addrs','keysforaddrs'))
if len(cmd_args) == 1:
from mmgen.util import check_infile
@ -1653,7 +1653,7 @@ elif 'addrs' in opts:
elif 'keysforaddrs' in opts:
from mmgen.util import get_lines_from_file
usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",remove_comments=True))
usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",trim_comments=True))
data = [i['sec'] for i in json_db['keys'] if i['addr'] in usr_addrs]
ext,what = "keys","private keys"
if len(data) < len(usr_addrs):

View file

@ -92,7 +92,7 @@ opts,infiles = parse_opts(sys.argv,help_data)
for l in (
('tx_id', 'info'),
('keys_from_file','all_keys_from_file')
): check_incompatible_opts(opts,l)
): warn_incompatible_opts(opts,l)
if "quiet" in opts: g.quiet = True
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
@ -114,8 +114,18 @@ infiles = list(set(infiles) - set(tx_files) - set(addrfiles))
if not "info" in opts: do_license_msg(immed=True)
if 'keys_from_file' in opts:
keys_from_file = get_lines_from_file(opts['keys_from_file'],"key data",
remove_comments=True)
from mmgen.crypto import mmgen_decrypt
fn = opts['keys_from_file']
if get_extension(fn) == g.mmenc_ext:
enc_d = get_data_from_file(fn,"encrypted keylist")
dec_d = mmgen_decrypt(enc_d,"",opts)
if dec_d:
keys_from_file = remove_comments(dec_d.split("\n"))
else:
msg("Decryption of encrypted keylist failed")
sys.exit(2)
else:
keys_from_file = get_lines_from_file(fn,"key data",trim_comments=True)
else: keys_from_file = []
for tx_file in tx_files:

View file

@ -24,6 +24,7 @@ import sys
import mmgen.config as g
from mmgen.Opts import *
from mmgen.util import *
from mmgen.crypto import get_seed_from_wallet,wallet_to_incog_data
help_data = {
'prog_name': g.prog_name,

View file

@ -26,6 +26,7 @@ import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.util import *
from mmgen.crypto import *
help_data = {
'prog_name': g.prog_name,

View file

@ -29,7 +29,7 @@ def print_version_info():
Copyright (C) {g.Cdates} by {g.author} {g.email}.
""".format(g=g).strip()
def check_incompatible_opts(opts,incompat_list):
def warn_incompatible_opts(opts,incompat_list):
bad = [k for k in opts.keys() if k in incompat_list]
if len(bad) > 1:
msg("Mutually exclusive options: %s" % " ".join(
@ -55,7 +55,7 @@ def parse_opts(argv,help_data):
('export_incog','export_incog_hex','export_incog_hidden','export_mnemonic',
'export_seed'),
('quiet','verbose')
): check_incompatible_opts(opts,l)
): warn_incompatible_opts(opts,l)
# check_opts() doesn't touch opts[]
if not check_opts(opts,long_opts): sys.exit(1)

394
mmgen/crypto.py Executable file
View file

@ -0,0 +1,394 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 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/>.
"""
crypto.py: Cryptographic and related routines for the mmgen-tool utility
"""
import sys
from binascii import hexlify
from hashlib import sha256
import mmgen.config as g
from mmgen.util import *
from mmgen.term import get_char
def encrypt_seed(seed, key):
return encrypt_data(seed, key, iv=1, what="seed")
def decrypt_seed(enc_seed, key, seed_id, key_id):
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
dec_seed = decrypt_data(enc_seed, key, iv=1, what="seed")
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...")
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
# else:
# qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1))
if g.debug: print "Decrypted seed: %s" % hexlify(dec_seed)
return dec_seed
def encrypt_data(data, key, iv=1, what="data", verify=True):
"""
Encrypt arbitrary data using AES256 in counter mode
"""
# 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR
from Crypto.Cipher import AES
from Crypto.Util import Counter
vmsg("Encrypting %s" % what)
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
enc_data = c.encrypt(data)
if verify:
vmsg_r("Performing a test decryption of the %s..." % what)
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
dec_data = c.decrypt(enc_data)
if dec_data == data: vmsg("done\n")
else:
msg("ERROR.\nDecrypted %s doesn't match original %s" % (what,what))
sys.exit(2)
return enc_data
def decrypt_data(enc_data, key, iv=1, what="data"):
vmsg("Decrypting %s with key..." % what)
from Crypto.Cipher import AES
from Crypto.Util import Counter
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
return c.decrypt(enc_data)
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
return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen)
def make_key(passwd, salt, hash_preset, what="key"):
vmsg_r("Generating %s. Please wait..." % what)
key = scrypt_hash_passphrase(passwd, salt, hash_preset)
vmsg("done")
if g.debug: print "Key: %s" % hexlify(key)
return key
def get_random_data_from_user(uchars):
if g.quiet: msg("Enter %s random symbols" % uchars)
else: msg(cmessages['usr_rand_notice'] % uchars)
prompt = "You may begin typing. %s symbols left: "
msg_r(prompt % uchars)
import time
# time.clock() always returns zero, so we'll use time.time()
saved_time = time.time()
key_data,time_data = "",[]
for i in range(uchars):
key_data += get_char(immed_chars="ALL")
msg_r("\r" + prompt % (uchars - i - 1))
now = time.time()
time_data.append(now - saved_time)
saved_time = now
if g.quiet: msg_r("\r")
else: msg_r("\rThank you. That's enough.%s\n\n" % (" "*18))
fmt_time_data = ["{:.22f}".format(i) for i in time_data]
if g.debug:
msg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" %
(key_data,"\n".join(fmt_time_data)))
prompt = "User random data successfully acquired. Press ENTER to continue"
prompt_and_get_char(prompt,"",enter_ok=True)
return key_data+"".join(fmt_time_data)
def get_random(length,opts):
from Crypto import Random
os_rand = Random.new().read(length)
if 'usr_randchars' in opts and opts['usr_randchars'] not in (0,-1):
kwhat = "a key from random data with "
if not g.user_entropy:
g.user_entropy = sha256(
get_random_data_from_user(opts['usr_randchars'])).digest()
kwhat += "user entropy"
else:
kwhat += "saved user entropy"
key = make_key(g.user_entropy, "", '2', what=kwhat)
return encrypt_data(os_rand,key,what="random data",verify=False)
else:
return os_rand
def get_seed_from_wallet(
infile,
opts,
prompt="Enter {} wallet passphrase: ".format(g.proj_name),
silent=False
):
wdata = get_data_from_wallet(infile,silent=silent)
label,metadata,hash_preset,salt,enc_seed = wdata
if g.verbose: display_control_data(*wdata)
passwd = get_mmgen_passphrase(prompt,opts)
key = make_key(passwd, salt, hash_preset)
return decrypt_seed(enc_seed, key, metadata[0], metadata[1])
def get_seed_from_incog_wallet(
infile,
opts,
prompt="Enter {} wallet passphrase: ".format(g.proj_name),
silent=False,
hex_input=False
):
what = "incognito wallet data"
if "from_incog_hidden" in opts:
d = get_hidden_incog_data(opts)
else:
d = get_data_from_file(infile,what)
if hex_input:
try:
d = unhexlify("".join(d.split()).strip())
except:
msg("Data in file '%s' is not in hexadecimal format" % infile)
sys.exit(2)
# 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:]
msg("Incog ID: %s (IV ID: %s)" % (make_iv_chksum(iv),make_chksum_8(iv)))
qmsg("Check the applicable value against your records.")
vmsg(cmessages['incog_iv_id_hidden' if "from_incog_hidden" in opts
else 'incog_iv_id'])
passwd = get_mmgen_passphrase(prompt,opts)
qmsg("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, g.hash_preset))
if not hp:
hp = g.hash_preset; break
elif hp in g.hash_presets:
break
msg("%s: Invalid hash preset" % hp)
# IV is used BOTH to initialize counter and to salt password!
key = make_key(passwd, iv, hp, "wrapper key")
d = decrypt_data(enc_incog_data, key, int(hexlify(iv),16), "incog data")
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['incog_key_id_hidden' if "from_incog_hidden" in opts
else 'incog_key_id'])
return seed
def wallet_to_incog_data(infile,opts):
d = get_data_from_wallet(infile,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)
iv = get_random(g.aesctr_iv_len,opts)
iv_id = make_iv_chksum(iv)
msg("Incog ID: %s" % iv_id)
# IV is used BOTH to initialize counter and to salt password!
key = make_key(passwd, iv, preset, "wrapper key")
m = "incog data"
wrap_enc = encrypt_data(salt + enc_seed, key, int(hexlify(iv),16), m)
return iv+wrap_enc,seed_id,key_id,iv_id,preset
def get_seed(infile,opts,silent=False):
ext = get_extension(infile)
if ext == g.mn_ext: source = "mnemonic"
elif ext == g.brain_ext: source = "brainwallet"
elif ext == g.seed_ext: source = "seed"
elif ext == g.wallet_ext: source = "wallet"
elif ext == g.incog_ext: source = "incognito wallet"
elif ext == g.incog_hex_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_incog' in opts: source = "incognito wallet"
else:
if infile: msg(
"Invalid file extension for file: %s\nValid extensions: '.%s'" %
(infile, "', '.".join(g.seedfile_exts)))
else: msg("No seed source type specified and no file supplied")
sys.exit(2)
if source == "mnemonic":
prompt = "Enter mnemonic: "
words = get_words(infile,"mnemonic data",prompt,opts)
wl = get_default_wordlist()
from mmgen.mnemonic import get_seed_from_mnemonic
seed = get_seed_from_mnemonic(words,wl)
elif source == "brainwallet":
if 'from_brain' not in opts:
msg("'--from-brain' parameters must be specified for brainwallet file")
sys.exit(2)
prompt = "Enter brainwallet passphrase: "
words = get_words(infile,"brainwallet data",prompt,opts)
seed = _get_seed_from_brain_passphrase(words,opts)
elif source == "seed":
prompt = "Enter seed in %s format: " % g.seed_ext
words = get_words(infile,"seed data",prompt,opts)
seed = get_seed_from_seed_data(words)
elif source == "wallet":
seed = get_seed_from_wallet(infile, opts, silent=silent)
elif source == "incognito wallet":
h = True if ext == g.incog_hex_ext or 'from_incog_hex' in opts else False
seed = get_seed_from_incog_wallet(infile, opts, silent=silent, hex_input=h)
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
def get_seed_retry(infile,opts):
silent = False
while True:
seed = get_seed(infile,opts,silent=silent)
silent = True
if seed: return seed
def _get_seed_from_brain_passphrase(words,opts):
bp = " ".join(words)
if g.debug: print "Sanitized brain passphrase: %s" % bp
seed_len,hash_preset = get_from_brain_opt_params(opts)
if g.debug: print "Brainwallet l = %s, p = %s" % (seed_len,hash_preset)
vmsg_r("Hashing brainwallet data. Please wait...")
# Use buflen arg of scrypt.hash() to get seed of desired length
seed = scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8)
vmsg("Done")
return seed
# Vars for mmgen_*crypt functions only
salt_len,sha256_len,nonce_len = 32,32,32
def mmgen_encrypt(data,hash_preset,opts):
salt,iv,nonce = get_random(salt_len,opts),\
get_random(g.aesctr_iv_len,opts), get_random(nonce_len,opts)
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
qmsg("Using %s hash preset of '%s'" % (m,hp))
passwd = get_new_passphrase("passphrase",{})
key = make_key(passwd, salt, hp)
enc_d = encrypt_data(sha256(nonce+data).digest() + nonce + data, key,
int(hexlify(iv),16))
return salt+iv+enc_d
def mmgen_decrypt(data,hash_preset,opts):
dstart = salt_len + g.aesctr_iv_len
salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:]
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
qmsg("Using %s hash preset of '%s'" % (m,hp))
passwd = get_mmgen_passphrase("Enter passphrase: ",{})
key = make_key(passwd, salt, hp)
dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16))
if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
return dec_d[sha256_len+nonce_len:]
else:
msg("Incorrect passphrase or hash preset")
return False

View file

@ -20,7 +20,8 @@ license.py: Show the license
"""
import sys
from mmgen.util import msg, msg_r, get_char
from mmgen.util import msg, msg_r
from mmgen.term import get_char
import mmgen.config as g
gpl = {
@ -595,7 +596,7 @@ def do_license_msg(immed=False):
while True:
reply = get_char(prompt, immed_chars="wc" if immed else "")
if reply == 'w':
from mmgen.util import do_pager
from mmgen.term import do_pager
do_pager(gpl['conditions'])
elif reply == 'c':
msg(""); break

View file

@ -20,9 +20,7 @@ term.py: Terminal-handling routines for the mmgen suite
"""
import sys, os, struct
def msg(s): sys.stderr.write(s + "\n")
def msg_r(s): sys.stderr.write(s)
from mmgen.util import msg, msg_r
def _kb_hold_protect_unix():
@ -122,7 +120,6 @@ def _get_terminal_size_linux():
def ioctl_GWINSZ(fd):
try:
import fcntl
import termios
cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
return cr
except:
@ -165,18 +162,23 @@ def _get_terminal_size_mswin():
except:
return 80,25
def mswin_dummy_flush(fd,termconst): pass
try:
import tty, termios
from select import select
get_char = _get_keypress_unix
kb_hold_protect = _kb_hold_protect_unix
get_terminal_size = _get_terminal_size_linux
myflush = termios.tcflush
# call: myflush(sys.stdin, termios.TCIOFLUSH)
except:
try:
import msvcrt, time
get_char = _get_keypress_mswin
kb_hold_protect = _kb_hold_protect_mswin
get_terminal_size = _get_terminal_size_mswin
myflush = mswin_dummy_flush
except:
if not sys.platform.startswith("linux") \
and not sys.platform.startswith("win"):
@ -186,5 +188,42 @@ except:
msg("Unable to set terminal mode")
sys.exit(2)
if __name__ == "__main__":
print "columns: {}, rows: {}".format(*get_terminal_size())
def do_pager(text):
pagers = ["less","more"]
shell = False
from os import environ
# Hack for MS Windows command line (i.e. non CygWin) environment
# When 'shell' is true, Windows aborts the calling program if executable
# not found.
# When 'shell' is false, an exception is raised, invoking the fallback
# 'print' instead of the pager.
# We risk assuming that "more" will always be available on a stock
# Windows installation.
if sys.platform.startswith("win") and 'HOME' not in environ:
shell = True
pagers = ["more"]
if 'PAGER' in environ and environ['PAGER'] != pagers[0]:
pagers = [environ['PAGER']] + pagers
for pager in pagers:
end = "" if pager == "less" else "\n(end of text)\n"
try:
from subprocess import Popen, PIPE, STDOUT
p = Popen([pager], stdin=PIPE, shell=shell)
except: pass
else:
try:
p.communicate(text+end+"\n")
except KeyboardInterrupt:
# Has no effect. Why?
if pager != "less":
msg("\n(User interrupt)\n")
finally:
msg_r("\r")
break
else: print text+end

View file

@ -24,6 +24,7 @@ import mmgen.bitcoin as bitcoin
import binascii as ba
import mmgen.config as g
from mmgen.crypto import *
from mmgen.util import *
from mmgen.tx import *
@ -55,7 +56,8 @@ commands = {
"mn_printlist": ['wordlist [str="electrum"]'],
"id8": ['<infile> [str]'],
"id6": ['<infile> [str]'],
"listaddresses": ['minconf [int=1]', 'showempty [bool=False]'],
"str2id6": ['<string (spaces are ignored)> [str]'],
"listaddresses":['minconf [int=1]', 'showempty [bool=False]'],
"getbalance": ['minconf [int=1]'],
"viewtx": ['<MMGen tx file> [str]'],
"check_addrfile": ['<MMGen addr file> [str]'],
@ -119,8 +121,9 @@ command_help = """
{pnm}-specific operations:
check_addrfile - compute checksum and address list for {pnm} address file
find_incog_data - Use an Incog ID to find hidden incognito wallet data
id6 - generate 6-character {pnm} ID checksum for file (or stdin)
id8 - generate 8-character {pnm} ID checksum for file (or stdin)
id6 - generate 6-character {pnm} ID for a file (or stdin)
id8 - generate 8-character {pnm} ID for a file (or stdin)
str2id6 - generate 6-character {pnm} ID for a string, ignoring spaces
Mnemonic operations (choose "electrum" (default), "tirosh" or "all"
wordlists):
@ -301,6 +304,7 @@ def mn_printlist(wordlist="electrum"):
def id8(infile): print make_chksum_8(get_data_from_file(infile,dash=True))
def id6(infile): print make_chksum_6(get_data_from_file(infile,dash=True))
def str2id6(s): print make_chksum_6("".join(s.split()))
# List MMGen addresses and their balances:
def listaddresses(minconf=1,showempty=False):
@ -410,48 +414,31 @@ def wif2hex(wif,compressed=False):
def hex2wif(hexpriv,compressed=False):
print bitcoin.hextowif(hexpriv,compressed)
salt_len,sha256_len,nonce_len = 32,32,32
def encrypt(infile,outfile="",hash_preset=''):
d = get_data_from_file(infile,"data for encryption")
salt,iv,nonce = get_random(salt_len,opts),\
get_random(g.aesctr_iv_len,opts), get_random(nonce_len,opts)
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
qmsg("Using %s hash preset of '%s'" % (m,hp))
passwd = get_new_passphrase("passphrase",{})
key = make_key(passwd, salt, hp)
from hashlib import sha256
enc_d = encrypt_data(sha256(nonce+d).digest() + nonce + d, key,
int(ba.hexlify(iv),16))
if outfile == '-': sys.stdout.write(salt+iv+enc_d)
data = get_data_from_file(infile,"data for encryption")
enc_d = mmgen_encrypt(data,hash_preset,opts)
if outfile == '-':
write_to_stdout(enc_d,"encrypted data",confirm=True)
else:
if not outfile:
outfile = os.path.basename(infile) + "." + g.mmenc_ext
write_to_file(outfile, salt+iv+enc_d, opts,"encrypted data",True,True)
write_to_file(outfile, enc_d, opts,"encrypted data",True,True)
def decrypt(infile,outfile="",hash_preset=''):
d = get_data_from_file(infile,"encrypted data")
dstart = salt_len + g.aesctr_iv_len
salt,iv,enc_d = d[:salt_len],d[salt_len:dstart],d[dstart:]
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
qmsg("Using %s hash preset of '%s'" % (m,hp))
passwd = get_mmgen_passphrase("Enter passphrase: ",{})
key = make_key(passwd, salt, hp)
dec_d = decrypt_data(enc_d, key, int(ba.hexlify(iv),16))
from hashlib import sha256
if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
out = dec_d[sha256_len+nonce_len:]
if outfile == '-': sys.stdout.write(out)
else:
if not outfile:
outfile = os.path.basename(infile)
if outfile[-len(g.mmenc_ext)-1:] == "."+g.mmenc_ext:
outfile = outfile[:-len(g.mmenc_ext)-1]
else:
outfile = outfile + ".dec"
write_to_file(outfile, out, opts,"decrypted data",True,True)
enc_d = get_data_from_file(infile,"encrypted data")
dec_d = mmgen_decrypt(enc_d,hash_preset,opts)
if outfile == '-':
write_to_stdout(dec_d,"decrypted data",confirm=True)
else:
msg("Incorrect passphrase or hash preset")
if not outfile:
outfile = os.path.basename(infile)
if outfile[-len(g.mmenc_ext)-1:] == "."+g.mmenc_ext:
outfile = outfile[:-len(g.mmenc_ext)-1]
else:
outfile = outfile + ".dec"
write_to_file(outfile, dec_d, opts,"decrypted data",True,True)
def find_incog_data(filename,iv_id,keep_searching=False):

View file

@ -25,6 +25,8 @@ from decimal import Decimal
import mmgen.config as g
from mmgen.util import *
from mmgen.crypto import get_seed_retry
from mmgen.term import do_pager,get_char
txmsg = {
'not_enough_btc': "Not enough BTC in the inputs for this transaction (%s BTC)",
@ -538,7 +540,7 @@ def check_addr_data_hash(seed_id,addr_data):
def parse_addrs_file(f):
lines = get_lines_from_file(f,"address data",remove_comments=True)
lines = get_lines_from_file(f,"address data",trim_comments=True)
try:
seed_id,obrace = lines[0].split()

View file

@ -16,7 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
util.py: Shared routines for the mmgen suite
util.py: Low-level routines imported by other modules for the MMGen suite
"""
import sys
@ -24,8 +24,6 @@ from hashlib import sha256
from binascii import hexlify,unhexlify
import mmgen.config as g
from mmgen.bitcoin import b58decode_pad,b58encode_pad
from mmgen.term import *
def msg(s): sys.stderr.write(s + "\n")
def msg_r(s): sys.stderr.write(s)
@ -42,97 +40,6 @@ def vmsg(s):
def vmsg_r(s):
if g.verbose: sys.stderr.write(s)
def bail(): sys.exit(9)
def get_extension(f):
import os
return os.path.splitext(f)[1][1:]
def get_random_data_from_user(uchars):
if g.quiet: msg("Enter %s random symbols" % uchars)
else: msg(cmessages['usr_rand_notice'] % uchars)
prompt = "You may begin typing. %s symbols left: "
msg_r(prompt % uchars)
import time
# time.clock() always returns zero, so we'll use time.time()
saved_time = time.time()
key_data,time_data = "",[]
for i in range(uchars):
key_data += get_char(immed_chars="ALL")
msg_r("\r" + prompt % (uchars - i - 1))
now = time.time()
time_data.append(now - saved_time)
saved_time = now
if g.quiet: msg_r("\r")
else: msg_r("\rThank you. That's enough.%s\n\n" % (" "*18))
fmt_time_data = ["{:.22f}".format(i) for i in time_data]
if g.debug:
msg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" %
(key_data,"\n".join(fmt_time_data)))
prompt = "User random data successfully acquired. Press ENTER to continue"
prompt_and_get_char(prompt,"",enter_ok=True)
return key_data+"".join(fmt_time_data)
def get_random(length,opts):
from Crypto import Random
os_rand = Random.new().read(length)
if 'usr_randchars' in opts and opts['usr_randchars'] not in (0,-1):
kwhat = "a key from random data with "
if not g.user_entropy:
g.user_entropy = sha256(
get_random_data_from_user(opts['usr_randchars'])).digest()
kwhat += "user entropy"
else:
kwhat += "saved user entropy"
key = make_key(g.user_entropy, "", '2', what=kwhat)
return encrypt_data(os_rand,key,what="random data",verify=False)
else:
return os_rand
def my_raw_input(prompt,echo=True):
try:
if echo:
reply = raw_input(prompt)
else:
from getpass import getpass
reply = getpass(prompt)
except KeyboardInterrupt:
msg("\nUser interrupt")
sys.exit(1)
kb_hold_protect()
return reply
def _get_hash_params(hash_preset):
if hash_preset in g.hash_presets:
return g.hash_presets[hash_preset] # N,p,r,buflen
else: # Shouldn't be here
msg("%s: invalid 'hash_preset' value" % hash_preset)
sys.exit(3)
def show_hash_presets():
fs = " {:<7} {:<6} {:<3} {}"
msg("Available parameters for scrypt.hash():")
msg(fs.format("Preset","N","r","p"))
for i in sorted(g.hash_presets.keys()):
msg(fs.format("'%s'" % i, *g.hash_presets[i]))
msg("N = memory usage (power of two), p = iterations (rounds)")
sys.exit(0)
cmessages = {
'null': "",
'incog_iv_id': """
@ -189,71 +96,113 @@ just hit ENTER twice.
""".strip()
}
def confirm_or_exit(message, question, expect="YES"):
vmsg("")
m = message.strip()
if m: msg(m)
conf_msg = "Type uppercase '%s' to confirm: " % expect
p = question+" "+conf_msg if question[0].isupper() else \
"Are you sure you want to %s?\n%s" % (question,conf_msg)
if my_raw_input(p).strip() != expect:
msg("Exiting at user request")
sys.exit(2)
vmsg("")
def user_confirm(prompt,default_yes=False,verbose=False):
q = "(Y/n)" if default_yes else "(y/N)"
while True:
reply = get_char("%s %s: " % (prompt, q)).strip("\n\r")
if not reply:
if default_yes: msg(""); return True
else: msg(""); return False
elif reply in 'yY': msg(""); return True
elif reply in 'nN': msg(""); return False
else:
if verbose: msg("\nInvalid reply")
else: msg_r("\r")
def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
while True:
reply = get_char("%s: " % prompt).strip("\n\r")
if reply in chars or (enter_ok and not reply):
msg("")
return reply
if verbose: msg("\nInvalid reply")
else: msg_r("\r")
def get_extension(f):
import os
return os.path.splitext(f)[1][1:]
def make_chksum_N(s,n,sep=False):
if n%4 or not (4 <= n <= 64): return False
s = sha256(sha256(s).digest()).hexdigest().upper()
sep = " " if sep else ""
return sep.join([s[i*4:i*4+4] for i in range(n/4)])
def make_chksum_8(s,sep=False):
s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
return "{} {}".format(s[:4],s[4:]) if sep else s
def make_chksum_6(s): return sha256(s).hexdigest()[:6]
def make_iv_chksum(s): return sha256(s).hexdigest()[:8].upper()
def make_chksum_6(s):
return sha256(s).hexdigest()[:6]
def splitN(s,n,sep=None): # always return an n-element list
ret = s.split(sep,n-1)
return ret + ["" for i in range(n-len(ret))]
def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list
def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
def make_iv_chksum(s):
return sha256(s).hexdigest()[:8].upper()
def col4(s):
nondiv = 1 if len(s) % 4 else 0
return " ".join([s[4*i:4*i+4] for i in range(len(s)/4 + nondiv)])
def make_timestamp():
import time
tv = time.gmtime(time.time())[:6]
return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv)
def make_timestr():
import time
tv = time.gmtime(time.time())[:6]
return "{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}".format(*tv)
def secs_to_hms(secs):
return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60)
def _is_hex(s):
try: int(s,16)
except: return False
else: return True
def match_ext(addr,ext):
return addr.split(".")[-1] == ext
def get_from_brain_opt_params(opts):
l,p = opts['from_brain'].split(",")
return(int(l),p)
def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
r = 1 if len(data) % gw else 0
return "".join(
[
("" if (line_nums == False or i % cols) else "%03i: " % (i/cols)) +
hexlify(data[i*gw:i*gw+gw]) +
(" " if (i+1) % cols else "\n")
for i in range(len(data)/gw + r)
]
).rstrip()
def decode_pretty_hexdump(data):
import re
lines = [re.sub('^\d+:\s+','',l) for l in data.split("\n")]
return unhexlify("".join(("".join(lines).split())))
def get_hash_params(hash_preset):
if hash_preset in g.hash_presets:
return g.hash_presets[hash_preset] # N,p,r,buflen
else: # Shouldn't be here
msg("%s: invalid 'hash_preset' value" % hash_preset)
sys.exit(3)
def show_hash_presets():
fs = " {:<7} {:<6} {:<3} {}"
msg("Available parameters for scrypt.hash():")
msg(fs.format("Preset","N","r","p"))
for i in sorted(g.hash_presets.keys()):
msg(fs.format("'%s'" % i, *g.hash_presets[i]))
msg("N = memory usage (power of two), p = iterations (rounds)")
sys.exit(0)
def compare_checksums(chksum1, desc1, chksum2, desc2):
if chksum1.lower() == chksum2.lower():
vmsg("OK (%s)" % chksum1.upper())
return True
else:
if g.debug:
print \
"ERROR!\nComputed checksum %s (%s) doesn't match checksum %s (%s)" \
% (desc1,chksum1,desc2,chksum2)
return False
def get_default_wordlist():
wl_id = g.default_wl
if wl_id == "electrum": from mmgen.mn_electrum import electrum_words as wl
elif wl_id == "tirosh": from mmgen.mn_tirosh import tirosh_words as wl
return wl.strip().split("\n")
def open_file_or_exit(filename,mode):
try:
f = open(filename, mode)
except:
what = "reading" if mode == 'r' else "writing"
msg("Unable to open file '%s' for %s" % (filename,what))
sys.exit(2)
return f
def check_file_type_and_access(fname,ftype):
@ -287,6 +236,7 @@ def check_infile(f): return check_file_type_and_access(f,"input file")
def check_outfile(f): return check_file_type_and_access(f,"output file")
def check_outdir(f): return check_file_type_and_access(f,"directory")
def _validate_addr_num(n):
try: n = int(n)
@ -301,6 +251,12 @@ def _validate_addr_num(n):
return n
def make_full_path(outdir,outfile):
import os
return os.path.normpath(os.sep.join([outdir, os.path.basename(outfile)]))
# os.path.join() doesn't work?
def parse_address_list(arg,sep=","):
ret = []
@ -354,65 +310,23 @@ def get_new_passphrase(what, opts):
return pw
def _scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
def confirm_or_exit(message, question, expect="YES"):
# Buflen arg is for brainwallets only, which use this function to generate
# the seed directly.
vmsg("")
N,r,p = _get_hash_params(hash_preset)
m = message.strip()
if m: msg(m)
import scrypt
return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen)
conf_msg = "Type uppercase '%s' to confirm: " % expect
p = question+" "+conf_msg if question[0].isupper() else \
"Are you sure you want to %s?\n%s" % (question,conf_msg)
def get_from_brain_opt_params(opts):
l,p = opts['from_brain'].split(",")
return(int(l),p)
if my_raw_input(p).strip() != expect:
msg("Exiting at user request")
sys.exit(2)
def _get_seed_from_brain_passphrase(words,opts):
bp = " ".join(words)
if g.debug: print "Sanitized brain passphrase: %s" % bp
seed_len,hash_preset = get_from_brain_opt_params(opts)
if g.debug: print "Brainwallet l = %s, p = %s" % (seed_len,hash_preset)
vmsg_r("Hashing brainwallet data. Please wait...")
# Use buflen arg of scrypt.hash() to get seed of desired length
seed = _scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8)
vmsg("Done")
return seed
def encrypt_seed(seed, key):
return encrypt_data(seed, key, iv=1, what="seed")
def encrypt_data(data, key, iv=1, what="data", verify=True):
"""
Encrypt arbitrary data using AES256 in counter mode
"""
# 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR
from Crypto.Cipher import AES
from Crypto.Util import Counter
vmsg("Encrypting %s" % what)
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
enc_data = c.encrypt(data)
if verify:
vmsg_r("Performing a test decryption of the %s..." % what)
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
dec_data = c.decrypt(enc_data)
if dec_data == data: vmsg("done\n")
else:
msg("ERROR.\nDecrypted %s doesn't match original %s" % (what,what))
sys.exit(2)
return enc_data
vmsg("")
def write_to_stdout(data, what, confirm=True):
@ -422,36 +336,12 @@ def write_to_stdout(data, what, confirm=True):
try:
import os
of = os.readlink("/proc/%d/fd/1" % os.getpid())
msg("Redirecting output to file '%s'" % of)
msg("Redirecting output to file '%s'" % os.path.relpath(of))
except:
msg("Redirecting output to file")
sys.stdout.write(data)
def get_default_wordlist():
wl_id = g.default_wl
if wl_id == "electrum": from mmgen.mn_electrum import electrum_words as wl
elif wl_id == "tirosh": from mmgen.mn_tirosh import tirosh_words as wl
return wl.strip().split("\n")
def open_file_or_exit(filename,mode):
try:
f = open(filename, mode)
except:
what = "reading" if mode == 'r' else "writing"
msg("Unable to open file '%s' for %s" % (filename,what))
sys.exit(2)
return f
def make_full_path(outdir,outfile):
import os
return os.path.normpath(os.sep.join([outdir, os.path.basename(outfile)]))
# os.path.join() doesn't work?
def write_to_file(outfile,data,opts,what="data",confirm=False,verbose=False):
if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
@ -487,11 +377,12 @@ def export_to_file(outfile, data, opts, what="data"):
write_to_file(outfile,data,opts,what,c,True)
def _display_control_data(label,metadata,hash_preset,salt,enc_seed):
from mmgen.bitcoin import b58decode_pad,b58encode_pad
def display_control_data(label,metadata,hash_preset,salt,enc_seed):
msg("WALLET DATA")
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].upper()),
@ -499,7 +390,7 @@ def _display_control_data(label,metadata,hash_preset,salt,enc_seed):
("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)]))),
" ".join([str(i) for i in get_hash_params(hash_preset)]))),
("Passphrase empty?", pw_empty.capitalize()),
("Timestamp:", "%s UTC" % metadata[4]),
): msg(fs.format(*i))
@ -515,31 +406,6 @@ def _display_control_data(label,metadata,hash_preset,salt,enc_seed):
): msg(fs.format(*i))
def splitN(s,n,sep=None): # always return an n-element list
ret = s.split(sep,n-1)
return ret + ["" for i in range(n-len(ret))]
def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list
def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
def col4(s):
nondiv = 1 if len(s) % 4 else 0
return " ".join([s[4*i:4*i+4] for i in range(len(s)/4 + nondiv)])
def make_timestamp():
import time
tv = time.gmtime(time.time())[:6]
return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv)
def make_timestr():
import time
tv = time.gmtime(time.time())[:6]
return "{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}".format(*tv)
def secs_to_hms(secs):
return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60)
def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
seed_id = make_chksum_8(seed)
@ -555,7 +421,7 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
lines = (
label,
"{} {} {} {} {}".format(*metadata),
"{}: {} {} {}".format(hash_preset,*_get_hash_params(hash_preset)),
"{}: {} {} {}".format(hash_preset,*get_hash_params(hash_preset)),
"{} {}".format(make_chksum_6(sf), col4(sf)),
"{} {}".format(make_chksum_6(esf), col4(esf))
)
@ -569,29 +435,9 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
write_to_file(outfile,d,opts,"wallet",c,True)
if g.verbose:
_display_control_data(label,metadata,hash_preset,salt,enc_seed)
display_control_data(label,metadata,hash_preset,salt,enc_seed)
def _compare_checksums(chksum1, desc1, chksum2, desc2):
if chksum1.lower() == chksum2.lower():
vmsg("OK (%s)" % chksum1.upper())
return True
else:
if g.debug:
print \
"ERROR!\nComputed checksum %s (%s) doesn't match checksum %s (%s)" \
% (desc1,chksum1,desc2,chksum2)
return False
def _is_hex(s):
try: int(s,16)
except: return False
else: return True
def match_ext(addr,ext):
return addr.split(".")[-1] == ext
def _check_mmseed_format(words):
valid = False
@ -664,7 +510,7 @@ def get_data_from_wallet(infile,silent=False):
hash_preset = hd[0][:-1]
hash_params = [int(i) for i in hd[1:]]
if hash_params != _get_hash_params(hash_preset):
if hash_params != get_hash_params(hash_preset):
msg("Hash parameters '%s' don't match hash preset '%s'" %
(" ".join(hash_params), hash_preset))
sys.exit(9)
@ -702,22 +548,29 @@ def _get_words_from_file(infile,what):
return words
def get_lines_from_file(infile,what="",remove_comments=False):
def get_words(infile,what,prompt,opts):
if infile:
return _get_words_from_file(infile,what)
else:
return _get_words_from_user(prompt,opts)
def remove_comments(lines):
import re
# re.sub(pattern, repl, string, count=0, flags=0)
ret = []
for i in lines:
i = re.sub('#.*','',i,1)
i = re.sub('\s+$','',i)
if i: ret.append(i)
return ret
def get_lines_from_file(infile,what="",trim_comments=False):
if what != "":
qmsg("Getting %s from file '%s'" % (what,infile))
f = open_file_or_exit(infile,'r')
lines = f.read().splitlines(); f.close()
if remove_comments:
import re
# re.sub(pattern, repl, string, count=0, flags=0)
ret = []
for i in lines:
i = re.sub('#.*','',i,1)
i = re.sub('\s+$','',i)
if i: ret.append(i)
return ret
else:
return lines
lines = f.read().splitlines()
f.close()
return remove_comments(lines) if trim_comments else lines
def get_data_from_file(infile,what="data",dash=False):
@ -729,7 +582,7 @@ def get_data_from_file(infile,what="data",dash=False):
return data
def _get_seed_from_seed_data(words):
def get_seed_from_seed_data(words):
if not _check_mmseed_format(words):
msg("Invalid %s data" % g.seed_ext)
@ -741,7 +594,7 @@ def _get_seed_from_seed_data(words):
chk = make_chksum_6(seed_b58)
vmsg_r("Validating %s checksum..." % g.seed_ext)
if _compare_checksums(chk, "from seed", stored_chk, "from input"):
if compare_checksums(chk, "from seed", stored_chk, "from input"):
seed = b58decode_pad(seed_b58)
if seed == False:
msg("Invalid b58 number: %s" % val)
@ -782,25 +635,6 @@ def get_bitcoind_passphrase(prompt,opts):
echo=True if 'echo_passphrase' in opts else False)
def get_seed_from_wallet(
infile,
opts,
prompt="Enter {} wallet passphrase: ".format(g.proj_name),
silent=False
):
wdata = get_data_from_wallet(infile,silent=silent)
label,metadata,hash_preset,salt,enc_seed = wdata
if g.verbose: _display_control_data(*wdata)
passwd = get_mmgen_passphrase(prompt,opts)
key = make_key(passwd, salt, hash_preset)
return decrypt_seed(enc_seed, key, metadata[0], metadata[1])
def check_data_fits_file_at_offset(fname,offset,dlen,action):
# TODO: Check for Windows
import os, stat
@ -835,236 +669,6 @@ def get_hidden_incog_data(opts):
"Data read from file")
return data
def get_seed_from_incog_wallet(
infile,
opts,
prompt="Enter {} wallet passphrase: ".format(g.proj_name),
silent=False,
hex_input=False
):
what = "incognito wallet data"
if "from_incog_hidden" in opts:
d = get_hidden_incog_data(opts)
else:
d = get_data_from_file(infile,what)
if hex_input:
try:
d = unhexlify("".join(d.split()).strip())
except:
msg("Data in file '%s' is not in hexadecimal format" % infile)
sys.exit(2)
# 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:]
msg("Incog ID: %s (IV ID: %s)" % (make_iv_chksum(iv),make_chksum_8(iv)))
qmsg("Check the applicable value against your records.")
vmsg(cmessages['incog_iv_id_hidden' if "from_incog_hidden" in opts
else 'incog_iv_id'])
passwd = get_mmgen_passphrase(prompt,opts)
qmsg("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, g.hash_preset))
if not hp:
hp = g.hash_preset; break
elif hp in g.hash_presets:
break
msg("%s: Invalid hash preset" % hp)
# IV is used BOTH to initialize counter and to salt password!
key = make_key(passwd, iv, hp, "wrapper key")
d = decrypt_data(enc_incog_data, key, int(hexlify(iv),16), "incog data")
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['incog_key_id_hidden' if "from_incog_hidden" in opts
else 'incog_key_id'])
return seed
def make_key(passwd, salt, hash_preset, what="key"):
vmsg_r("Generating %s. 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):
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
dec_seed = decrypt_data(enc_seed, key, iv=1, what="seed")
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...")
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
# else:
# qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1))
if g.debug: print "Decrypted seed: %s" % hexlify(dec_seed)
return dec_seed
def decrypt_data(enc_data, key, iv=1, what="data"):
vmsg("Decrypting %s with key..." % what)
from Crypto.Cipher import AES
from Crypto.Util import Counter
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
return c.decrypt(enc_data)
def _get_words(infile,what,prompt,opts):
if infile:
return _get_words_from_file(infile,what)
else:
return _get_words_from_user(prompt,opts)
def get_seed(infile,opts,silent=False):
ext = get_extension(infile)
if ext == g.mn_ext: source = "mnemonic"
elif ext == g.brain_ext: source = "brainwallet"
elif ext == g.seed_ext: source = "seed"
elif ext == g.wallet_ext: source = "wallet"
elif ext == g.incog_ext: source = "incognito wallet"
elif ext == g.incog_hex_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_incog' in opts: source = "incognito wallet"
else:
if infile: msg(
"Invalid file extension for file: %s\nValid extensions: '.%s'" %
(infile, "', '.".join(g.seedfile_exts)))
else: msg("No seed source type specified and no file supplied")
sys.exit(2)
if source == "mnemonic":
prompt = "Enter mnemonic: "
words = _get_words(infile,"mnemonic data",prompt,opts)
wl = get_default_wordlist()
from mmgen.mnemonic import get_seed_from_mnemonic
seed = get_seed_from_mnemonic(words,wl)
elif source == "brainwallet":
if 'from_brain' not in opts:
msg("'--from-brain' parameters must be specified for brainwallet file")
sys.exit(2)
prompt = "Enter brainwallet passphrase: "
words = _get_words(infile,"brainwallet data",prompt,opts)
seed = _get_seed_from_brain_passphrase(words,opts)
elif source == "seed":
prompt = "Enter seed in %s format: " % g.seed_ext
words = _get_words(infile,"seed data",prompt,opts)
seed = _get_seed_from_seed_data(words)
elif source == "wallet":
seed = get_seed_from_wallet(infile, opts, silent=silent)
elif source == "incognito wallet":
h = True if ext == g.incog_hex_ext or 'from_incog_hex' in opts else False
seed = get_seed_from_incog_wallet(infile, opts, silent=silent, hex_input=h)
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
def get_seed_retry(infile,opts):
silent = False
while True:
seed = get_seed(infile,opts,silent=silent)
silent = True
if seed: return seed
def do_pager(text):
pagers = ["less","more"]
shell = False
from os import environ
# Hack for MS Windows command line (i.e. non CygWin) environment
# When 'shell' is true, Windows aborts the calling program if executable
# not found.
# When 'shell' is false, an exception is raised, invoking the fallback
# 'print' instead of the pager.
# We risk assuming that "more" will always be available on a stock
# Windows installation.
if sys.platform.startswith("win") and 'HOME' not in environ:
shell = True
pagers = ["more"]
if 'PAGER' in environ and environ['PAGER'] != pagers[0]:
pagers = [environ['PAGER']] + pagers
for pager in pagers:
end = "" if pager == "less" else "\n(end of text)\n"
try:
from subprocess import Popen, PIPE, STDOUT
p = Popen([pager], stdin=PIPE, shell=shell)
except: pass
else:
try:
p.communicate(text+end+"\n")
except KeyboardInterrupt:
# Has no effect. Why?
if pager != "less":
msg("\n(User interrupt)\n")
finally:
msg_r("\r")
break
else: print text+end
def export_to_hidden_incog(incog_enc,opts):
outfile,offset = opts['export_incog_hidden'].split(",") #Already sanity-checked
@ -1081,46 +685,50 @@ def export_to_hidden_incog(incog_enc,opts):
(os.path.relpath(outfile),offset))
def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
r = 1 if len(data) % gw else 0
return "".join(
[
("" if (line_nums == False or i % cols) else "%03i: " % (i/cols)) +
hexlify(data[i*gw:i*gw+gw]) +
(" " if (i+1) % cols else "\n")
for i in range(len(data)/gw + r)
]
).rstrip()
from mmgen.term import kb_hold_protect,get_char
def decode_pretty_hexdump(data):
import re
lines = [re.sub('^\d+:\s+','',l) for l in data.split("\n")]
return unhexlify("".join(("".join(lines).split())))
def my_raw_input(prompt,echo=True):
try:
if echo:
reply = raw_input(prompt)
else:
from getpass import getpass
reply = getpass(prompt)
except KeyboardInterrupt:
msg("\nUser interrupt")
sys.exit(1)
kb_hold_protect()
return reply
def wallet_to_incog_data(infile,opts):
def user_confirm(prompt,default_yes=False,verbose=False):
d = get_data_from_wallet(infile,silent=True)
seed_id,key_id,preset,salt,enc_seed = \
d[1][0], d[1][1], d[2].split(":")[0], d[3], d[4]
q = "(Y/n)" if default_yes else "(y/N)"
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)
while True:
reply = get_char("%s %s: " % (prompt, q)).strip("\n\r")
iv = get_random(g.aesctr_iv_len,opts)
iv_id = make_iv_chksum(iv)
msg("Incog ID: %s" % iv_id)
# IV is used BOTH to initialize counter and to salt password!
key = make_key(passwd, iv, preset, "wrapper key")
m = "incog data"
wrap_enc = encrypt_data(salt + enc_seed, key, int(hexlify(iv),16), m)
return iv+wrap_enc,seed_id,key_id,iv_id,preset
if not reply:
if default_yes: msg(""); return True
else: msg(""); return False
elif reply in 'yY': msg(""); return True
elif reply in 'nN': msg(""); return False
else:
if verbose: msg("\nInvalid reply")
else: msg_r("\r")
def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
while True:
reply = get_char("%s: " % prompt).strip("\n\r")
if reply in chars or (enter_ok and not reply):
msg("")
return reply
if verbose: msg("\nInvalid reply")
else: msg_r("\r")
if __name__ == "__main__":
print "util.py"