From 1a2a3c6d72e173425885caa2342138c4615a69a3 Mon Sep 17 00:00:00 2001 From: drebs Date: Fri, 17 May 2013 09:53:37 -0300 Subject: Use leap.common.crypto and AES-256 CTR for symmetric encryption. --- ...ange-symmetric-encryption-method-to-aes-256-ctr | 1 + setup.py | 5 +- src/leap/soledad/backends/leap_backend.py | 65 ++++++++++++++-------- src/leap/soledad/crypto.py | 4 +- src/leap/soledad/tests/__init__.py | 2 +- 5 files changed, 50 insertions(+), 27 deletions(-) create mode 100644 changes/feature_change-symmetric-encryption-method-to-aes-256-ctr diff --git a/changes/feature_change-symmetric-encryption-method-to-aes-256-ctr b/changes/feature_change-symmetric-encryption-method-to-aes-256-ctr new file mode 100644 index 00000000..8c44436a --- /dev/null +++ b/changes/feature_change-symmetric-encryption-method-to-aes-256-ctr @@ -0,0 +1 @@ + o Change symmetric encryption method to AES-256 CTR mode. diff --git a/setup.py b/setup.py index d2d6314d..0f02f4fa 100644 --- a/setup.py +++ b/setup.py @@ -88,7 +88,10 @@ setup( "LEAP client, an API for data storage and sync." ), namespace_packages=["leap"], - packages=find_packages('src', exclude=['leap.soledad.tests']), + # For now, we do not exclude tests because of the circular dependency + # between leap.common and leap.soledad. + #packages=find_packages('src', exclude=['leap.soledad.tests']), + packages=find_packages('src'), package_dir={'': 'src'}, test_suite='leap.soledad.tests', install_requires=install_requirements, diff --git a/src/leap/soledad/backends/leap_backend.py b/src/leap/soledad/backends/leap_backend.py index 87f63432..8fa662e9 100644 --- a/src/leap/soledad/backends/leap_backend.py +++ b/src/leap/soledad/backends/leap_backend.py @@ -27,6 +27,7 @@ except ImportError: import json # noqa import hashlib import hmac +import binascii from u1db import Document @@ -35,6 +36,11 @@ from u1db.errors import BrokenSyncStream from u1db.remote.http_target import HTTPSyncTarget +from leap.common.crypto import ( + EncryptionMethods, + encrypt_sym, + decrypt_sym, +) from leap.common.keymanager import KeyManager from leap.common.check import leap_assert from leap.soledad.auth import TokenBasedAuth @@ -100,6 +106,8 @@ class MacMethods(object): ENC_JSON_KEY = '_enc_json' ENC_SCHEME_KEY = '_enc_scheme' +ENC_METHOD_KEY = '_enc_method' +ENC_IV_KEY = '_enc_iv' MAC_KEY = '_mac' MAC_METHOD_KEY = '_mac_method' @@ -132,7 +140,7 @@ def mac_doc(crypto, doc_id, doc_rev, ciphertext, mac_method): return hmac.new( crypto.doc_mac_key(doc_id), str(doc_id) + str(doc_rev) + ciphertext, - hashlib.sha256).hexdigest() + hashlib.sha256).digest() # raise if we do not know how to handle this MAC method raise UnknownMacMethod('Unknown MAC method: %s.' % mac_method) @@ -141,11 +149,14 @@ def encrypt_doc(crypto, doc): """ Encrypt C{doc}'s content. - Return a valid JSON string representing the following: + Encrypt doc's contents using AES-256 CTR mode and return a valid JSON + string representing the following: { ENC_JSON_KEY: '', ENC_SCHEME_KEY: 'symkey', + ENC_METHOD_KEY: EncryptionMethods.AES_256_CTR, + ENC_IV_KEY: '', MAC_KEY: '' MAC_METHOD_KEY: 'hmac' } @@ -160,19 +171,24 @@ def encrypt_doc(crypto, doc): @rtype: str """ leap_assert(doc.is_tombstone() is False) - # encrypt content - ciphertext = crypto.encrypt_sym( + # encrypt content using AES-256 CTR mode + iv, ciphertext = encrypt_sym( doc.get_json(), - crypto.doc_passphrase(doc.doc_id)) - # verify it is indeed encrypted - if not crypto.is_encrypted_sym(ciphertext): - raise DocumentNotEncrypted('Failed encrypting document.') - # update doc's content with encrypted version + crypto.doc_passphrase(doc.doc_id), + method=EncryptionMethods.AES_256_CTR) + # Return a representation for the encrypted content. In the following, we + # convert binary data to hexadecimal representation so the JSON + # serialization does not complain about what it tries to serialize. + hex_ciphertext = binascii.b2a_hex(ciphertext) return json.dumps({ - ENC_JSON_KEY: ciphertext, + ENC_JSON_KEY: hex_ciphertext, ENC_SCHEME_KEY: EncryptionSchemes.SYMKEY, - MAC_KEY: mac_doc( - crypto, doc.doc_id, doc.rev, ciphertext, MacMethods.HMAC), + ENC_METHOD_KEY: EncryptionMethods.AES_256_CTR, + ENC_IV_KEY: iv, + MAC_KEY: binascii.b2a_hex(mac_doc( # store the mac as hex. + crypto, doc.doc_id, doc.rev, + ciphertext, + MacMethods.HMAC)), MAC_METHOD_KEY: MacMethods.HMAC, }) @@ -188,13 +204,16 @@ def decrypt_doc(crypto, doc): { ENC_JSON_KEY: '', ENC_SCHEME_KEY: '', + ENC_METHOD_KEY: '', + ENC_IV_KEY: '', # (optional) MAC_KEY: '' MAC_METHOD_KEY: 'hmac' } C{enc_blob} is the encryption of the JSON serialization of the document's content. For now Soledad just deals with documents whose C{enc_scheme} is - EncryptionSchemes.SYMKEY. + EncryptionSchemes.SYMKEY and C{enc_method} is + EncryptionMethods.AES_256_CTR. @param crypto: A SoledadCryto instance to perform the encryption. @type crypto: leap.soledad.crypto.SoledadCrypto @@ -207,28 +226,28 @@ def decrypt_doc(crypto, doc): leap_assert(doc.is_tombstone() is False) leap_assert(ENC_JSON_KEY in doc.content) leap_assert(ENC_SCHEME_KEY in doc.content) + leap_assert(ENC_METHOD_KEY in doc.content) leap_assert(MAC_KEY in doc.content) leap_assert(MAC_METHOD_KEY in doc.content) # verify MAC + ciphertext = binascii.a2b_hex( # content is stored as hex. + doc.content[ENC_JSON_KEY]) mac = mac_doc( crypto, doc.doc_id, doc.rev, - doc.content[ENC_JSON_KEY], + ciphertext, doc.content[MAC_METHOD_KEY]) - if doc.content[MAC_KEY] != mac: + if binascii.a2b_hex(doc.content[MAC_KEY]) != mac: # mac is stored as hex. raise WrongMac('Could not authenticate document\'s contents.') # decrypt doc's content - ciphertext = doc.content[ENC_JSON_KEY] enc_scheme = doc.content[ENC_SCHEME_KEY] plainjson = None if enc_scheme == EncryptionSchemes.SYMKEY: - if not crypto.is_encrypted_sym(ciphertext): - raise DocumentNotEncrypted( - 'Unable to identify document encryption for incoming ' - 'document, although it is marked as being encrypted with a ' - 'symmetric key.') - plainjson = crypto.decrypt_sym( + leap_assert(ENC_IV_KEY in doc.content) + plainjson = decrypt_sym( ciphertext, - crypto.doc_passphrase(doc.doc_id)) + crypto.doc_passphrase(doc.doc_id), + method=doc.content[ENC_METHOD_KEY], + iv=doc.content[ENC_IV_KEY]) else: raise UnknownEncryptionScheme(enc_scheme) return plainjson diff --git a/src/leap/soledad/crypto.py b/src/leap/soledad/crypto.py index 6140ef31..d0e2c720 100644 --- a/src/leap/soledad/crypto.py +++ b/src/leap/soledad/crypto.py @@ -123,7 +123,7 @@ class SoledadCrypto(object): return hmac.new( self.secret[self.MAC_KEY_LENGTH:], doc_id, - hashlib.sha256).hexdigest() + hashlib.sha256).digest() def doc_mac_key(self, doc_id): """ @@ -147,7 +147,7 @@ class SoledadCrypto(object): return hmac.new( self.secret[:self.MAC_KEY_LENGTH], doc_id, - hashlib.sha256).hexdigest() + hashlib.sha256).digest() # # secret setters/getters diff --git a/src/leap/soledad/tests/__init__.py b/src/leap/soledad/tests/__init__.py index 07038910..79ee69c4 100644 --- a/src/leap/soledad/tests/__init__.py +++ b/src/leap/soledad/tests/__init__.py @@ -23,7 +23,7 @@ from leap.common.testing.basetest import BaseLeapTest class BaseSoledadTest(BaseLeapTest): """ - Instantiates GPG and Soledad for usage in tests. + Instantiates Soledad for usage in tests. """ def setUp(self): -- cgit v1.2.3