Created
August 15, 2011 22:34
-
-
Save charlieman/1148050 to your computer and use it in GitHub Desktop.
One file command line version of oplop (http://oplop.googlecode.com/), added option to generate passphrases with -p
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# "Generate account passwords based on an account name and a master password." | |
from __future__ import print_function | |
try: | |
import argparse | |
except ImportError: | |
from . import argparse | |
try: | |
import win32clipboard | |
except ImportError: | |
win32clipboard = None | |
from getpass import getpass | |
import subprocess | |
import sys | |
## __init__.py | |
try: | |
from hashlib import md5 | |
except ImportError: | |
from md5 import md5 | |
import base64 | |
import re | |
_length = 8 | |
def make_counter(start=0): | |
def get_next(): | |
get_next.count += 1 | |
return get_next.count | |
get_next.count = start | |
return get_next | |
def iter_pairs(iterable): | |
loop = iter(iterable) | |
first = loop.next() | |
for second in loop: | |
yield (first, second) | |
first = second | |
def _raw_hash(label, password): | |
"""Generate a unique hash from a label and master password.""" | |
hash_object = md5() | |
hash_object.update(password) | |
hash_object.update(label) | |
return base64.urlsafe_b64encode(hash_object.digest()) | |
def _raw_hash_sha512(label, password): | |
"""Generate a unique hash from a label and master password.""" | |
from hashlib import sha512 | |
hash_object = sha512() | |
hash_object.update(password) | |
hash_object.update(label) | |
return base64.urlsafe_b64encode(hash_object.digest()) | |
def create(label, master): | |
"""Create a password from a label and master password.""" | |
encoded_label = label.encode("utf-8") | |
encoded_master = master.encode("utf-8") | |
hash_ = _raw_hash(encoded_label, encoded_master).decode("ascii") | |
found = re.search(r"\d+", hash_) | |
if not found: | |
hash_ = '1' + hash_ | |
elif found.start() >= _length: | |
hash_ = found.group() + hash_ | |
return hash_[:_length] | |
def create_phrase(label, master, parts=4): | |
"""Create a passphrase from a label and master password.""" | |
encoded_label = label.encode("utf-8") | |
encoded_master = master.encode("utf-8") | |
hash_ = _raw_hash_sha512(encoded_label, encoded_master).decode("ascii") | |
counter = make_counter(1) | |
size = len(hash_) | |
ranges = range(0, size, _length)[:parts + 1] | |
regex = re.compile(r"\d+") | |
phrase = [] | |
for start, end in iter_pairs(ranges): | |
part = hash_[start:end] | |
found = regex.search(part) | |
if not found: | |
part = str(counter()) + part | |
elif found.start() >= _length: | |
part = found.group() + part | |
phrase.append(part) | |
return ' '.join(phrase) | |
## | |
# Python 2.6 compat along with ease of mocking. | |
try: | |
from builtins import input | |
except ImportError: | |
input = raw_input | |
def get_account_name(): | |
print("Nickname = ", end="", file=sys.stderr) | |
return input() | |
def get_master_password(verifying=False): | |
return getpass('Master password {0}(not echoed) ... '.format( | |
['', 'again '][verifying])) | |
def print_account_password(account_password): | |
"""Print the account password to stdout.""" | |
print('', file=sys.stderr) | |
sys.stderr.flush() | |
print(account_password, end="", file=sys.stdout) | |
sys.stdout.flush() | |
print('', file=sys.stderr) | |
sys.stderr.flush() | |
return True | |
def clipboard(command, account_password): | |
try: | |
clipboard = subprocess.Popen(command, stdin=subprocess.PIPE) | |
except OSError as exc: | |
if exc.errno == 2: | |
print("{0} does not exist.".format(command[0]), file=sys.stderr) | |
else: | |
raise | |
return False | |
account_password_bytes = account_password.encode(sys.stdin.encoding) | |
out, err = clipboard.communicate(account_password_bytes) | |
if out or err: | |
print("Unexpected output when using {0!r}:".format(' '.join(command)), | |
file=sys.stderr) | |
if out: | |
print("stdout:\n", out, sep=" ", file=sys.stderr) | |
if err: | |
print("stderr:\n", err, sep=" ", file=sys.stderr) | |
return False | |
else: | |
print("\nAccount password copied to the clipboard " | |
"using {0!r}".format(' '.join(command)), file=sys.stderr) | |
return True | |
def osx_clipboard(account_password): | |
"""Set the clipboard to the account password under OS X.""" | |
return clipboard(['pbcopy'], account_password) | |
def x11_clipboard(account_password): | |
"""Use the X11 clipboard through xclip to set the account password.""" | |
return clipboard(['xclip', '-selection', 'clipboard'], account_password) | |
def win32_clipboard(account_password): | |
win32clipboard.OpenClipboard() | |
win32clipboard.EmptyClipboard() | |
win32clipboard.SetClipboardText(account_password) | |
win32clipboard.CloseClipboard() | |
print("\nAccount password copied to the clipboard", file=sys.stderr) | |
return True | |
def set_account_password(account_password, clipboard=True, stdout=True): | |
if clipboard: | |
if sys.platform == 'darwin': | |
if osx_clipboard(account_password): | |
return True | |
elif sys.platform == 'win32' and win32clipboard is not None: | |
if win32_clipboard(account_password): | |
return True | |
elif x11_clipboard(account_password): | |
return True | |
if stdout: | |
# Fallback if no clipboard works. | |
print_account_password(account_password) | |
return True | |
else: | |
return False | |
def main(cmd_line_args=[]): | |
parser = argparse.ArgumentParser(prog="oplop", description=__doc__) | |
parser.add_argument("--passphrase", "-p", action='store_true', | |
help="Create a passphrase rather than a password") | |
parser.add_argument("--stdout", "-o", action='store_true', | |
help="Print account password to stdout; do not use the clipboard") | |
parser.add_argument("--clipboard", "-c", action='store_true', | |
help="Only use the clipboard") | |
parser.add_argument("--verify", "-v", action='store_true', | |
help='Double-check the master password by entering it twice') | |
parser.add_argument("nickname", nargs='?', help="Account nickname") | |
parser.add_argument("master_password", nargs='?', help="Master password") | |
args = parser.parse_args(cmd_line_args) | |
if args.nickname: | |
label = args.nickname | |
else: | |
label = get_account_name() | |
if args.master_password: | |
master = args.master_password | |
else: | |
master = get_master_password() | |
if args.verify: | |
master_again = get_master_password(verifying=True) | |
if master != master_again: | |
print("\nMaster password verification failed!", file=sys.stderr) | |
sys.exit(1) | |
if args.passphrase: | |
password = create_phrase(label, master) | |
else: | |
password = create(label, master) | |
use_clipboard = not args.stdout | |
use_stdout = not args.clipboard | |
if not set_account_password(password, clipboard=use_clipboard, | |
stdout=use_stdout): | |
sys.exit(1) | |
if __name__ == '__main__': | |
main(sys.argv[1:]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment