main_passgen.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2023 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. mmgen-passgen: Generate a series or range of passwords from an MMGen
  20. deterministic wallet
  21. """
  22. import mmgen.opts as opts
  23. from .cfg import gc,Config
  24. from .addrlist import AddrIdxList
  25. from .passwdlist import PasswordList
  26. from .wallet import Wallet
  27. from .obj import MMGenPWIDString
  28. pwi = PasswordList.pw_info
  29. opts_data = {
  30. 'sets': [('print_checksum',True,'quiet',True)],
  31. 'text': {
  32. 'desc': f"""
  33. Generate a range or list of passwords from an {gc.proj_name} wallet,
  34. mnemonic, seed or brainwallet for the given ID string
  35. """,
  36. 'usage':'[opts] [seed source] <ID string> <index list or range(s)>',
  37. 'options': """
  38. -h, --help Print this help message
  39. --, --longhelp Print help message for long options (common options)
  40. -d, --outdir= d Output files to directory 'd' instead of working dir
  41. -e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
  42. -f, --passwd-fmt= f Generate passwords of format 'f'. Default: {dpf}.
  43. See PASSWORD FORMATS below
  44. -i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
  45. -H, --hidden-incog-input-params=f,o Read hidden incognito data from file
  46. 'f' at offset 'o' (comma-separated)
  47. -O, --old-incog-fmt Specify old-format incognito input
  48. -L, --passwd-len= l Specify length of generated passwords. For defaults,
  49. see PASSWORD FORMATS below. An argument of 'h' will
  50. generate passwords of half the default length.
  51. -l, --seed-len= l Specify wallet seed length of 'l' bits. This option
  52. is required only for brainwallet and incognito inputs
  53. with non-standard (< {dsl}-bit) seed lengths.
  54. -p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
  55. for password hashing (default: '{gc.dfl_hash_preset}')
  56. -z, --show-hash-presets Show information on available hash presets
  57. -P, --passwd-file= f Get wallet passphrase from file 'f'
  58. -q, --quiet Produce quieter output; suppress some warnings
  59. -r, --usr-randchars=n Get 'n' characters of additional randomness from user
  60. (min={cfg.min_urandchars}, max={cfg.max_urandchars}, default={cfg.usr_randchars})
  61. -S, --stdout Print passwords to stdout
  62. -v, --verbose Produce more verbose output
  63. """,
  64. 'notes': """
  65. NOTES FOR THIS COMMAND
  66. ID string must be a valid UTF-8 string not longer than {ml} characters and
  67. not containing the symbols '{fs}'.
  68. Password indexes are given as a comma-separated list and/or hyphen-separated
  69. range(s).
  70. Changing either the password format (base32,base58) or length alters the seed
  71. and thus generates a completely new set of passwords.
  72. PASSWORD FORMATS:
  73. {pfi}
  74. EXAMPLES:
  75. Generate ten base58 passwords of length {i58.dfl_len} for Alice's email account:
  76. {gc.prog_name} alice@nowhere.com 1-10
  77. Generate ten base58 passwords of length 16 for Alice's email account:
  78. {gc.prog_name} --passwd-len=16 alice@nowhere.com 1-10
  79. Generate ten base32 passwords of length {i32.dfl_len} for Alice's email account:
  80. {gc.prog_name} --passwd-fmt=b32 alice@nowhere.com 1-10
  81. Generate three BIP39 mnemonic seed phrases of length {i39.dfl_len} for Alice's
  82. Trezor device:
  83. {gc.prog_name} --passwd-fmt=bip39 mytrezor 1-3
  84. All passwords are cryptographically unlinkable with each other, including
  85. passwords with the same format but different length, so Alice needn't worry
  86. about inadvertent reuse of private data.
  87. NOTES FOR ALL GENERATOR COMMANDS
  88. {n_pw}
  89. {n_bw}
  90. FMT CODES:
  91. {n_fmt}
  92. """
  93. },
  94. 'code': {
  95. 'options': lambda cfg,help_notes,s: s.format(
  96. pnm=gc.proj_name,
  97. dsl=help_notes('dfl_seed_len'),
  98. dpf=PasswordList.dfl_pw_fmt,
  99. cfg=cfg,
  100. gc=gc,
  101. ),
  102. 'notes': lambda cfg,help_notes,s: s.format(
  103. o=opts,cfg=cfg,i58=pwi['b58'],i32=pwi['b32'],i39=pwi['bip39'],
  104. ml=MMGenPWIDString.max_len,
  105. fs="', '".join(MMGenPWIDString.forbidden),
  106. n_pw=help_notes('passwd'),
  107. n_bw=help_notes('brainwallet'),
  108. pfi=help_notes('password_formats'),
  109. n_fmt=help_notes('fmt_codes'),
  110. gc=gc,
  111. )
  112. }
  113. }
  114. cfg = Config(opts_data=opts_data)
  115. if len(cfg._args) < 2:
  116. cfg._opts.usage()
  117. pw_idxs = AddrIdxList(fmt_str=cfg._args.pop())
  118. pw_id_str = cfg._args.pop()
  119. from .fileutil import get_seed_file
  120. sf = get_seed_file(cfg,1)
  121. pw_fmt = cfg.passwd_fmt or PasswordList.dfl_pw_fmt
  122. pw_len = pwi[pw_fmt].dfl_len // 2 if cfg.passwd_len in ('h','H') else cfg.passwd_len
  123. from .protocol import init_proto
  124. proto = init_proto( cfg, 'btc' ) # TODO: get rid of dummy proto
  125. PasswordList(
  126. cfg = cfg,
  127. proto = proto,
  128. pw_id_str = pw_id_str,
  129. pw_len = pw_len,
  130. pw_fmt = pw_fmt,
  131. chk_params_only = True )
  132. from .ui import do_license_msg
  133. do_license_msg(cfg)
  134. ss = Wallet(cfg,sf)
  135. al = PasswordList(
  136. cfg = cfg,
  137. proto = proto,
  138. seed = ss.seed,
  139. pw_idxs = pw_idxs,
  140. pw_id_str = pw_id_str,
  141. pw_len = pw_len,
  142. pw_fmt = pw_fmt )
  143. af = al.get_file()
  144. af.format()
  145. from .ui import keypress_confirm
  146. if keypress_confirm( cfg, 'Encrypt password list?' ):
  147. af.encrypt()
  148. af.write(binary=True,desc='encrypted password list')
  149. else:
  150. if cfg.test_suite_popen_spawn and gc.platform == 'win':
  151. import time
  152. time.sleep(0.1)
  153. af.write(desc='password list')