summaryrefslogtreecommitdiff
path: root/soledad/src/leap/soledad/crypto.py
diff options
context:
space:
mode:
Diffstat (limited to 'soledad/src/leap/soledad/crypto.py')
-rw-r--r--soledad/src/leap/soledad/crypto.py207
1 files changed, 207 insertions, 0 deletions
diff --git a/soledad/src/leap/soledad/crypto.py b/soledad/src/leap/soledad/crypto.py
new file mode 100644
index 00000000..bfad66d1
--- /dev/null
+++ b/soledad/src/leap/soledad/crypto.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+# crypto.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+"""
+Cryptographic utilities for Soledad.
+"""
+
+
+import os
+import binascii
+import hmac
+import hashlib
+
+
+from Crypto.Cipher import AES
+from Crypto.Util import Counter
+
+
+from leap.soledad import (
+ soledad_assert,
+ soledad_assert_type,
+)
+
+
+class EncryptionMethods(object):
+ """
+ Representation of encryption methods that can be used.
+ """
+
+ AES_256_CTR = 'aes-256-ctr'
+
+
+class UnknownEncryptionMethod(Exception):
+ """
+ Raised when trying to encrypt/decrypt with unknown method.
+ """
+ pass
+
+
+class NoSymmetricSecret(Exception):
+ """
+ Raised when trying to get a hashed passphrase.
+ """
+
+
+class SoledadCrypto(object):
+ """
+ General cryptographic functionality.
+ """
+
+ MAC_KEY_LENGTH = 64
+
+ def __init__(self, soledad):
+ """
+ Initialize the crypto object.
+
+ @param soledad: A Soledad instance for key lookup.
+ @type soledad: leap.soledad.Soledad
+ """
+ self._soledad = soledad
+
+ def encrypt_sym(self, data, key,
+ method=EncryptionMethods.AES_256_CTR):
+ """
+ Encrypt C{data} using a {password}.
+
+ Currently, the only encryption method supported is AES-256 CTR mode.
+
+ @param data: The data to be encrypted.
+ @type data: str
+ @param key: The key used to encrypt C{data} (must be 256 bits long).
+ @type key: str
+ @param method: The encryption method to use.
+ @type method: str
+
+ @return: A tuple with the initial value and the encrypted data.
+ @rtype: (long, str)
+ """
+ soledad_assert_type(key, str)
+
+ # AES-256 in CTR mode
+ if method == EncryptionMethods.AES_256_CTR:
+ soledad_assert(
+ len(key) == 32, # 32 x 8 = 256 bits.
+ 'Wrong key size: %s bits (must be 256 bits long).' %
+ (len(key) * 8))
+ iv = os.urandom(8)
+ ctr = Counter.new(64, prefix=iv)
+ cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr)
+ return binascii.b2a_base64(iv), cipher.encrypt(data)
+
+ # raise if method is unknown
+ raise UnknownEncryptionMethod('Unkwnown method: %s' % method)
+
+ def decrypt_sym(self, data, key,
+ method=EncryptionMethods.AES_256_CTR, **kwargs):
+ """
+ Decrypt data using symmetric secret.
+
+ Currently, the only encryption method supported is AES-256 CTR mode.
+
+ @param data: The data to be decrypted.
+ @type data: str
+ @param key: The key used to decrypt C{data} (must be 256 bits long).
+ @type key: str
+ @param method: The encryption method to use.
+ @type method: str
+ @param kwargs: Other parameters specific to each encryption method.
+ @type kwargs: dict
+
+ @return: The decrypted data.
+ @rtype: str
+ """
+ soledad_assert_type(key, str)
+
+ # AES-256 in CTR mode
+ if method == EncryptionMethods.AES_256_CTR:
+ # assert params
+ soledad_assert(
+ len(key) == 32, # 32 x 8 = 256 bits.
+ 'Wrong key size: %s (must be 256 bits long).' % len(key))
+ soledad_assert(
+ 'iv' in kwargs,
+ 'AES-256-CTR needs an initial value.')
+ ctr = Counter.new(64, prefix=binascii.a2b_base64(kwargs['iv']))
+ cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr)
+ return cipher.decrypt(data)
+
+ # raise if method is unknown
+ raise UnknownEncryptionMethod('Unkwnown method: %s' % method)
+
+ def doc_passphrase(self, doc_id):
+ """
+ Generate a passphrase for symmetric encryption of document's contents.
+
+ The password is derived using HMAC having sha256 as underlying hash
+ function. The key used for HMAC are the first
+ C{soledad.REMOTE_STORAGE_SECRET_KENGTH} bytes of Soledad's storage
+ secret stripped from the first MAC_KEY_LENGTH characters. The HMAC
+ message is C{doc_id}.
+
+ @param doc_id: The id of the document that will be encrypted using
+ this passphrase.
+ @type doc_id: str
+
+ @return: The passphrase.
+ @rtype: str
+
+ @raise NoSymmetricSecret: if no symmetric secret was supplied.
+ """
+ if self.secret is None:
+ raise NoSymmetricSecret()
+ return hmac.new(
+ self.secret[
+ self.MAC_KEY_LENGTH:
+ self._soledad.REMOTE_STORAGE_SECRET_LENGTH],
+ doc_id,
+ hashlib.sha256).digest()
+
+ def doc_mac_key(self, doc_id):
+ """
+ Generate a key for calculating a MAC for a document whose id is
+ C{doc_id}.
+
+ The key is derived using HMAC having sha256 as underlying hash
+ function. The key used for HMAC is the first MAC_KEY_LENGTH characters
+ of Soledad's storage secret. The HMAC message is C{doc_id}.
+
+ @param doc_id: The id of the document.
+ @type doc_id: str
+
+ @return: The key.
+ @rtype: str
+
+ @raise NoSymmetricSecret: if no symmetric secret was supplied.
+ """
+ if self.secret is None:
+ raise NoSymmetricSecret()
+ return hmac.new(
+ self.secret[:self.MAC_KEY_LENGTH],
+ doc_id,
+ hashlib.sha256).digest()
+
+ #
+ # secret setters/getters
+ #
+
+ def _get_secret(self):
+ return self._soledad.storage_secret
+
+ secret = property(
+ _get_secret, doc='The secret used for symmetric encryption')