mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-13 09:58:31 +02:00
more cleaning
This commit is contained in:
parent
bb1f263d6c
commit
a76f066a7c
8 changed files with 101 additions and 95 deletions
|
@ -1,49 +0,0 @@
|
|||
# Copyright © 2012-2022 jrnl contributors
|
||||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
import hashlib
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
|
||||
from jrnl.Journal import LegacyJournal
|
||||
|
||||
|
||||
class LegacyEncryptedJournal(LegacyJournal):
|
||||
"""Legacy class to support opening journals encrypted with the jrnl 1.x
|
||||
standard. You'll not be able to save these journals anymore."""
|
||||
|
||||
def __init__(self, name="default", **kwargs):
|
||||
super().__init__(name, **kwargs)
|
||||
self.config["encrypt"] = True
|
||||
self.password = None
|
||||
|
||||
def _load(self, filename):
|
||||
with open(filename, "rb") as f:
|
||||
journal_encrypted = f.read()
|
||||
iv, cipher = journal_encrypted[:16], journal_encrypted[16:]
|
||||
|
||||
def decrypt_journal(password):
|
||||
decryption_key = hashlib.sha256(password.encode("utf-8")).digest()
|
||||
decryptor = Cipher(
|
||||
algorithms.AES(decryption_key), modes.CBC(iv), default_backend()
|
||||
).decryptor()
|
||||
try:
|
||||
plain_padded = decryptor.update(cipher) + decryptor.finalize()
|
||||
self.password = password
|
||||
if plain_padded[-1] in (" ", 32):
|
||||
# Ancient versions of jrnl. Do not judge me.
|
||||
return plain_padded.decode("utf-8").rstrip(" ")
|
||||
else:
|
||||
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
||||
plain = unpadder.update(plain_padded) + unpadder.finalize()
|
||||
return plain.decode("utf-8")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
if self.password:
|
||||
return decrypt_journal(self.password)
|
||||
return decrypt_content(keychain=self.name, decrypt_func=decrypt_journal)
|
|
@ -6,7 +6,6 @@ import logging
|
|||
import os
|
||||
import re
|
||||
|
||||
from jrnl import EncryptedJournal
|
||||
from jrnl import Entry
|
||||
from jrnl import time
|
||||
from jrnl.encryption import determine_encryption_method
|
||||
|
@ -48,7 +47,7 @@ class Journal:
|
|||
self.search_tags = None # Store tags we're highlighting
|
||||
self.name = name
|
||||
self.entries = []
|
||||
self._encryption_method = None
|
||||
self.encryption_method = None
|
||||
|
||||
def __len__(self):
|
||||
"""Returns the number of entries"""
|
||||
|
@ -81,21 +80,20 @@ class Journal:
|
|||
self.sort()
|
||||
|
||||
def _get_encryption_method(self):
|
||||
self._encryption_method = determine_encryption_method(self.config["encrypt"])(
|
||||
self.config
|
||||
)
|
||||
encryption_method = determine_encryption_method(self.config["encrypt"])
|
||||
self.encryption_method = encryption_method(self.config)
|
||||
|
||||
def _decrypt(self, text):
|
||||
if not self._encryption_method:
|
||||
if self.encryption_method is None:
|
||||
self._get_encryption_method()
|
||||
|
||||
return self._encryption_method.decrypt(text)
|
||||
return self.encryption_method.decrypt(text)
|
||||
|
||||
def _encrypt(self, text):
|
||||
if not self._encryption_method:
|
||||
if self.encryption_method is None:
|
||||
self._get_encryption_method()
|
||||
|
||||
return self._encryption_method.encrypt(text)
|
||||
return self.encryption_method.encrypt(text)
|
||||
|
||||
def open(self, filename=None):
|
||||
"""Opens the journal file defined in the config and parses it into a list of Entries.
|
||||
|
@ -488,5 +486,6 @@ def open_journal(journal_name, config, legacy=False):
|
|||
return PlainJournal(journal_name, **config).open()
|
||||
|
||||
if legacy:
|
||||
return EncryptedJournal.LegacyEncryptedJournal(journal_name, **config).open()
|
||||
config["encrypt"] = "jrnlv1"
|
||||
return LegacyJournal(journal_name, **config).open()
|
||||
return PlainJournal(journal_name, **config).open()
|
||||
|
|
|
@ -14,6 +14,7 @@ run.
|
|||
Also, please note that all (non-builtin) imports should be scoped to each function to
|
||||
avoid any possible overhead for these standalone commands.
|
||||
"""
|
||||
import logging
|
||||
import platform
|
||||
import sys
|
||||
|
||||
|
@ -22,7 +23,6 @@ from jrnl.messages import Message
|
|||
from jrnl.messages import MsgStyle
|
||||
from jrnl.messages import MsgText
|
||||
from jrnl.output import print_msg
|
||||
from jrnl.prompt import create_password
|
||||
|
||||
|
||||
def preconfig_diagnostic(_):
|
||||
|
@ -79,7 +79,6 @@ def postconfig_encrypt(args, config, original_config, **kwargs):
|
|||
"""
|
||||
from jrnl.config import update_config
|
||||
from jrnl.install import save_config
|
||||
from jrnl.Journal import PlainJournal
|
||||
from jrnl.Journal import open_journal
|
||||
|
||||
# Open the journal
|
||||
|
@ -97,21 +96,21 @@ def postconfig_encrypt(args, config, original_config, **kwargs):
|
|||
)
|
||||
)
|
||||
|
||||
new_journal = PlainJournal.from_journal(journal)
|
||||
|
||||
# If journal is encrypted, create new password
|
||||
if journal.config["encrypt"] is True:
|
||||
logging.debug("Journal already encrypted. Re-encrypting...")
|
||||
print(f"Journal {journal.name} is already encrypted. Create a new password.")
|
||||
new_journal.password = create_password(new_journal.name)
|
||||
|
||||
logging.debug("Clearing encryption method...")
|
||||
journal.encryption_method = None
|
||||
journal.config["encrypt"] = True
|
||||
new_journal.write(args.filename)
|
||||
journal.write(args.filename)
|
||||
|
||||
print_msg(
|
||||
Message(
|
||||
MsgText.JournalEncryptedTo,
|
||||
MsgStyle.NORMAL,
|
||||
{"path": args.filename or new_journal.config["journal"]},
|
||||
{"path": args.filename or journal.config["journal"]},
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -127,19 +126,20 @@ def postconfig_decrypt(args, config, original_config, **kwargs):
|
|||
"""Decrypts into new file. If filename is not set, we encrypt the journal file itself."""
|
||||
from jrnl.config import update_config
|
||||
from jrnl.install import save_config
|
||||
from jrnl.Journal import PlainJournal
|
||||
from jrnl.Journal import open_journal
|
||||
|
||||
journal = open_journal(args.journal_name, config)
|
||||
journal.config["encrypt"] = False
|
||||
|
||||
new_journal = PlainJournal.from_journal(journal)
|
||||
new_journal.write(args.filename)
|
||||
logging.debug("Clearing encryption method...")
|
||||
journal.config["encrypt"] = False
|
||||
journal.encryption_method = None
|
||||
|
||||
journal.write(args.filename)
|
||||
print_msg(
|
||||
Message(
|
||||
MsgText.JournalDecryptedTo,
|
||||
MsgStyle.NORMAL,
|
||||
{"path": args.filename or new_journal.config["journal"]},
|
||||
{"path": args.filename or journal.config["journal"]},
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -9,15 +9,15 @@ class BaseEncryption(ABC):
|
|||
self._config = config
|
||||
|
||||
@abstractmethod
|
||||
def encrypt(self, text: str) -> bytes:
|
||||
def encrypt(self, text: str) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def decrypt(self, text: bytes) -> str | None:
|
||||
def decrypt(self, text: str) -> str | None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _encrypt(self, text: bytes) -> str:
|
||||
def _encrypt(self, text: str) -> str:
|
||||
"""
|
||||
This is needed because self.decrypt might need
|
||||
to perform actions (e.g. prompt for password)
|
||||
|
@ -26,7 +26,7 @@ class BaseEncryption(ABC):
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _decrypt(self, text: bytes) -> str:
|
||||
def _decrypt(self, text: str) -> str:
|
||||
"""
|
||||
This is needed because self.decrypt might need
|
||||
to perform actions (e.g. prompt for password)
|
||||
|
|
|
@ -7,6 +7,7 @@ from jrnl.messages import Message
|
|||
from jrnl.messages import MsgStyle
|
||||
from jrnl.messages import MsgText
|
||||
from jrnl.output import print_msg
|
||||
from jrnl.prompt import create_password
|
||||
|
||||
|
||||
class BasePasswordEncryption(BaseEncryption):
|
||||
|
@ -14,26 +15,37 @@ class BasePasswordEncryption(BaseEncryption):
|
|||
_journal_name: str
|
||||
_max_attempts: int
|
||||
_password: str | None
|
||||
_encoding: str
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._attempts = 0
|
||||
self._max_attempts = 3
|
||||
self._password = None
|
||||
self._encoding = "utf-8"
|
||||
|
||||
# Check keyring first to be ready for decryption
|
||||
get_keyring_password(self._config["journal"])
|
||||
|
||||
# Prompt for password if keyring didn't work
|
||||
if self._password is None:
|
||||
if self.password is None:
|
||||
self._prompt_password()
|
||||
|
||||
def encrypt(self, text: str) -> bytes:
|
||||
@property
|
||||
def password(self):
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
self._password = value
|
||||
|
||||
def encrypt(self, text):
|
||||
if self.password is None:
|
||||
self.password = create_password(self._config["journal"])
|
||||
return self._encrypt(text)
|
||||
|
||||
def decrypt(self, text: bytes) -> str:
|
||||
encoded_text = text.encode(self._encoding)
|
||||
while (result := self._decrypt(encoded_text)) is None:
|
||||
def decrypt(self, text):
|
||||
while (result := self._decrypt(text)) is None:
|
||||
self._prompt_password()
|
||||
|
||||
return result
|
||||
|
@ -48,7 +60,7 @@ class BasePasswordEncryption(BaseEncryption):
|
|||
print_msg(Message(MsgText.WrongPasswordTryAgain, MsgStyle.WARNING))
|
||||
|
||||
self._attempts += 1
|
||||
self._password = print_msg(
|
||||
self.password = print_msg(
|
||||
Message(MsgText.Password, MsgStyle.PROMPT),
|
||||
get_input=True,
|
||||
hide_input=True,
|
||||
|
|
|
@ -1,7 +1,38 @@
|
|||
# Copyright © 2012-2022 jrnl contributors
|
||||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
from jrnl.encryption.BaseEncryption import BaseEncryption
|
||||
import hashlib
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
|
||||
from jrnl.encryption.BasePasswordEncryption import BasePasswordEncryption
|
||||
|
||||
|
||||
class Jrnlv1Encryption(BaseEncryption):
|
||||
pass
|
||||
class Jrnlv1Encryption(BasePasswordEncryption):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# def _encrypt(self, text: str) -> bytes:
|
||||
# raise NotImplementedError
|
||||
|
||||
def _decrypt(self, text: bytes) -> str | None:
|
||||
iv, cipher = text[:16], text[16:]
|
||||
decryption_key = hashlib.sha256(self._password.encode("utf-8")).digest()
|
||||
decryptor = Cipher(
|
||||
algorithms.AES(decryption_key), modes.CBC(iv), default_backend()
|
||||
).decryptor()
|
||||
try:
|
||||
plain_padded = decryptor.update(cipher) + decryptor.finalize()
|
||||
# self._password = password
|
||||
if plain_padded[-1] in (" ", 32):
|
||||
# Ancient versions of jrnl. Do not judge me.
|
||||
return plain_padded.decode("utf-8").rstrip(" ")
|
||||
else:
|
||||
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
||||
plain = unpadder.update(plain_padded) + unpadder.finalize()
|
||||
return plain.decode("utf-8")
|
||||
except ValueError:
|
||||
return None
|
||||
|
|
|
@ -13,19 +13,25 @@ from .BasePasswordEncryption import BasePasswordEncryption
|
|||
|
||||
class Jrnlv2Encryption(BasePasswordEncryption):
|
||||
_salt: bytes
|
||||
_encoding: str
|
||||
_key: bytes
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
# Salt is hard-coded
|
||||
self._salt: bytes = b"\xf2\xd5q\x0e\xc1\x8d.\xde\xdc\x8e6t\x89\x04\xce\xf8"
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Salt is hard-coded
|
||||
self._salt = b"\xf2\xd5q\x0e\xc1\x8d.\xde\xdc\x8e6t\x89\x04\xce\xf8"
|
||||
self._encoding = "utf-8"
|
||||
@property
|
||||
def password(self):
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
self._password = value
|
||||
self._make_key()
|
||||
|
||||
def _make_key(self) -> None:
|
||||
password = self._password.encode(self._encoding)
|
||||
password = self.password.encode(self._encoding)
|
||||
kdf = PBKDF2HMAC(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=32,
|
||||
|
@ -36,11 +42,19 @@ class Jrnlv2Encryption(BasePasswordEncryption):
|
|||
key = kdf.derive(password)
|
||||
self._key = base64.urlsafe_b64encode(key)
|
||||
|
||||
def _encrypt(self, text: str) -> bytes:
|
||||
return Fernet(self._key).encrypt(text.encode(self._encoding))
|
||||
def _encrypt(self, text: str) -> str:
|
||||
return (
|
||||
Fernet(self._key)
|
||||
.encrypt(text.encode(self._encoding))
|
||||
.decode(self._encoding)
|
||||
)
|
||||
|
||||
def _decrypt(self, text: bytes) -> str | None:
|
||||
def _decrypt(self, text: str) -> str | None:
|
||||
try:
|
||||
return Fernet(self._key).decrypt((text)).decode(self._encoding)
|
||||
return (
|
||||
Fernet(self._key)
|
||||
.decrypt(text.encode(self._encoding))
|
||||
.decode(self._encoding)
|
||||
)
|
||||
except (InvalidToken, IndexError):
|
||||
return None
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import os
|
||||
|
||||
from jrnl import Journal
|
||||
from jrnl import PlainJournal
|
||||
from jrnl import __version__
|
||||
from jrnl.config import is_config_json
|
||||
from jrnl.config import load_config
|
||||
|
@ -132,7 +131,7 @@ def upgrade_jrnl(config_path):
|
|||
old_journal = Journal.open_journal(
|
||||
journal_name, scope_config(config, journal_name), legacy=True
|
||||
)
|
||||
all_journals.append(PlainJournal.from_journal(old_journal))
|
||||
all_journals.append(Journal.PlainJournal.from_journal(old_journal))
|
||||
|
||||
for journal_name, path in plain_journals.items():
|
||||
print_msg(
|
||||
|
|
Loading…
Add table
Reference in a new issue