2 """Generate Password from Salts and Passphrase.
4 The salts come from command line option and the passphrase from stdin.
5 Print ``hash(salts + passphrase)'' for your password.
6 You can generate different passwords for many domains from one passphrase.
8 usage: genpasswd [options] [string...]
11 $ genpasswd example.com # domain to login
12 Passphrase:My Passphrase # without echoback
13 Vn/aHPNgXbieJCkSGYiAA7y9GwM # got your password for example.com
15 $ genpasswd example.net # domain to login
16 Passphrase:My Passphrase # without echoback
17 /+/G4MzuaiSo9dHE/c0+GgPi6Nc # got your password for example.net
20 # Copyright (c) 2009-2021 Satoshi Fukutomi <info@fuktommy.com>.
21 # All rights reserved.
23 # Redistribution and use in source and binary forms, with or without
24 # modification, are permitted provided that the following conditions
26 # 1. Redistributions of source code must retain the above copyright
27 # notice, this list of conditions and the following disclaimer.
28 # 2. Redistributions in binary form must reproduce the above copyright
29 # notice, this list of conditions and the following disclaimer in the
30 # documentation and/or other materials provided with the distribution.
32 # THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
33 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
36 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
37 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
38 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
39 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
40 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
41 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51 from getpass import getpass
55 """Parse command line argments.
57 usage = 'usage: %prog [options] [string...]'
58 parser = optparse.OptionParser(usage=usage)
59 parser.add_option('-a', '--alphanum', dest='alphanum_mode',
60 default=False, action='store_true',
61 help='password includes alphabet and number only')
62 parser.add_option('-c', '--clipboard', dest='clipboard_mode',
63 default=False, action='store_true',
64 help='copy password to clipboard (require putclip command)')
65 parser.add_option('-f', '--file', dest='saltfile', metavar='FILE',
66 help='additional salt file')
67 parser.add_option('-s', '--size', type='int', dest='size',
69 parser.add_option('--test', action='callback', callback=_test,
70 help='run test and exit')
71 return parser.parse_args(argv)
74 def generate_password(salt, options):
75 """Generate password(hash) from salt.
79 buf.extend(':'.encode('utf8'))
80 if isinstance(s, str):
81 buf.extend(s.encode('utf8'))
84 digest = hashlib.sha1(bytes(buf[1:])).digest()
85 passwd = base64.b64encode(digest).decode('ascii').replace('=', '').strip()
87 if options.alphanum_mode:
88 passwd = passwd.replace('+', '').replace('/', '')
89 if options.size is not None:
90 passwd = passwd[:options.size]
94 def clipboard_command():
95 """Select clipboard command for platform.
97 if sys.platform.startswith('linux'):
98 return 'xsel --input --clipboard'
99 elif sys.platform == 'darwin':
102 raise Exception('I do not know your clipboard command.')
104 def windows_put_clipboard(string):
105 """Put string to clipboard on Windows.
109 import win32clipboard
110 win32clipboard.OpenClipboard()
111 win32clipboard.SetClipboardText(string)
112 win32clipboard.CloseClipboard()
114 def put_clipboard(string):
115 """Put string to clipboard.
118 windows_put_clipboard(string)
122 if sys.platform == 'cygwin':
123 open('/dev/clipboard', 'wb').write(string)
125 cmd = clipboard_command()
126 pipe = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE)
127 pipe.stdin.write(string)
131 class GeneratePasswordTest(unittest.TestCase):
132 def test_generate(self):
135 argv = ['foo', 'bar']
136 options, salt = parse_args(argv)
137 result = generate_password(salt, options)
138 self.assertEqual('VNy+Z9IdXrOUk9Rtia4fQS071t4', result)
140 def test_generate_alpha(self):
141 """Set alpha_num_mode on.
143 argv = ['foo', 'bar', '-a']
144 options, salt = parse_args(argv)
145 result = generate_password(salt, options)
146 self.assertEqual('VNyZ9IdXrOUk9Rtia4fQS071t4', result)
148 def test_generate_size(self):
151 argv = ['foo', 'bar', '-s', '6']
152 options, salt = parse_args(argv)
153 result = generate_password(salt, options)
154 self.assertEqual('VNy+Z9', result)
156 def test_generate_alpha_size(self):
157 """Set size and alpha_num_mode.
159 Password size is set size.
161 argv = ['foo', 'bar', '-a', '-s', '6']
162 options, salt = parse_args(argv)
163 result = generate_password(salt, options)
164 self.assertEqual('VNyZ9I', result)
167 def _test(option, opt_str, value, parser, *args, **kwargs):
168 suite = unittest.TestSuite()
169 suite.addTest(unittest.makeSuite(GeneratePasswordTest))
170 result = unittest.TextTestRunner(verbosity=2).run(suite)
171 if result.errors or result.failures:
178 options, salt = parse_args(sys.argv[1:])
180 salt.append(open(options.saltfile, 'rb').read())
181 passphrase = getpass('Passphrase:')
182 salt.append(passphrase)
183 passwd = generate_password(salt, options)
184 if options.clipboard_mode:
185 print('put password to clipboard.')
186 put_clipboard(passwd)
191 if __name__ == '__main__':