Browse Source

Added features:
* 'mmgen-tool': file encryption utility with strong encryption
* 'mmgen-tool': find hidden incognito data in file using the Incog ID
* User may now supply additional entropy in all cases where random data is
needed. This user entropy (typed symbols + keystroke intervals) is hashed
into a key with Scrypt and used to encrypt all random data produced during
the session by the OS.

philemon 10 years ago
parent
commit
e328a6a24b
9 changed files with 204 additions and 88 deletions
  1. 1 0
      MANIFEST
  2. 2 1
      mmgen-addrgen
  3. 3 5
      mmgen-addrimport
  4. 1 1
      mmgen-tool
  5. 11 12
      mmgen/Opts.py
  6. 144 36
      mmgen/tool.py
  7. 7 8
      mmgen/tx.py
  8. 34 24
      mmgen/util.py
  9. 1 1
      setup.py

+ 1 - 0
MANIFEST

@@ -2,6 +2,7 @@
 __init__.py
 __init__.py
 mmgen-addrgen
 mmgen-addrgen
 mmgen-addrimport
 mmgen-addrimport
+mmgen-keygen
 mmgen-passchg
 mmgen-passchg
 mmgen-pywallet
 mmgen-pywallet
 mmgen-tool
 mmgen-tool

+ 2 - 1
mmgen-addrgen

@@ -182,4 +182,5 @@ if not 'no_addresses' in opts:
 		a = "address data checksum"
 		a = "address data checksum"
 		write_to_file(outfile_base+".chk",addr_data_chksum,opts,a,confirm,True)
 		write_to_file(outfile_base+".chk",addr_data_chksum,opts,a,confirm,True)
 	else:
 	else:
-		qmsg("Save this information to a secure location")
+		qmsg("This checksum will be used to verify the address file in the future.")
+		qmsg("Record it to a safe location.")

+ 3 - 5
mmgen-addrimport

@@ -83,6 +83,7 @@ and has a balance, you must exit the program now and rerun it using the
 '--rescan' option.  Otherwise you may ignore this message and continue.
 '--rescan' option.  Otherwise you may ignore this message and continue.
 """.strip()
 """.strip()
 
 
+if g.quiet: m = ""
 confirm_or_exit(m, "continue", expect="YES")
 confirm_or_exit(m, "continue", expect="YES")
 
 
 err_flag = False
 err_flag = False
@@ -135,10 +136,7 @@ for n,i in enumerate(addr_data):
 				break
 				break
 	else:
 	else:
 		import_address(i[1],label,rescan=False)
 		import_address(i[1],label,rescan=False)
-		msg_r(msg_fmt % (
-				("%s/%s:" % (n+1,len(addr_data))),
-				i[1], "(" + label + ")"
-			)
-		)
+		msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_data))),
+							i[1], "(" + label + ")"))
 		if err_flag: msg("\nImport failed"); sys.exit(2)
 		if err_flag: msg("\nImport failed"); sys.exit(2)
 		msg(" - OK")
 		msg(" - OK")

+ 1 - 1
mmgen-tool

@@ -30,7 +30,7 @@ from mmgen.util import pretty_hexdump
 help_data = {
 help_data = {
 	'prog_name': g.prog_name,
 	'prog_name': g.prog_name,
 	'desc':    "Perform various BTC-related operations",
 	'desc':    "Perform various BTC-related operations",
-	'usage':   "[opts] <command> <args>",
+	'usage':   "[opts] <command> <command args>",
 	'options': """
 	'options': """
 -d, --outdir=       d Specify an alternate directory 'd' for output
 -d, --outdir=       d Specify an alternate directory 'd' for output
 -h, --help            Print this help message
 -h, --help            Print this help message

+ 11 - 12
mmgen/Opts.py

@@ -23,16 +23,17 @@ def usage(hd):
 	print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
 	print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
 	sys.exit(2)
 	sys.exit(2)
 
 
-def print_version_info(progname):
+def print_version_info(): # MMGen only
 	print """
 	print """
-'{}' version {g.version}.  Part of the {g.proj_name} suite.
+'{g.prog_name}' version {g.version}.  Part of the {g.proj_name} suite.
 Copyright (C) {g.Cdates} by {g.author} {g.email}.
 Copyright (C) {g.Cdates} by {g.author} {g.email}.
-""".format(progname, g=g).strip()
+""".format(g=g).strip()
 
 
-def print_help(progname,help_data):
-	pn_len = str(len(progname)+2)
-	print ("  %-"+pn_len+"s %s") % (progname.upper()+":", help_data['desc'].strip())
-	print ("  %-"+pn_len+"s %s %s")%("USAGE:", progname, help_data['usage'].strip())
+def print_help(help_data):
+	pn = help_data['prog_name']
+	pn_len = str(len(pn)+2)
+	print ("  %-"+pn_len+"s %s") % (pn.upper()+":", help_data['desc'].strip())
+	print ("  %-"+pn_len+"s %s %s")%("USAGE:", pn, help_data['usage'].strip())
 	sep = "\n    "
 	sep = "\n    "
 	print "  OPTIONS:"+sep+"%s" % sep.join(help_data['options'].strip().split("\n"))
 	print "  OPTIONS:"+sep+"%s" % sep.join(help_data['options'].strip().split("\n"))
 	if "notes" in help_data:
 	if "notes" in help_data:
@@ -41,10 +42,8 @@ def print_help(progname,help_data):
 
 
 def process_opts(argv,help_data,short_opts,long_opts):
 def process_opts(argv,help_data,short_opts,long_opts):
 
 
-	progname = argv[0].split("/")[-1]
-
 	if len(argv) == 2 and argv[1] == '--version': # MMGen only!
 	if len(argv) == 2 and argv[1] == '--version': # MMGen only!
-		print_version_info(progname); sys.exit()
+		print_version_info(); sys.exit()
 
 
 	if g.debug:
 	if g.debug:
 		print "Short opts: %s" % repr(short_opts)
 		print "Short opts: %s" % repr(short_opts)
@@ -63,7 +62,7 @@ def process_opts(argv,help_data,short_opts,long_opts):
 		else:        short_opts_l     += i
 		else:        short_opts_l     += i
 
 
 	for opt, arg in cl_opts:
 	for opt, arg in cl_opts:
-		if   opt in ("-h","--help"): print_help(progname,help_data); sys.exit()
+		if   opt in ("-h","--help"): print_help(help_data); sys.exit()
 		elif opt[:2] == "--" and opt[2:] in long_opts:
 		elif opt[:2] == "--" and opt[2:] in long_opts:
 			opts[opt[2:].replace("-","_")] = True
 			opts[opt[2:].replace("-","_")] = True
 		elif opt[:2] == "--" and opt[2:]+"=" in long_opts:
 		elif opt[:2] == "--" and opt[2:]+"=" in long_opts:
@@ -143,7 +142,7 @@ def check_opts(opts,long_opts):
 					msg("Requested %s '%s' is unwritable by you" % (what,val))
 					msg("Requested %s '%s' is unwritable by you" % (what,val))
 					return False
 					return False
 			else:
 			else:
-				msg("Requested %s '%s' doen not exist" % (what,val))
+				msg("Requested %s '%s' does not exist" % (what,val))
 				return False
 				return False
 
 
 		elif opt == 'label':
 		elif opt == 'label':

+ 144 - 36
mmgen/tool.py

@@ -40,7 +40,7 @@ commands = {
 	"hextob58":     ['<hex number> [str]'],
 	"hextob58":     ['<hex number> [str]'],
 	"b58tohex":     ['<b58 number> [str]'],
 	"b58tohex":     ['<b58 number> [str]'],
 	"b58randenc":   [],
 	"b58randenc":   [],
-	"getrand":      ['bytes [int=32]'],
+	"randhex":      ['nbytes [int=32]'],
 	"randwif":      ['compressed [bool=False]'],
 	"randwif":      ['compressed [bool=False]'],
 	"randpair":     ['compressed [bool=False]'],
 	"randpair":     ['compressed [bool=False]'],
 	"wif2hex":      ['<wif> [str]', 'compressed [bool=False]'],
 	"wif2hex":      ['<wif> [str]', 'compressed [bool=False]'],
@@ -58,7 +58,8 @@ commands = {
 	"listaddresses": ['minconf [int=1]', 'showempty [bool=False]'],
 	"listaddresses": ['minconf [int=1]', 'showempty [bool=False]'],
 	"getbalance":   ['minconf [int=1]'],
 	"getbalance":   ['minconf [int=1]'],
 	"viewtx":       ['<MMGen tx file> [str]'],
 	"viewtx":       ['<MMGen tx file> [str]'],
-	"check_addrfile":  ['<MMGen addr file> [str]'],
+	"check_addrfile": ['<MMGen addr file> [str]'],
+	"find_incog_data": ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]'],
 	"hexreverse":   ['<hexadecimal string> [str]'],
 	"hexreverse":   ['<hexadecimal string> [str]'],
 	"sha256x2":     ['<str, hexstr or filename> [str]',
 	"sha256x2":     ['<str, hexstr or filename> [str]',
 					'hex_input [bool=False]','file_input [bool=False]'],
 					'hex_input [bool=False]','file_input [bool=False]'],
@@ -70,48 +71,57 @@ commands = {
 	"privhex2addr": ['<private key in hex format> [str]','compressed [bool=False]'],
 	"privhex2addr": ['<private key in hex format> [str]','compressed [bool=False]'],
 	"encrypt":      ['<infile> [str]','outfile [str=""]','hash_preset [str="3"]'],
 	"encrypt":      ['<infile> [str]','outfile [str=""]','hash_preset [str="3"]'],
 	"decrypt":      ['<infile> [str]','outfile [str=""]','hash_preset [str="3"]'],
 	"decrypt":      ['<infile> [str]','outfile [str=""]','hash_preset [str="3"]'],
+	"rand2file":    ['<outfile> [str]','<nbytes> [str]','threads [int=4]'],
+	"bytespec":     ['<bytespec> [str]'],
 }
 }
 
 
 command_help = """
 command_help = """
-  File operations
-  hexdump      - encode data into formatted hexadecimal form (file or stdin)
-  unhexdump    - decode formatted hexadecimal data (file or stdin)
-
-  {pnm}-specific operations
-  id8          - generate 8-character {pnm} ID checksum for file (or stdin)
-  id6          - generate 6-character {pnm} ID checksum for file (or stdin)
-  check_addrfile - compute checksum and address list for {pnm} address file
-
-  Bitcoin operations:
-  strtob58     - convert a string to base 58
-  hextob58     - convert a hexadecimal number to base 58
-  b58tohex     - convert a base 58 number to hexadecimal
+  Bitcoin address/key operations (compressed addresses supported):
+  addr2hexaddr - convert Bitcoin address from base58 to hex format
   b58randenc   - generate a random 32-byte number and convert it to base 58
   b58randenc   - generate a random 32-byte number and convert it to base 58
-  randwif      - generate a random private key in WIF format
-  randpair     - generate a random private key/address pair
-  wif2hex      - convert a private key from WIF to hex format
+  b58tohex     - convert a base 58 number to hexadecimal
   hex2wif      - convert a private key from hex to WIF format
   hex2wif      - convert a private key from hex to WIF format
-  wif2addr     - generate a Bitcoin address from a key in WIF format
-  pubkey2addr  - convert Bitcoin public key to address
-  pubkey2hexaddr - convert Bitcoin public key to address in hex format
   hexaddr2addr - convert Bitcoin address from hex to base58 format
   hexaddr2addr - convert Bitcoin address from hex to base58 format
-  addr2hexaddr - convert Bitcoin address from base58 to hex format
+  hextob58     - convert a hexadecimal number to base 58
   privhex2addr - generate Bitcoin address from private key in hex format
   privhex2addr - generate Bitcoin address from private key in hex format
+  pubkey2addr  - convert Bitcoin public key to address
+  pubkey2hexaddr - convert Bitcoin public key to address in hex format
+  randpair     - generate a random private key/address pair
+  randwif      - generate a random private key in WIF format
+  strtob58     - convert a string to base 58
+  wif2addr     - generate a Bitcoin address from a key in WIF format
+  wif2hex      - convert a private key from WIF to hex format
 
 
-  Miscellaneous operations:
-  hexreverse   - reverse bytes of a hexadecimal string
+  Wallet/TX operations (bitcoind must be running):
+  getbalance    - like 'bitcoind getbalance' but shows confirmed/unconfirmed,
+                  spendable/unspendable balances for individual {pnm} wallets
+  listaddresses - list {pnm} addresses and their balances
+  viewtx        - show raw/signed {pnm} transaction in human-readable form
+
+  General utilities:
+  bytespec     - convert a byte specifier such as '1GB' into a plain integer
+  hexdump      - encode data into formatted hexadecimal form (file or stdin)
   hexlify      - display string in hexadecimal format
   hexlify      - display string in hexadecimal format
+  hexreverse   - reverse bytes of a hexadecimal string
+  rand2file    - write 'n' bytes of random data to specified file
+  randhex      - print 'n' bytes (default 32) of random data in hex format
   sha256x2     - compute a double sha256 hash of data
   sha256x2     - compute a double sha256 hash of data
-  getrand      - print 'n' bytes (default 32) of random data in hex format
+  unhexdump    - decode formatted hexadecimal data (file or stdin)
 
 
-  Encryption operations:
-  encrypt      - encrypt a file using {pnm}'s encryption suite
-  decrypt      - decrypt an {pnm}-encrypted file
+  File encryption:
+  encrypt      - encrypt a file
+  decrypt      - decrypt a file
     {pnm} encryption suite:
     {pnm} encryption suite:
       * Key: Scrypt (user-configurable hash parameters, 32-byte salt)
       * Key: Scrypt (user-configurable hash parameters, 32-byte salt)
       * Enc: AES256_CTR, 16-byte rand IV, sha256 hash + 32-byte nonce + data
       * Enc: AES256_CTR, 16-byte rand IV, sha256 hash + 32-byte nonce + data
       * The encrypted file is indistinguishable from random data
       * The encrypted file is indistinguishable from random data
 
 
+  {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)
+
   Mnemonic operations (choose "electrum" (default), "tirosh" or "all"
   Mnemonic operations (choose "electrum" (default), "tirosh" or "all"
   wordlists):
   wordlists):
   mn_rand128   - generate random 128-bit mnemonic
   mn_rand128   - generate random 128-bit mnemonic
@@ -120,12 +130,6 @@ command_help = """
   mn_stats     - show stats for mnemonic wordlist
   mn_stats     - show stats for mnemonic wordlist
   mn_printlist - print mnemonic wordlist
   mn_printlist - print mnemonic wordlist
 
 
-  Bitcoind operations (bitcoind must be running):
-  listaddresses - show {pnm} addresses and their balances
-  getbalance    - like 'bitcoind getbalance' but shows confirmed/unconfirmed,
-                  spendable/unspendable
-  viewtx        - show raw/signed {pnm} transaction in human-readable form
-
   IMPORTANT NOTE: Though {pnm} mnemonics use the Electrum wordlist, they're
   IMPORTANT NOTE: Though {pnm} mnemonics use the Electrum wordlist, they're
   computed using a different algorithm and are NOT Electrum-compatible!
   computed using a different algorithm and are NOT Electrum-compatible!
 """.format(pnm=g.proj_name)
 """.format(pnm=g.proj_name)
@@ -234,8 +238,8 @@ def b58randenc():
 	dec = bitcoin.b58decode(enc)
 	dec = bitcoin.b58decode(enc)
 	print_convert_results(ba.hexlify(r),enc,ba.hexlify(dec))
 	print_convert_results(ba.hexlify(r),enc,ba.hexlify(dec))
 
 
-def getrand(bytes='32'):
-	print ba.hexlify(get_random(int(bytes),opts))
+def randhex(nbytes='32'):
+	print ba.hexlify(get_random(int(nbytes),opts))
 
 
 def randwif(compressed=False):
 def randwif(compressed=False):
 	r_hex = ba.hexlify(get_random(32,opts))
 	r_hex = ba.hexlify(get_random(32,opts))
@@ -443,3 +447,107 @@ def decrypt(infile,outfile="",hash_preset=''):
 			write_to_file((outfile or of),out,opts,"decrypted data",True,True)
 			write_to_file((outfile or of),out,opts,"decrypted data",True,True)
 	else:
 	else:
 		msg("Incorrect passphrase or hash preset")
 		msg("Incorrect passphrase or hash preset")
+
+
+def find_incog_data(filename,iv_id,keep_searching=False):
+	ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8
+	n,carry = 0," "*ivsize
+	f = os.open(filename,os.O_RDONLY)
+	while True:
+		d = os.read(f,bsize)
+		if not d: break
+		d = carry + d
+		for i in range(bsize):
+			if sha256(d[i:i+ivsize]).hexdigest()[:8].upper() == iv_id:
+				if n+i < ivsize: continue
+				msg("\rIncog data for ID %s found at offset %s" %
+					(iv_id,n+i-ivsize))
+				if not keep_searching: sys.exit(0)
+		carry = d[len(d)-ivsize:]
+		n += bsize
+		if not n % mod: msg_r("\rSearched: %s bytes" % n)
+
+	msg("")
+	os.close(f)
+
+# From "man dd":
+# c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024,
+# GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y.
+
+def parse_nbytes(nbytes):
+	import re
+	m = re.match(r'([0123456789]+)(.*)',nbytes)
+	smap = ("c",1),("w",2),("b",512),("kB",1000),("K",1024),("MB",1000*1000),\
+			("M",1024*1024),("GB",1000*1000*1000),("G",1024*1024*1024)
+	if m:
+		if m.group(2):
+			for k,v in smap:
+				if k == m.group(2):
+					return int(m.group(1)) * v
+			else:
+				msg("Valid byte specifiers: '%s'" % "' '".join([i[0] for i in smap]))
+		else:
+			return int(nbytes)
+
+	msg("'%s': invalid byte specifier" % nbytes)
+	sys.exit(1)
+
+
+def rand2file(outfile, nbytes, threads=4):
+	nbytes = parse_nbytes(nbytes)
+	from Crypto import Random
+	rh = Random.new()
+	from Queue import Queue
+	from threading import Thread
+	bsize = 2**20
+	roll = bsize * 4
+	if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
+	f = open(outfile,"w")
+
+	from Crypto.Cipher import AES
+	from Crypto.Util import Counter
+
+	key = get_random(32,opts)
+
+	def encrypt_worker(wid):
+		while True:
+			i,d = q1.get()
+			c = AES.new(key, AES.MODE_CTR,
+					counter=Counter.new(g.aesctr_iv_len*8,initial_value=i))
+			enc_data = c.encrypt(d)
+			q2.put(enc_data)
+			q1.task_done()
+
+	def output_worker():
+		while True:
+			data = q2.get()
+			f.write(data)
+			q2.task_done()
+
+	q1 = Queue()
+	for i in range(max(1,threads-2)):
+		t = Thread(target=encrypt_worker, args=(i,))
+		t.daemon = True
+		t.start()
+
+	q2 = Queue()
+	t = Thread(target=output_worker)
+	t.daemon = True
+	t.start()
+
+	i = 1; rbytes = nbytes
+	while rbytes > 0:
+		d = rh.read(min(bsize,rbytes))
+		q1.put((i,d))
+		rbytes -= bsize
+		i += 1
+		if not (bsize*i) % roll:
+			msg_r("\rRead: %s bytes" % (bsize*i))
+
+	msg("\rRead: %s bytes" % nbytes)
+	qmsg("\r%s bytes written to file '%s'" % (nbytes,outfile))
+	q1.join()
+	q2.join()
+	f.close()
+
+def bytespec(s): print parse_nbytes(s)

+ 7 - 8
mmgen/tx.py

@@ -141,18 +141,17 @@ def normalize_btc_amt(amt):
 
 
 def get_bitcoind_cfg_options(cfg_keys):
 def get_bitcoind_cfg_options(cfg_keys):
 
 
-	if "HOME" in os.environ:
-		data_dir = ".bitcoin"
-		cfg_file = "%s/%s/%s" % (os.environ["HOME"], data_dir, "bitcoin.conf")
-	elif "HOMEPATH" in os.environ:
-	# Windows:
-		data_dir = r"Application Data\Bitcoin"
-		cfg_file = "%s\%s\%s" % (os.environ["HOMEPATH"],data_dir,"bitcoin.conf")
+	if "HOME" in os.environ:       # Linux
+		homedir,datadir = os.environ["HOME"],".bitcoin"
+	elif "HOMEPATH" in os.environ: # Windows:
+		homedir,data_dir = os.environ["HOMEPATH"],r"Application Data\Bitcoin"
 	else:
 	else:
 		msg("Neither $HOME nor %HOMEPATH% are set")
 		msg("Neither $HOME nor %HOMEPATH% are set")
 		msg("Don't know where to look for 'bitcoin.conf'")
 		msg("Don't know where to look for 'bitcoin.conf'")
 		sys.exit(3)
 		sys.exit(3)
 
 
+	cfg_file = os.sep.join((homedir, datadir, "bitcoin.conf"))
+
 	cfg = dict([(k,v) for k,v in [split2(line.translate(None,"\t "),"=")
 	cfg = dict([(k,v) for k,v in [split2(line.translate(None,"\t "),"=")
 			for line in get_lines_from_file(cfg_file)] if k in cfg_keys])
 			for line in get_lines_from_file(cfg_file)] if k in cfg_keys])
 
 
@@ -535,7 +534,7 @@ def check_addr_data_hash(seed_id,addr_data):
 	fl = fmt_addr_idxs([int(a[0]) for a in addr_data])
 	fl = fmt_addr_idxs([int(a[0]) for a in addr_data])
 	msg("Computed checksum for addr data {}[{}]: {}".format(
 	msg("Computed checksum for addr data {}[{}]: {}".format(
 				seed_id,fl,addr_data_chksum))
 				seed_id,fl,addr_data_chksum))
-	msg("Check this value against your records")
+	qmsg("Check this value against your records")
 
 
 def parse_addrs_file(f):
 def parse_addrs_file(f):
 
 

+ 34 - 24
mmgen/util.py

@@ -80,7 +80,6 @@ def get_random_data_from_user(uchars):
 
 
 	prompt = "User random data successfully acquired.  Press ENTER to continue"
 	prompt = "User random data successfully acquired.  Press ENTER to continue"
 	prompt_and_get_char(prompt,"",enter_ok=True)
 	prompt_and_get_char(prompt,"",enter_ok=True)
-	msg("")
 
 
 	return key_data+"".join(fmt_time_data)
 	return key_data+"".join(fmt_time_data)
 
 
@@ -97,7 +96,7 @@ def get_random(length,opts):
 		else:
 		else:
 			kwhat += "saved user entropy"
 			kwhat += "saved user entropy"
 		key = make_key(g.user_entropy, "", '2', what=kwhat)
 		key = make_key(g.user_entropy, "", '2', what=kwhat)
-		return encrypt_data(os_rand,key,what="random data")
+		return encrypt_data(os_rand,key,what="random data",verify=False)
 	else:
 	else:
 		return os_rand
 		return os_rand
 
 
@@ -137,11 +136,11 @@ def show_hash_presets():
 cmessages = {
 cmessages = {
 	'null': "",
 	'null': "",
 	'incog_iv_id': """
 	'incog_iv_id': """
-   If you know your IV ID, check it against the value above.  If it's
+   If you know your Incog ID, check it against the value above.  If it's
    incorrect, then your incognito data is invalid.
    incorrect, then your incognito data is invalid.
 """,
 """,
 	'incog_iv_id_hidden': """
 	'incog_iv_id_hidden': """
-   If you know your IV ID, check it against the value above.  If it's
+   If you know your Incog ID, check it against the value above.  If it's
    incorrect, then your incognito data is invalid or you've supplied
    incorrect, then your incognito data is invalid or you've supplied
    an incorrect offset.
    an incorrect offset.
 """,
 """,
@@ -253,6 +252,9 @@ def make_chksum_8(s,sep=False):
 def make_chksum_6(s):
 def make_chksum_6(s):
 	return sha256(s).hexdigest()[:6]
 	return sha256(s).hexdigest()[:6]
 
 
+def make_iv_chksum(s):
+	return sha256(s).hexdigest()[:8].upper()
+
 
 
 def check_infile(f):
 def check_infile(f):
 
 
@@ -372,7 +374,7 @@ def _get_seed_from_brain_passphrase(words,opts):
 def encrypt_seed(seed, key):
 def encrypt_seed(seed, key):
 	return encrypt_data(seed, key, iv=1, what="seed")
 	return encrypt_data(seed, key, iv=1, what="seed")
 
 
-def encrypt_data(data, key, iv=1, what="data"):
+def encrypt_data(data, key, iv=1, what="data", verify=True):
 	"""
 	"""
 	Encrypt arbitrary data using AES256 in counter mode
 	Encrypt arbitrary data using AES256 in counter mode
 	"""
 	"""
@@ -387,16 +389,17 @@ def encrypt_data(data, key, iv=1, what="data"):
 			counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
 			counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
 	enc_data = c.encrypt(data)
 	enc_data = c.encrypt(data)
 
 
-	vmsg_r("Performing a test decryption of the %s..." % what)
+	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)
+		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)
+		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
 	return enc_data
 
 
@@ -432,9 +435,15 @@ def open_file_or_exit(filename,mode):
 	return f
 	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):
 def write_to_file(outfile,data,opts,what="data",confirm=False,verbose=False):
 
 
-	if 'outdir' in opts: outfile = "%s/%s" % (opts['outdir'], outfile)
+	if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
 
 
 	if confirm:
 	if confirm:
 		from os import stat
 		from os import stat
@@ -456,7 +465,6 @@ def write_to_file(outfile,data,opts,what="data",confirm=False,verbose=False):
 	if verbose: msg("%s written to file '%s'" % (what.capitalize(),outfile))
 	if verbose: msg("%s written to file '%s'" % (what.capitalize(),outfile))
 
 
 
 
-
 def export_to_file(outfile, data, opts, what="data"):
 def export_to_file(outfile, data, opts, what="data"):
 
 
 	if 'stdout' in opts:
 	if 'stdout' in opts:
@@ -846,7 +854,8 @@ def get_seed_from_incog_wallet(
 
 
 	iv, enc_incog_data = d[0:g.aesctr_iv_len], d[g.aesctr_iv_len:]
 	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))
+	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
 	vmsg(cmessages['incog_iv_id_hidden' if "from_incog_hidden" in opts
 			else 'incog_iv_id'])
 			else 'incog_iv_id'])
 
 
@@ -1047,17 +1056,18 @@ def do_pager(text):
 
 
 
 
 def export_to_hidden_incog(incog_enc,opts):
 def export_to_hidden_incog(incog_enc,opts):
-	fname,offset = opts['export_incog_hidden'].split(",") #Already sanity-checked
+	outfile,offset = opts['export_incog_hidden'].split(",") #Already sanity-checked
+	if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
 
 
-	check_data_fits_file_at_offset(fname,int(offset),len(incog_enc),"write")
+	check_data_fits_file_at_offset(outfile,int(offset),len(incog_enc),"write")
 
 
-	if not g.quiet: confirm_or_exit("","alter file '%s'" % fname)
-	f = os.open(fname,os.O_RDWR)
+	if not g.quiet: confirm_or_exit("","alter file '%s'" % outfile)
+	f = os.open(outfile,os.O_RDWR)
 	os.lseek(f, int(offset), os.SEEK_SET)
 	os.lseek(f, int(offset), os.SEEK_SET)
 	os.write(f, incog_enc)
 	os.write(f, incog_enc)
 	os.close(f)
 	os.close(f)
-	qmsg("Data written to file '%s' at offset %s" % (fname,offset),
-			"Data written to file")
+	msg("Data written to file '%s' at offset %s" %
+			(os.path.relpath(outfile),offset))
 
 
 
 
 def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
 def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
@@ -1090,8 +1100,8 @@ def wallet_to_incog_data(infile,opts):
 		sys.exit(2)
 		sys.exit(2)
 
 
 	iv = get_random(g.aesctr_iv_len,opts)
 	iv = get_random(g.aesctr_iv_len,opts)
-	iv_id = make_chksum_8(iv)
-	qmsg("IV ID: %s" % iv_id)
+	iv_id = make_iv_chksum(iv)
+	msg("Incog ID: %s" % iv_id)
 
 
 	# IV is used BOTH to initialize counter and to salt password!
 	# IV is used BOTH to initialize counter and to salt password!
 	key = make_key(passwd, iv, preset, "wrapper key")
 	key = make_key(passwd, iv, preset, "wrapper key")

+ 1 - 1
setup.py

@@ -3,7 +3,7 @@ from distutils.core import setup
 
 
 setup(
 setup(
 		name         = 'mmgen',
 		name         = 'mmgen',
-		version      = '0.7.6a',
+		version      = '0.7.7',
 		author       = 'Philemon',
 		author       = 'Philemon',
 		author_email = 'mmgen-py@yandex.com',
 		author_email = 'mmgen-py@yandex.com',
 		url          = 'https://github.com/mmgen/mmgen',
 		url          = 'https://github.com/mmgen/mmgen',