summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/feature_use-pycrypto-for-symmetric-encryption1
-rw-r--r--pkg/requirements.pip3
-rw-r--r--setup.py2
-rw-r--r--src/leap/common/crypto.py114
-rw-r--r--src/leap/common/tests/test_crypto.py80
5 files changed, 199 insertions, 1 deletions
diff --git a/changes/feature_use-pycrypto-for-symmetric-encryption b/changes/feature_use-pycrypto-for-symmetric-encryption
new file mode 100644
index 0000000..5448483
--- /dev/null
+++ b/changes/feature_use-pycrypto-for-symmetric-encryption
@@ -0,0 +1 @@
+ o Add AES-256 (CTR mode) encrypting/decrypting functions using PyCrypto.
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index 01fe3ea..141c325 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -5,5 +5,6 @@ pyopenssl
python-dateutil
autopep8
python-gnupg
+PyCrypto
-https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz \ No newline at end of file
+https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz
diff --git a/setup.py b/setup.py
index 112736e..99fd25b 100644
--- a/setup.py
+++ b/setup.py
@@ -28,6 +28,8 @@ requirements = [
"PyOpenSSL",
"python-dateutil",
"leap.soledad",
+ "python-gnupg",
+ "PyCrypto",
]
diff --git a/src/leap/common/crypto.py b/src/leap/common/crypto.py
new file mode 100644
index 0000000..f49933b
--- /dev/null
+++ b/src/leap/common/crypto.py
@@ -0,0 +1,114 @@
+# -*- 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/>.
+
+
+from Crypto.Cipher import AES
+from Crypto.Random import random
+from Crypto.Util import Counter
+from leap.common.check import leap_assert, leap_assert_type
+
+
+#
+# encryption methods
+#
+
+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
+
+
+#
+# encrypt/decrypt functions
+#
+
+# In the future, we might want to implement other encryption schemes and
+# possibly factor out the actual encryption/decryption routines of the
+# following functions to specific classes, while not changing the API.
+
+def encrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR):
+ """
+ Encrypt C{data} with C{key}, using C{method} encryption method.
+
+ @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)
+ """
+ leap_assert_type(key, str)
+
+ # AES-256 in CTR mode
+ if method == EncryptionMethods.AES_256_CTR:
+ leap_assert(
+ len(key) == 32, # 32 x 8 = 256 bits.
+ 'Wrong key size: %s bits (must be 256 bits long).' % (len(key)*8))
+ iv = random.getrandbits(256)
+ ctr = Counter.new(128, initial_value=iv)
+ cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr)
+ return iv, cipher.encrypt(data)
+
+ # raise if method is unknown
+ raise UnknownEncryptionMethod('Unkwnown method: %s' % method)
+
+
+def decrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR, **kwargs):
+ """
+ Decrypt C{data} with C{key} using C{method} encryption method.
+
+ @param data: The data to be decrypted with prepended IV.
+ @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: long
+
+ @return: The decrypted data.
+ @rtype: str
+ """
+ leap_assert_type(key, str)
+
+ # AES-256 in CTR mode
+ if method == EncryptionMethods.AES_256_CTR:
+ # assert params
+ leap_assert(
+ len(key) == 32, # 32 x 8 = 256 bits.
+ 'Wrong key size: %s (must be 256 bits long).' % len(key))
+ leap_assert(
+ 'iv' in kwargs,
+ 'AES-256-CTR needs an initial value given as.')
+ ctr = Counter.new(128, initial_value=kwargs['iv'])
+ cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
+ return cipher.decrypt(data)
+
+ # raise if method is unknown
+ raise UnknownEncryptionMethod('Unkwnown method: %s' % method)
diff --git a/src/leap/common/tests/test_crypto.py b/src/leap/common/tests/test_crypto.py
new file mode 100644
index 0000000..b704c05
--- /dev/null
+++ b/src/leap/common/tests/test_crypto.py
@@ -0,0 +1,80 @@
+## -*- coding: utf-8 -*-
+# test_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/>.
+
+
+"""
+Tests for the crypto submodule.
+"""
+
+
+from leap.common.testing.basetest import BaseLeapTest
+from leap.common import crypto
+from Crypto import Random
+
+
+class CryptoTestCase(BaseLeapTest):
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_encrypt_decrypt_sym(self):
+ # generate 256-bit key
+ key = Random.new().read(32)
+ iv, cyphertext = crypto.encrypt_sym(
+ 'data', key,
+ method=crypto.EncryptionMethods.AES_256_CTR)
+ self.assertTrue(cyphertext is not None)
+ self.assertTrue(cyphertext != '')
+ self.assertTrue(cyphertext != 'data')
+ plaintext = crypto.decrypt_sym(
+ cyphertext, key, iv=iv,
+ method=crypto.EncryptionMethods.AES_256_CTR)
+ self.assertEqual('data', plaintext)
+
+ def test_decrypt_with_wrong_iv_fails(self):
+ key = Random.new().read(32)
+ iv, cyphertext = crypto.encrypt_sym(
+ 'data', key,
+ method=crypto.EncryptionMethods.AES_256_CTR)
+ self.assertTrue(cyphertext is not None)
+ self.assertTrue(cyphertext != '')
+ self.assertTrue(cyphertext != 'data')
+ iv += 1
+ plaintext = crypto.decrypt_sym(
+ cyphertext, key, iv=iv,
+ method=crypto.EncryptionMethods.AES_256_CTR)
+ self.assertNotEqual('data', plaintext)
+
+ def test_decrypt_with_wrong_key_fails(self):
+ key = Random.new().read(32)
+ iv, cyphertext = crypto.encrypt_sym(
+ 'data', key,
+ method=crypto.EncryptionMethods.AES_256_CTR)
+ self.assertTrue(cyphertext is not None)
+ self.assertTrue(cyphertext != '')
+ self.assertTrue(cyphertext != 'data')
+ wrongkey = Random.new().read(32) # 256-bits key
+ # ensure keys are different in case we are extremely lucky
+ while wrongkey == key:
+ wrongkey = Random.new().read(32)
+ plaintext = crypto.decrypt_sym(
+ cyphertext, wrongkey, iv=iv,
+ method=crypto.EncryptionMethods.AES_256_CTR)
+ self.assertNotEqual('data', plaintext)