Adds encryption and decryption of existing journals, closes #6

This commit is contained in:
Manuel Ebert 2012-04-17 21:34:01 +02:00
parent c7f7e2f287
commit 4c7ea456cb
2 changed files with 54 additions and 18 deletions

View file

@ -1,6 +1,10 @@
Changelog Changelog
========= =========
### 0.2.2
* Adds --encrypt and --decrypt to encrypt / descrypt existing journal files
### 0.2.1 ### 0.2.1
* Submitted to [PyPi](http://pypi.python.org/pypi/jrnl/0.2.1). * Submitted to [PyPi](http://pypi.python.org/pypi/jrnl/0.2.1).

66
jrnl.py
View file

@ -30,6 +30,8 @@ default_config = {
'tagsymbols': '@' 'tagsymbols': '@'
} }
CONFIG_PATH = os.path.expanduser('~/.jrnl_config')
class Entry: class Entry:
def __init__(self, journal, date=None, title="", body=""): def __init__(self, journal, date=None, title="", body=""):
self.journal = journal # Reference to journal mainly to access it's config self.journal = journal # Reference to journal mainly to access it's config
@ -104,6 +106,11 @@ class Journal:
plain += " " * 16 plain += " " * 16
return iv + crypto.encrypt(plain) return iv + crypto.encrypt(plain)
def make_key(self, prompt="Password: "):
"""Creates an encryption key from the default password or prompts for a new password."""
password = self.config['password'] or getpass.getpass(prompt)
self.key = hashlib.sha256(password).digest()
def open(self, filename=None): def open(self, filename=None):
"""Opens the journal file defined in the config and parses it into a list of Entries. """Opens the journal file defined in the config and parses it into a list of Entries.
Entries have the form (date, title, body).""" Entries have the form (date, title, body)."""
@ -115,12 +122,11 @@ class Journal:
decrypted = None decrypted = None
attempts = 0 attempts = 0
while decrypted is None: while decrypted is None:
password = self.config['password'] or getpass.getpass() self.make_key()
self.key = hashlib.sha256(password).digest()
decrypted = self._decrypt(journal) decrypted = self._decrypt(journal)
if not decrypted: if decrypted is None:
attempts += 1 attempts += 1
self.config['password'] = None # This doesn't work. self.config['password'] = None # This password doesn't work.
if attempts < 3: if attempts < 3:
print("Wrong password, try again.") print("Wrong password, try again.")
else: else:
@ -203,7 +209,6 @@ class Journal:
search_tags = set([tag.lower() for tag in tags]) search_tags = set([tag.lower() for tag in tags])
end_date = self.parse_date(end_date) end_date = self.parse_date(end_date)
start_date = self.parse_date(start_date) start_date = self.parse_date(start_date)
print start_date, end_date
# If strict mode is on, all tags have to be present in entry # If strict mode is on, all tags have to be present in entry
tagged = search_tags.issubset if strict else search_tags.intersection tagged = search_tags.issubset if strict else search_tags.intersection
result = [ result = [
@ -257,7 +262,11 @@ class Journal:
self.entries.append(Entry(self, date, title, body)) self.entries.append(Entry(self, date, title, body))
self.sort() self.sort()
def setup(config_path): def save_config(self, config_path = CONFIG_PATH):
with open(config_path, 'w') as f:
json.dump(self.config, f, indent=2)
def setup():
def autocomplete(text, state): def autocomplete(text, state):
expansions = glob.glob(os.path.expanduser(text)+'*') expansions = glob.glob(os.path.expanduser(text)+'*')
expansions = [e+"/" if os.path.isdir(e) else e for e in expansions] expansions = [e+"/" if os.path.isdir(e) else e for e in expansions]
@ -281,7 +290,7 @@ def setup(config_path):
open(default_config['journal'], 'a').close() # Touch to make sure it's there open(default_config['journal'], 'a').close() # Touch to make sure it's there
# Write config to ~/.jrnl_conf # Write config to ~/.jrnl_conf
with open(config_path, 'w') as f: with open(CONFIG_PATH, 'w') as f:
json.dump(default_config, f, indent=2) json.dump(default_config, f, indent=2)
config = default_config config = default_config
if password: if password:
@ -289,11 +298,11 @@ def setup(config_path):
return config return config
if __name__ == "__main__": if __name__ == "__main__":
config_path = os.path.expanduser('~/.jrnl_config')
if not os.path.exists(config_path): if not os.path.exists(CONFIG_PATH):
config = setup(config_path) config = setup()
else: else:
with open(config_path) as f: with open(CONFIG_PATH) as f:
config = json.load(f) config = json.load(f)
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -306,12 +315,20 @@ if __name__ == "__main__":
reading.add_argument('-to', dest='end_date', metavar="DATE", help='View entries before this date') reading.add_argument('-to', dest='end_date', metavar="DATE", help='View entries before this date')
reading.add_argument('-and', dest='strict', action="store_true", help='Filter by tags using AND (default: OR)') reading.add_argument('-and', dest='strict', action="store_true", help='Filter by tags using AND (default: OR)')
reading.add_argument('-n', dest='limit', default=None, metavar="N", help='Shows the last n entries matching the filter', nargs="?", type=int) reading.add_argument('-n', dest='limit', default=None, metavar="N", help='Shows the last n entries matching the filter', nargs="?", type=int)
reading.add_argument('-json', dest='json', action="store_true", help='Returns a JSON-encoded version of the Journal')
reading = parser.add_argument_group('Export / Import', 'Options for transmogrifying your journal')
reading.add_argument('--json', dest='json', action="store_true", help='Returns a JSON-encoded version of the Journal')
reading.add_argument('--encrypt', dest='encrypt', action="store_true", help='Encrypts your existing journal with a new password')
reading.add_argument('--decrypt', dest='decrypt', action="store_true", help='Decrypts your journal and stores it in plain text')
args = parser.parse_args() args = parser.parse_args()
# Guess mode # Guess mode
compose = True compose = True
if args.start_date or args.end_date or args.limit or args.json or args.strict: export = False
if args.json or args.decrypt or args.encrypt:
compose = False
export = True
elif args.start_date or args.end_date or args.limit or args.strict:
# Any sign of displaying stuff? # Any sign of displaying stuff?
compose = False compose = False
elif not args.date and args.text and all(word[0] in config['tagsymbols'] for word in args.text): elif not args.date and args.text and all(word[0] in config['tagsymbols'] for word in args.text):
@ -344,10 +361,25 @@ if __name__ == "__main__":
print("Entry added.") print("Entry added.")
journal.write() journal.write()
else: # read mode elif not export: # read mode
journal.filter(tags=args.text, start_date=args.start_date, end_date=args.end_date, strict=args.strict) journal.filter(tags=args.text, start_date=args.start_date, end_date=args.end_date, strict=args.strict)
journal.limit(args.limit) journal.limit(args.limit)
if args.json:
print(journal.to_json())
else:
print(journal) print(journal)
elif args.json: # export to json
print(journal.to_json())
elif args.encrypt:
journal.config['encrypt'] = True
journal.config['password'] = ""
journal.make_key(prompt="Enter new password:")
journal.write()
journal.save_config()
print("Journal encrypted to %s." % journal.config['journal'])
elif args.decrypt:
journal.config['encrypt'] = False
journal.config['password'] = ""
journal.write()
journal.save_config()
print("Journal decrypted to %s." % journal.config['journal'])