mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-17 11:38:32 +02:00
Updated docs from master
This commit is contained in:
parent
8a845e778f
commit
fee1a95fcb
6 changed files with 53 additions and 22 deletions
|
@ -29,7 +29,7 @@ If you don't initially store the password in the keychain but decide to do so at
|
||||||
Manual decryption
|
Manual decryption
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm. The key used for encryption is the SHA-256-hash of your password, and the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. So, to decrypt a journal file in python, run::
|
Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm in CBC. The key used for encryption is the SHA-256-hash of your password, the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. The plain text is encoded in UTF-8 and padded according to PKCS#7 before being encrypted. So, to decrypt a journal file in python, run::
|
||||||
|
|
||||||
import hashlib, Crypto.Cipher
|
import hashlib, Crypto.Cipher
|
||||||
key = hashlib.sha256(my_password).digest()
|
key = hashlib.sha256(my_password).digest()
|
||||||
|
@ -37,3 +37,5 @@ Should you ever want to decrypt your journal manually, you can do so with any pr
|
||||||
cipher = f.read()
|
cipher = f.read()
|
||||||
crypto = AES.new(key, AES.MODE_CBC, iv = cipher[:16])
|
crypto = AES.new(key, AES.MODE_CBC, iv = cipher[:16])
|
||||||
plain = crypto.decrypt(cipher[16:])
|
plain = crypto.decrypt(cipher[16:])
|
||||||
|
plain = plain.strip(plain[-1])
|
||||||
|
plain = plain.decode("utf-8")
|
||||||
|
|
|
@ -24,6 +24,7 @@ import plistlib
|
||||||
import pytz
|
import pytz
|
||||||
import uuid
|
import uuid
|
||||||
import tzlocal
|
import tzlocal
|
||||||
|
from xml.parsers.expat import ExpatError
|
||||||
|
|
||||||
|
|
||||||
class Journal(object):
|
class Journal(object):
|
||||||
|
@ -65,11 +66,19 @@ class Journal(object):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
util.prompt("ERROR: Your journal file seems to be corrupted. You do have a backup, don't you?")
|
util.prompt("ERROR: Your journal file seems to be corrupted. You do have a backup, don't you?")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
padding = " ".encode("utf-8")
|
|
||||||
if not plain.endswith(padding): # Journals are always padded
|
padding_length = util.byte2int(plain[-1])
|
||||||
|
if padding_length > AES.block_size and padding_length != 32:
|
||||||
|
# 32 is the space character and is kept for backwards compatibility
|
||||||
|
return None
|
||||||
|
elif padding_length == 32:
|
||||||
|
plain = plain.strip()
|
||||||
|
elif plain[-padding_length:] != util.int2byte(padding_length) * padding_length:
|
||||||
|
# Invalid padding!
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return plain.decode("utf-8")
|
plain = plain[:-padding_length]
|
||||||
|
return plain.decode("utf-8")
|
||||||
|
|
||||||
def _encrypt(self, plain):
|
def _encrypt(self, plain):
|
||||||
"""Encrypt a plaintext string using self.key as the key"""
|
"""Encrypt a plaintext string using self.key as the key"""
|
||||||
|
@ -79,7 +88,8 @@ class Journal(object):
|
||||||
iv = Random.new().read(AES.block_size)
|
iv = Random.new().read(AES.block_size)
|
||||||
crypto = AES.new(self.key, AES.MODE_CBC, iv)
|
crypto = AES.new(self.key, AES.MODE_CBC, iv)
|
||||||
plain = plain.encode("utf-8")
|
plain = plain.encode("utf-8")
|
||||||
plain += b" " * (AES.block_size - len(plain) % AES.block_size)
|
padding_length = AES.block_size - len(plain) % AES.block_size
|
||||||
|
plain += util.int2byte(padding_length) * padding_length
|
||||||
return iv + crypto.encrypt(plain)
|
return iv + crypto.encrypt(plain)
|
||||||
|
|
||||||
def make_key(self, password):
|
def make_key(self, password):
|
||||||
|
@ -280,7 +290,7 @@ class Journal(object):
|
||||||
raw = raw.replace('\\n ', '\n').replace('\\n', '\n')
|
raw = raw.replace('\\n ', '\n').replace('\\n', '\n')
|
||||||
starred = False
|
starred = False
|
||||||
# Split raw text into title and body
|
# Split raw text into title and body
|
||||||
sep = re.search("\n|[\?!.]+ *\n?", raw)
|
sep = re.search("\n|[\?!.]+ +\n?", raw)
|
||||||
title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
|
title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
|
||||||
starred = False
|
starred = False
|
||||||
if not date:
|
if not date:
|
||||||
|
@ -332,20 +342,24 @@ class DayOne(Journal):
|
||||||
self.entries = []
|
self.entries = []
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
with open(filename, 'rb') as plist_entry:
|
with open(filename, 'rb') as plist_entry:
|
||||||
dict_entry = plistlib.readPlist(plist_entry)
|
|
||||||
try:
|
try:
|
||||||
timezone = pytz.timezone(dict_entry['Time Zone'])
|
dict_entry = plistlib.readPlist(plist_entry)
|
||||||
except (KeyError, pytz.exceptions.UnknownTimeZoneError):
|
except ExpatError:
|
||||||
timezone = tzlocal.get_localzone()
|
pass
|
||||||
date = dict_entry['Creation Date']
|
else:
|
||||||
date = date + timezone.utcoffset(date)
|
try:
|
||||||
raw = dict_entry['Entry Text']
|
timezone = pytz.timezone(dict_entry['Time Zone'])
|
||||||
sep = re.search("[\n!?.]+", raw)
|
except (KeyError, pytz.exceptions.UnknownTimeZoneError):
|
||||||
title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
|
timezone = tzlocal.get_localzone()
|
||||||
entry = Entry.Entry(self, date, title, body, starred=dict_entry["Starred"])
|
date = dict_entry['Creation Date']
|
||||||
entry.uuid = dict_entry["UUID"]
|
date = date + timezone.utcoffset(date, is_dst=False)
|
||||||
entry.tags = [self.config['tagsymbols'][0] + tag for tag in dict_entry.get("Tags", [])]
|
raw = dict_entry['Entry Text']
|
||||||
self.entries.append(entry)
|
sep = re.search("\n|[\?!.]+ +\n?", raw)
|
||||||
|
title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
|
||||||
|
entry = Entry.Entry(self, date, title, body, starred=dict_entry["Starred"])
|
||||||
|
entry.uuid = dict_entry["UUID"]
|
||||||
|
entry.tags = [self.config['tagsymbols'][0] + tag for tag in dict_entry.get("Tags", [])]
|
||||||
|
self.entries.append(entry)
|
||||||
self.sort()
|
self.sort()
|
||||||
|
|
||||||
def write(self):
|
def write(self):
|
||||||
|
|
|
@ -8,7 +8,7 @@ jrnl is a simple journal application for your command line.
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
__title__ = 'jrnl'
|
__title__ = 'jrnl'
|
||||||
__version__ = '1.7.19'
|
__version__ = '1.7.22'
|
||||||
__author__ = 'Manuel Ebert'
|
__author__ = 'Manuel Ebert'
|
||||||
__license__ = 'MIT License'
|
__license__ = 'MIT License'
|
||||||
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
|
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
|
||||||
|
|
|
@ -233,6 +233,9 @@ def run(manual_args=None):
|
||||||
install.save_config(original_config, config_path=CONFIG_PATH)
|
install.save_config(original_config, config_path=CONFIG_PATH)
|
||||||
|
|
||||||
elif args.edit:
|
elif args.edit:
|
||||||
|
if not config['editor']:
|
||||||
|
util.prompt(u"[You need to specify an editor in {0} to use the --edit function.]".format(CONFIG_PATH))
|
||||||
|
sys.exit(1)
|
||||||
other_entries = [e for e in old_entries if e not in journal.entries]
|
other_entries = [e for e in old_entries if e not in journal.entries]
|
||||||
# Edit
|
# Edit
|
||||||
old_num_entries = len(journal)
|
old_num_entries = len(journal)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from __future__ import absolute_import
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from .util import u, slugify
|
from .util import u, slugify
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
|
||||||
def get_tags_count(journal):
|
def get_tags_count(journal):
|
||||||
|
@ -81,7 +82,7 @@ def export(journal, format, output=None):
|
||||||
content = maps[format](journal)
|
content = maps[format](journal)
|
||||||
if output:
|
if output:
|
||||||
try:
|
try:
|
||||||
with open(output, 'w') as f:
|
with codecs.open(output, "w", "utf-8") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
return "[Journal exported to {0}]".format(output)
|
return "[Journal exported to {0}]".format(output)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
|
@ -101,6 +102,6 @@ def write_files(journal, path, format):
|
||||||
content = e.to_md()
|
content = e.to_md()
|
||||||
elif format == 'txt':
|
elif format == 'txt':
|
||||||
content = u(e)
|
content = u(e)
|
||||||
with open(full_path, 'w') as f:
|
with codecs.open(full_path, "w", "utf-8") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
return "[Journal exported individual files in {0}]".format(path)
|
return "[Journal exported individual files in {0}]".format(path)
|
||||||
|
|
11
jrnl/util.py
11
jrnl/util.py
|
@ -141,3 +141,14 @@ def slugify(string):
|
||||||
slug = re.sub(r'[-\s]+', '-', no_punctuation)
|
slug = re.sub(r'[-\s]+', '-', no_punctuation)
|
||||||
return u(slug)
|
return u(slug)
|
||||||
|
|
||||||
|
def int2byte(i):
|
||||||
|
"""Converts an integer to a byte.
|
||||||
|
This is equivalent to chr() in Python 2 and bytes((i,)) in Python 3."""
|
||||||
|
return chr(i) if PY2 else bytes((i,))
|
||||||
|
|
||||||
|
|
||||||
|
def byte2int(b):
|
||||||
|
"""Converts a byte to an integer.
|
||||||
|
This is equivalent to ord(bs[0]) on Python 2 and bs[0] on Python 3."""
|
||||||
|
return ord(b)if PY2 else b
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue