diff options
| -rw-r--r-- | changes/feature_add-xsalsa20-encryption-method | 1 | ||||
| -rw-r--r-- | soledad/src/leap/soledad/crypto.py | 41 | ||||
| -rw-r--r-- | soledad/src/leap/soledad/tests/test_crypto.py | 54 | 
3 files changed, 79 insertions, 17 deletions
| diff --git a/changes/feature_add-xsalsa20-encryption-method b/changes/feature_add-xsalsa20-encryption-method new file mode 100644 index 00000000..66b97de8 --- /dev/null +++ b/changes/feature_add-xsalsa20-encryption-method @@ -0,0 +1 @@ +  o Add XSalsa20 symmetric encryption method. diff --git a/soledad/src/leap/soledad/crypto.py b/soledad/src/leap/soledad/crypto.py index 6187b1ab..3c1061d5 100644 --- a/soledad/src/leap/soledad/crypto.py +++ b/soledad/src/leap/soledad/crypto.py @@ -28,6 +28,7 @@ import hashlib  from pycryptopp.cipher.aes import AES +from pycryptopp.cipher.xsalsa20 import XSalsa20  from leap.soledad import ( @@ -42,6 +43,7 @@ class EncryptionMethods(object):      """      AES_256_CTR = 'aes-256-ctr' +    XSALSA20 = 'xsalsa20'  class UnknownEncryptionMethod(Exception): @@ -92,18 +94,23 @@ class SoledadCrypto(object):          """          soledad_assert_type(key, str) +        soledad_assert( +            len(key) == 32,  # 32 x 8 = 256 bits. +            'Wrong key size: %s bits (must be 256 bits long).' % +            (len(key) * 8)) +        iv = None          # 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(16)              ciphertext = AES(key=key, iv=iv).process(data) -            return binascii.b2a_base64(iv), ciphertext - -        # raise if method is unknown -        raise UnknownEncryptionMethod('Unkwnown method: %s' % method) +        # XSalsa20 +        elif method == EncryptionMethods.XSALSA20: +            iv = os.urandom(24) +            ciphertext = XSalsa20(key=key, iv=iv).process(data) +        else: +            # raise if method is unknown +            raise UnknownEncryptionMethod('Unkwnown method: %s' % method) +        return binascii.b2a_base64(iv), ciphertext      def decrypt_sym(self, data, key,                      method=EncryptionMethods.AES_256_CTR, **kwargs): @@ -125,18 +132,20 @@ class SoledadCrypto(object):          @rtype: str          """          soledad_assert_type(key, str) - +        # 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, +            '%s needs an initial value.' % method)          # 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.')              return AES(                  key=key, iv=binascii.a2b_base64(kwargs['iv'])).process(data) +        elif method == EncryptionMethods.XSALSA20: +            return XSalsa20( +                key=key, iv=binascii.a2b_base64(kwargs['iv'])).process(data)          # raise if method is unknown          raise UnknownEncryptionMethod('Unkwnown method: %s' % method) diff --git a/soledad/src/leap/soledad/tests/test_crypto.py b/soledad/src/leap/soledad/tests/test_crypto.py index c727a2ff..eea67b45 100644 --- a/soledad/src/leap/soledad/tests/test_crypto.py +++ b/soledad/src/leap/soledad/tests/test_crypto.py @@ -189,7 +189,7 @@ class MacAuthTestCase(BaseSoledadTest):              target.decrypt_doc, self._soledad._crypto, doc) -class SoledadCryptoTestCase(BaseSoledadTest): +class SoledadCryptoAESTestCase(BaseSoledadTest):      def test_encrypt_decrypt_sym(self):          # generate 256-bit key @@ -239,3 +239,55 @@ class SoledadCryptoTestCase(BaseSoledadTest):              cyphertext, wrongkey, iv=iv,              method=crypto.EncryptionMethods.AES_256_CTR)          self.assertNotEqual('data', plaintext) + + +class SoledadCryptoXSalsa20TestCase(BaseSoledadTest): + +    def test_encrypt_decrypt_sym(self): +        # generate 256-bit key +        key = Random.new().read(32) +        iv, cyphertext = self._soledad._crypto.encrypt_sym( +            'data', key, +            method=crypto.EncryptionMethods.XSALSA20) +        self.assertTrue(cyphertext is not None) +        self.assertTrue(cyphertext != '') +        self.assertTrue(cyphertext != 'data') +        plaintext = self._soledad._crypto.decrypt_sym( +            cyphertext, key, iv=iv, +            method=crypto.EncryptionMethods.XSALSA20) +        self.assertEqual('data', plaintext) + +    def test_decrypt_with_wrong_iv_fails(self): +        key = Random.new().read(32) +        iv, cyphertext = self._soledad._crypto.encrypt_sym( +            'data', key, +            method=crypto.EncryptionMethods.XSALSA20) +        self.assertTrue(cyphertext is not None) +        self.assertTrue(cyphertext != '') +        self.assertTrue(cyphertext != 'data') +        # get a different iv by changing the first byte +        rawiv = binascii.a2b_base64(iv) +        wrongiv = rawiv +        while wrongiv == rawiv: +            wrongiv = os.urandom(1) + rawiv[1:] +        plaintext = self._soledad._crypto.decrypt_sym( +            cyphertext, key, iv=binascii.b2a_base64(wrongiv), +            method=crypto.EncryptionMethods.XSALSA20) +        self.assertNotEqual('data', plaintext) + +    def test_decrypt_with_wrong_key_fails(self): +        key = Random.new().read(32) +        iv, cyphertext = self._soledad._crypto.encrypt_sym( +            'data', key, +            method=crypto.EncryptionMethods.XSALSA20) +        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 = self._soledad._crypto.decrypt_sym( +            cyphertext, wrongkey, iv=iv, +            method=crypto.EncryptionMethods.XSALSA20) +        self.assertNotEqual('data', plaintext) | 
