mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 16:48:31 +02:00
Work on cryptography
This commit is contained in:
parent
e75e3d73a0
commit
a1b5a4099e
11 changed files with 69 additions and 17 deletions
13
features/data/configs/encrypted_old.json
Normal file
13
features/data/configs/encrypted_old.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"default_hour": 9,
|
||||
"default_minute": 0,
|
||||
"editor": "",
|
||||
"encrypt": true,
|
||||
"highlight": true,
|
||||
"journals": {
|
||||
"default": "features/journals/encrypted_jrnl-1-9-5.journal"
|
||||
},
|
||||
"linewrap": 80,
|
||||
"tagsymbols": "@",
|
||||
"timeformat": "%Y-%m-%d %H:%M"
|
||||
}
|
|
@ -4,8 +4,7 @@ editor: ''
|
|||
encrypt: true
|
||||
highlight: true
|
||||
journals:
|
||||
default: features/journals/encrypted.journal
|
||||
default: features/journals/encrypted_jrnl1-9-5.journal
|
||||
linewrap: 80
|
||||
password: bad doggie no biscuit
|
||||
tagsymbols: '@'
|
||||
timeformat: '%Y-%m-%d %H:%M'
|
Binary file not shown.
|
@ -1,11 +1,16 @@
|
|||
Feature: Encrypted journals
|
||||
|
||||
Scenario: Loading an encrypted journal
|
||||
Given we use the config "encrypted.yaml"
|
||||
When we run "jrnl -n 1" and enter "bad doggie no biscuit"
|
||||
Then we should see the message "Password"
|
||||
and the output should contain "2013-06-10 15:40 Life is good"
|
||||
|
||||
Scenario: Upgrading a journal encrypted with jrnl 1.x
|
||||
Given we use the config "encrypted_old.json"
|
||||
When we run "jrnl -n 1" and enter "Y"
|
||||
Then we should see the message "Password"
|
||||
and the output should contain "2013-06-10 15:40 Life is good"
|
||||
|
||||
Scenario: Decrypting a journal
|
||||
Given we use the config "encrypted.yaml"
|
||||
When we run "jrnl --decrypt" and enter "bad doggie no biscuit"
|
||||
|
|
|
@ -50,9 +50,10 @@ def open_journal(journal_name="default"):
|
|||
def set_config(context, config_file):
|
||||
full_path = os.path.join("features/configs", config_file)
|
||||
install.CONFIG_FILE_PATH = os.path.abspath(full_path)
|
||||
# Add jrnl version to file
|
||||
with open(install.CONFIG_FILE_PATH, 'a') as cf:
|
||||
cf.write("version: {}".format(__version__))
|
||||
if config_file.endswith("yaml"):
|
||||
# Add jrnl version to file for 2.x journals
|
||||
with open(install.CONFIG_FILE_PATH, 'a') as cf:
|
||||
cf.write("version: {}".format(__version__))
|
||||
|
||||
|
||||
@when('we run "{command}" and enter')
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
from . import Journal, util
|
||||
from cryptography.fernet import Fernet, InvalidToken
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
import base64
|
||||
from passlib.hash import pbkdf2_sha256
|
||||
import os
|
||||
|
||||
|
||||
def make_key(password):
|
||||
derived_key = pbkdf2_sha256.encrypt(password.encode("utf-8"), rounds=10000, salt_size=32)
|
||||
return base64.urlsafe_b64encode(derived_key)
|
||||
kdf = PBKDF2HMAC(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=32,
|
||||
salt=os.urandom(16),
|
||||
iterations=100000,
|
||||
backend=default_backend()
|
||||
)
|
||||
key = kdf.derive(password)
|
||||
return base64.urlsafe_b64encode(key)
|
||||
|
||||
|
||||
class EncryptedJournal(Journal.Journal):
|
||||
|
@ -21,14 +31,15 @@ class EncryptedJournal(Journal.Journal):
|
|||
def validate_password(password):
|
||||
key = make_key(password)
|
||||
try:
|
||||
return Fernet(key).decrypt(journal_encrypted)
|
||||
except InvalidToken:
|
||||
return Fernet(key).decrypt(journal_encrypted).decode('utf-8')
|
||||
except (InvalidToken, IndexError):
|
||||
print base64.urlsafe_b64decode(journal_encrypted)
|
||||
return None
|
||||
|
||||
return util.get_password(keychain=self.name, validator=validate_password)
|
||||
|
||||
def _store(self, filename, text):
|
||||
key = make_key(self.config['password'])
|
||||
journal = Fernet(key).encrypt(text)
|
||||
journal = Fernet(key).encrypt(text.encode('utf-8'))
|
||||
with open(filename, 'w') as f:
|
||||
f.write(journal)
|
||||
|
|
|
@ -45,7 +45,7 @@ class Journal(object):
|
|||
def write(self, filename=None):
|
||||
"""Dumps the journal into the config file, overwriting it"""
|
||||
filename = filename or self.config['journal']
|
||||
text = u"\n".join([e.__unicode__() for e in self.entries])
|
||||
text = "\n".join([e.__unicode__() for e in self.entries])
|
||||
self._store(filename, text)
|
||||
|
||||
def _load(self, filename):
|
||||
|
|
|
@ -131,7 +131,10 @@ def run(manual_args=None):
|
|||
config = install.load_or_install_jrnl()
|
||||
|
||||
if args.ls:
|
||||
print(util.py2encode(list_journals(config)))
|
||||
util.prnt(u"Journals defined in {}".format(install.CONFIG_FILE_PATH))
|
||||
ml = min(max(len(k) for k in config['journals']), 20)
|
||||
for journal, cfg in config['journals'].items():
|
||||
print " * {:{}} -> {}".format(journal, ml, cfg['journal'] if isinstance(cfg, dict) else cfg)
|
||||
sys.exit(0)
|
||||
|
||||
original_config = config.copy()
|
||||
|
|
|
@ -77,8 +77,8 @@ def load_or_install_jrnl():
|
|||
config_path = CONFIG_FILE_PATH if os.path.exists(CONFIG_FILE_PATH) else CONFIG_FILE_PATH_FALLBACK
|
||||
if os.path.exists(config_path):
|
||||
config = util.load_config(CONFIG_FILE_PATH)
|
||||
upgrade_config(config)
|
||||
upgrade.upgrade_jrnl_if_necessary(CONFIG_FILE_PATH)
|
||||
upgrade_config(config)
|
||||
return config
|
||||
else:
|
||||
install()
|
||||
|
|
|
@ -4,12 +4,15 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|||
import hashlib
|
||||
import util
|
||||
from . import __version__
|
||||
from . import EncryptedJournal
|
||||
import sys
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
|
||||
def upgrade_encrypted_journal(filename, key_plain):
|
||||
"""Decrypts a journal in memory using the jrnl 1.x encryption scheme
|
||||
and returns it in plain text."""
|
||||
util.prompt( "UPGRADING JOURNAL")
|
||||
with open(filename) as f:
|
||||
iv_cipher = f.read()
|
||||
iv, cipher = iv_cipher[:16], iv_cipher[16:]
|
||||
|
@ -23,6 +26,11 @@ def upgrade_encrypted_journal(filename, key_plain):
|
|||
plain += unpadder.finalize()
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
key = EncryptedJournal.make_key(key_plain)
|
||||
journal = Fernet(key).encrypt(plain.encode('utf-8'))
|
||||
with open(filename, 'w') as f:
|
||||
f.write(journal)
|
||||
return plain
|
||||
|
||||
|
||||
|
@ -44,12 +52,15 @@ def upgrade_jrnl_if_necessary(config_path):
|
|||
plain_journals = {}
|
||||
for journal, journal_conf in config['journals'].items():
|
||||
if isinstance(journal_conf, dict):
|
||||
if journal_conf.get("encrypted"):
|
||||
if journal_conf.get("encrypt"):
|
||||
encrypted_journals[journal] = journal_conf.get("journal")
|
||||
else:
|
||||
plain_journals[journal] = journal_conf.get("journal")
|
||||
else:
|
||||
plain_journals[journal] = journal_conf.get("journal")
|
||||
if config.get('encrypt'):
|
||||
encrypted_journals[journal] = journal_conf
|
||||
else:
|
||||
plain_journals[journal] = journal_conf
|
||||
if encrypted_journals:
|
||||
util.prompt("Following encrypted journals will be upgraded to jrnl {}:".format(__version__))
|
||||
for journal, path in encrypted_journals.items():
|
||||
|
@ -63,3 +74,7 @@ def upgrade_jrnl_if_necessary(config_path):
|
|||
if not cont:
|
||||
util.prompt("jrnl NOT upgraded, exiting.")
|
||||
sys.exit(1)
|
||||
|
||||
for journal, path in encrypted_journals.items():
|
||||
util.prompt("Enter password for {} journal (stored in {}).".format(journal, path))
|
||||
util.get_password(keychain=journal, validator=lambda pwd: upgrade_encrypted_journal(path, pwd))
|
||||
|
|
|
@ -79,6 +79,11 @@ def py2encode(s):
|
|||
return s.encode("utf-8") if PY2 and type(s) is unicode else s
|
||||
|
||||
|
||||
def prnt(s):
|
||||
"""Encode and print a string"""
|
||||
STDOUT.write(u(s + "\n"))
|
||||
|
||||
|
||||
def prompt(msg):
|
||||
"""Prints a message to the std err stream defined in util."""
|
||||
if not msg.endswith("\n"):
|
||||
|
|
Loading…
Add table
Reference in a new issue