Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving performance #15

Merged
merged 1 commit into from
Dec 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions crypt4gh/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
import logging
import errno

from nacl.exceptions import InvalidkeyError, BadSignatureError, CryptoError
from cryptography.exceptions import InvalidTag
from nacl.exceptions import (InvalidkeyError,
BadSignatureError,
CryptoError)

LOG = logging.getLogger(__name__)

Expand All @@ -36,7 +37,7 @@ def exit_on_invalid_passphrase(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except (InvalidTag) as e:
except CryptoError as e:
LOG.error('Exiting for %r', e)
print('Invalid Key or Passphrase', file=sys.stderr)
sys.exit(2)
Expand Down
29 changes: 14 additions & 15 deletions crypt4gh/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import os
import logging
from itertools import chain
from types import GeneratorType
# from types import GeneratorType

from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from nacl.bindings import crypto_kx_client_session_keys, crypto_kx_server_session_keys
from nacl.bindings import (crypto_kx_client_session_keys,
crypto_kx_server_session_keys,
crypto_aead_chacha20poly1305_ietf_encrypt,
crypto_aead_chacha20poly1305_ietf_decrypt)
from nacl.exceptions import CryptoError
from nacl.public import PrivateKey
from cryptography.exceptions import InvalidTag

from . import SEGMENT_SIZE, VERSION

Expand Down Expand Up @@ -182,11 +184,9 @@ def encrypt_X25519_Chacha20_Poly1305(data, seckey, recipient_pubkey):
LOG.debug('shared key: %s', shared_key.hex())

# Chacha20_Poly1305
engine = ChaCha20Poly1305(shared_key)
nonce = os.urandom(12)
return (pubkey +
nonce +
engine.encrypt(nonce, data, None)) # No add
encrypted_data = crypto_aead_chacha20poly1305_ietf_encrypt(data, None, nonce, shared_key) # no add
return (pubkey + nonce + encrypted_data)

def decrypt_X25519_Chacha20_Poly1305(encrypted_part, privkey, sender_pubkey=None):
#LOG.debug('----------- Encrypted data: %s', encrypted_part.hex())
Expand All @@ -210,8 +210,7 @@ def decrypt_X25519_Chacha20_Poly1305(encrypted_part, privkey, sender_pubkey=None
LOG.debug('shared key: %s', shared_key.hex())

# Chacha20_Poly1305
engine = ChaCha20Poly1305(shared_key)
return engine.decrypt(nonce, packet_data, None) # No add
return crypto_aead_chacha20poly1305_ietf_decrypt(packet_data, None, nonce, shared_key) # no add


def decrypt_packet(packet, keys, sender_pubkey=None):
Expand All @@ -233,7 +232,7 @@ def decrypt_packet(packet, keys, sender_pubkey=None):
try:
privkey, _ = key # must fit
return decrypt_X25519_Chacha20_Poly1305(packet[4:], privkey, sender_pubkey=sender_pubkey)
except InvalidTag as tag:
except CryptoError as tag:
LOG.error('Packet Decryption failed: %s', tag)
except Exception as e: # Any other error, like (IndexError, TypeError, ValueError)
LOG.error('Not a X25519 key: ignoring | %s', e)
Expand Down Expand Up @@ -305,8 +304,8 @@ def deconstruct(infile, keys, sender_pubkey=None):

Leaves the infile stream right after the header.

:return: a pair with a list of ciphers and a generator of lengths from an edit list (or None if there was no edit list).
:rtype: (list of ChaCha20Poly1305 ciphers, int generator or None)
:return: a pair with a list of session keys and a generator of lengths from an edit list (or None if there was no edit list).
:rtype: (list of bytes, int generator or None)

:raises: ValueError if the header could not be decrypted
"""
Expand All @@ -318,9 +317,9 @@ def deconstruct(infile, keys, sender_pubkey=None):

data_packets, edit_packet = partition_packets(packets)
# Parse returns the session key (since it should be method 0)
ciphers = [ChaCha20Poly1305(parse_enc_packet(packet)) for packet in data_packets]
session_keys = [parse_enc_packet(packet) for packet in data_packets]
edit_list = parse_edit_list_packet(edit_packet) if edit_packet else None
return ciphers, edit_list
return session_keys, edit_list

# -------------------------------------
# Header Re-Encryption
Expand Down
4 changes: 2 additions & 2 deletions crypt4gh/keys/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
from cryptography.exceptions import InvalidTag
from nacl.exceptions import CryptoError
from nacl.bindings.crypto_sign import crypto_sign_ed25519_pk_to_curve25519, crypto_sign_ed25519_sk_to_curve25519

from .kdf import derive_key
Expand Down Expand Up @@ -170,7 +170,7 @@ def parse_private_key(stream, callback):

if private_data[:4] != private_data[4:8]: # check don't pass
LOG.debug('Check: %s != %s', private_data[:4], private_data[4:8])
raise InvalidTag()
raise CryptoError()
private_data = io.BytesIO(private_data[8:])
# Note: we ignore the comment and padding after the priv blob
return _get_skpk_from_private_blob(private_data) # no need to unpad
Expand Down
49 changes: 25 additions & 24 deletions crypt4gh/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import io
import collections

from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.exceptions import InvalidTag
from nacl.bindings import (crypto_aead_chacha20poly1305_ietf_encrypt,
crypto_aead_chacha20poly1305_ietf_decrypt)
from nacl.exceptions import CryptoError


from . import SEGMENT_SIZE
from .exceptions import convert_error, close_on_broken_pipe
from .exceptions import close_on_broken_pipe
from . import header

LOG = logging.getLogger(__name__)
Expand All @@ -32,13 +34,13 @@
##
##############################################################

def _encrypt_segment(data, process, cipher):
def _encrypt_segment(data, process, key):
'''Utility function to generate a nonce, encrypt data with Chacha20, and authenticate it with Poly1305.'''

#LOG.debug("Segment [%d bytes]: %s..%s", len(data), data[:10], data[-10:])

nonce = os.urandom(12)
encrypted_data = cipher.encrypt(nonce, data, None) # No add
encrypted_data = crypto_aead_chacha20poly1305_ietf_encrypt(data, None, nonce, key) # no add
process(nonce) # after producing the segment, so we don't start outputing when an error occurs
process(encrypted_data)

Expand Down Expand Up @@ -81,7 +83,6 @@ def encrypt(keys, infile, outfile, offset=0, span=None):
# Preparing the encryption engine
encryption_method = 0 # only choice for this version
session_key = os.urandom(32) # we use one session key for all blocks
cipher = ChaCha20Poly1305(session_key) # create a new one in case an old one is not reset

# Output the header
LOG.debug('Creating Crypt4GH header')
Expand All @@ -108,11 +109,11 @@ def encrypt(keys, infile, outfile, offset=0, span=None):

if segment_len < SEGMENT_SIZE: # not a full segment
data = bytes(segment[:segment_len]) # to discard the bytes from the previous segments
_encrypt_segment(data, outfile.write, cipher)
_encrypt_segment(data, outfile.write, session_key)
break

data = bytes(segment) # this is a full segment
_encrypt_segment(data, outfile.write, cipher)
_encrypt_segment(data, outfile.write, session_key)

else: # we have a max size
assert( span )
Expand All @@ -128,10 +129,10 @@ def encrypt(keys, infile, outfile, offset=0, span=None):

if span < segment_len: # stop early
data = data[:span]
_encrypt_segment(data, outfile.write, cipher)
_encrypt_segment(data, outfile.write, session_key)
break

_encrypt_segment(data, outfile.write, cipher)
_encrypt_segment(data, outfile.write, session_key)

span -= segment_len

Expand All @@ -156,17 +157,17 @@ def cipher_chunker(f, size):
assert( ciphersegment_len > CIPHER_DIFF )
yield ciphersegment

def decrypt_block(ciphersegment, ciphers):
def decrypt_block(ciphersegment, session_keys):
# Trying the different session keys (via the cipher objects)
# Note: we could order them and if one fails, we move it at the end of the list
# So... LRU solution. For now, try them as they come.
nonce = ciphersegment[:12]
data = ciphersegment[12:]

for cipher in ciphers:
for key in session_keys:
try:
return cipher.decrypt(nonce, data, None) # No aad, and break the loop
except InvalidTag as tag:
return crypto_aead_chacha20poly1305_ietf_decrypt(data, None, nonce, key) # no add, and break the loop
except CryptoError as tag:
LOG.error('Decryption failed: %s', tag)
else: # no cipher worked: Bark!
raise ValueError('Could not decrypt that block')
Expand Down Expand Up @@ -214,7 +215,7 @@ def limited_output(offset=0, limit=None, process=None):
offset = 0 # reset offset


def body_decrypt(infile, ciphers, output, offset):
def body_decrypt(infile, session_keys, output, offset):
"""Decrypt the whole data portion.

We fast-forward if offset >= SEGMENT_SIZE.
Expand All @@ -231,16 +232,16 @@ def body_decrypt(infile, ciphers, output, offset):

try:
for ciphersegment in cipher_chunker(infile, CIPHER_SEGMENT_SIZE):
segment = decrypt_block(ciphersegment, ciphers)
segment = decrypt_block(ciphersegment, session_keys)
output.send(segment)
except ProcessingOver: # output raised it
pass


class DecryptedBuffer():
def __init__(self, fileobj, ciphers, output):
def __init__(self, fileobj, session_keys, output):
self.fileobj = fileobj
self.ciphers = ciphers
self.session_keys = session_keys
self.buf = io.BytesIO()
self.block = 0 # just used for printing, if that block is entirely skipped
self.output = output
Expand Down Expand Up @@ -278,7 +279,7 @@ def _fetch(self, nodecrypt=False):
# else, we decrypt
LOG.debug('Decrypting block %d', self.block)
assert( len(data) > CIPHER_DIFF )
segment = decrypt_block(data, self.ciphers)
segment = decrypt_block(data, self.session_keys)
LOG.debug('Adding %d bytes to the buffer', len(segment))
self._append_to_buf(segment)
LOG.debug('Buffer size: %d', self.buf_size())
Expand Down Expand Up @@ -322,7 +323,7 @@ def read(self, size):
size -= len(b2)


def body_decrypt_parts(infile, ciphers, output, edit_list=None):
def body_decrypt_parts(infile, session_keys, output, edit_list=None):
"""Decrypt the data portion according to the edit list.

We do not decrypt segments that are entirely skipped, and only output a warning (that it should not be the case).
Expand All @@ -332,7 +333,7 @@ def body_decrypt_parts(infile, ciphers, output, edit_list=None):
LOG.debug('Edit List: %s', edit_list)
assert(len(edit_list) > 0), "You can not call this function without an edit_list"

decrypted = DecryptedBuffer(infile, ciphers, output)
decrypted = DecryptedBuffer(infile, session_keys, output)

try:

Expand Down Expand Up @@ -374,7 +375,7 @@ def decrypt(keys, infile, outfile, sender_pubkey=None, offset=0, span=None):
)
)

ciphers, edit_list = header.deconstruct(infile, keys, sender_pubkey=sender_pubkey)
session_keys, edit_list = header.deconstruct(infile, keys, sender_pubkey=sender_pubkey)

# Infile in now positioned at the beginning of the data portion

Expand All @@ -384,11 +385,11 @@ def decrypt(keys, infile, outfile, sender_pubkey=None, offset=0, span=None):

if edit_list is None:
# No edit list: decrypt all segments until the end
body_decrypt(infile, ciphers, output, offset)
body_decrypt(infile, session_keys, output, offset)
# We could use body_decrypt_parts but there is an inner buffer, and segments might not be aligned
else:
# Edit list: it drives which segments is decrypted
body_decrypt_parts(infile, ciphers, output, edit_list=list(edit_list))
body_decrypt_parts(infile, session_keys, output, edit_list=list(edit_list))

LOG.info('Decryption Over')

Expand Down
9 changes: 3 additions & 6 deletions tests/_common/edit_list_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
from functools import partial
from getpass import getpass

from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305

from crypt4gh.keys import get_private_key, get_public_key
from crypt4gh import header,lib, SEGMENT_SIZE
from crypt4gh import header, lib, SEGMENT_SIZE

if __name__ == '__main__':

Expand Down Expand Up @@ -73,7 +71,6 @@
#############################################################
encryption_method = 0 # only choice for this version
session_key = os.urandom(32) # we use one session key for all blocks
cipher = ChaCha20Poly1305(session_key) # create a new one in case an old one is not reset

#############################################################
# Output the header
Expand All @@ -99,10 +96,10 @@

if segment_len < SEGMENT_SIZE: # not a full segment
data = bytes(segment[:segment_len]) # to discard the bytes from the previous segments
lib._encrypt_segment(data, outfile.write, cipher)
lib._encrypt_segment(data, outfile.write, session_key)
break

data = bytes(segment) # this is a full segment
lib._encrypt_segment(data, outfile.write, cipher)
lib._encrypt_segment(data, outfile.write, session_key)