Exemple #1
0
    def _import_recovery_document(self, data):
        """
        Import storage secrets for symmetric encryption and uuid (if present)
        from a recovery document.

        Note that this method does not store the imported data on disk. For
        that, use C{self._store_secrets()}.

        :param data: The recovery document.
        :type data: dict

        :return: A tuple containing the number of imported secrets and the
                 secret_id of the last active secret.
        :rtype: (int, str)
        """
        soledad_assert(self.STORAGE_SECRETS_KEY in data)
        # include secrets in the secret pool.
        secret_count = 0
        secrets = data[self.STORAGE_SECRETS_KEY].items()
        active_secret = None
        # XXX remove check for existence of key (included for backwards
        # compatibility)
        if self.ACTIVE_SECRET_KEY in data:
            active_secret = data[self.ACTIVE_SECRET_KEY]
        for secret_id, encrypted_secret in secrets:
            if secret_id not in self._secrets:
                try:
                    self._secrets[secret_id] = \
                        self._decrypt_storage_secret(encrypted_secret)
                    secret_count += 1
                except SecretsException as e:
                    logger.error("Failed to decrypt storage secret: %s"
                                 % str(e))
        return secret_count, active_secret
Exemple #2
0
    def _put_secrets_in_shared_db(self):
        """
        Assert local keys are the same as shared db's ones.

        Try to fetch keys from shared recovery database. If they already exist
        in the remote db, assert that that data is the same as local data.
        Otherwise, upload keys to shared recovery database.
        """
        soledad_assert(
            self._has_secret(),
            'Tried to send keys to server but they don\'t exist in local '
            'storage.')
        # try to get secrets doc from server, otherwise create it
        doc = self._get_secrets_from_shared_db()
        if doc is None:
            doc = document.SoledadDocument(
                doc_id=self._shared_db_doc_id())
        # fill doc with encrypted secrets
        doc.content = self._export_recovery_document()
        # upload secrets to server
        user_data = self._get_user_data()
        events.emit_async(events.SOLEDAD_UPLOADING_KEYS, user_data)
        db = self._shared_db
        if not db:
            logger.warning('No shared db found')
            return
        db.put_doc(doc)
        events.emit_async(events.SOLEDAD_DONE_UPLOADING_KEYS, user_data)
Exemple #3
0
    def insert_encrypted_received_doc(self, doc_id, doc_rev, content, gen, trans_id, idx):
        """
        Decrypt and insert a received document into local staging area to be
        processed later on.

        :param doc_id: The document ID.
        :type doc_id: str
        :param doc_rev: The document Revision
        :param doc_rev: str
        :param content: The content of the document
        :type content: dict
        :param gen: The document Generation
        :type gen: int
        :param trans_id: Transaction ID
        :type trans_id: str
        :param idx: The index of this document in the current sync process.
        :type idx: int

        :return: A deferred that will fire after the decrypted document has
                 been inserted in the sync db.
        :rtype: twisted.internet.defer.Deferred
        """
        soledad_assert(self._crypto is not None, "need a crypto object")

        key = self._crypto.doc_passphrase(doc_id)
        secret = self._crypto.secret
        args = doc_id, doc_rev, content, gen, trans_id, key, secret, idx
        # decrypt asynchronously
        doc = decrypt_doc_task(*args)
        # callback will insert it for later processing
        return self._decrypt_doc_cb(doc)
Exemple #4
0
def decrypt_sym(data, key, iv):
    """
    Decrypt some data previously encrypted using AES-256 cipher in CTR mode.

    :param data: The data to be decrypted.
    :type data: str
    :param key: The symmetric key used to decrypt data (must be 256 bits
                long).
    :type key: str
    :param iv: The initialization vector.
    :type iv: long

    :return: The decrypted data.
    :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))
    backend = MultiBackend([OpenSSLBackend()])
    iv = binascii.a2b_base64(iv)
    cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=backend)
    decryptor = cipher.decryptor()
    return decryptor.update(data) + decryptor.finalize()
Exemple #5
0
def decrypt_sym(data, key, method, **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

    :raise UnknownEncryptionMethodError: Raised when C{method} is unknown.
    """
    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)
    _assert_known_encryption_method(method)
    # AES-256 in CTR mode
    if method == crypto.EncryptionMethods.AES_256_CTR:
        return AES(key=key, iv=binascii.a2b_base64(kwargs['iv'])).process(data)
    elif method == crypto.EncryptionMethods.XSALSA20:
        return XSalsa20(key=key,
                        iv=binascii.a2b_base64(kwargs['iv'])).process(data)
Exemple #6
0
    def _async_decrypt_doc(self, doc_id, rev, content, gen, trans_id, idx):
        """
        Dispatch an asynchronous document decrypting routine and save the
        result object.

        :param doc_id: The ID for the document with contents to be encrypted.
        :type doc: str
        :param rev: The revision of the document.
        :type rev: str
        :param content: The serialized content of the document.
        :type content: str
        :param gen: The generation corresponding to the modification of that
                    document.
        :type gen: int
        :param trans_id: The transaction id corresponding to the modification
                         of that document.
        :type trans_id: str
        :param idx: The index of this document in the current sync process.
        :type idx: int
        """
        soledad_assert(self._crypto is not None, "need a crypto object")

        content = json.loads(content)
        key = self._crypto.doc_passphrase(doc_id)
        secret = self._crypto.secret
        args = doc_id, rev, content, gen, trans_id, key, secret, idx
        # decrypt asynchronously
        self._async_results.append(
            self._pool.apply_async(
                decrypt_doc_task, args))
Exemple #7
0
    def insert_encrypted_received_doc(self, doc_id, doc_rev, content, gen,
                                      trans_id, idx):
        """
        Decrypt and insert a received document into local staging area to be
        processed later on.

        :param doc_id: The document ID.
        :type doc_id: str
        :param doc_rev: The document Revision
        :param doc_rev: str
        :param content: The content of the document
        :type content: dict
        :param gen: The document Generation
        :type gen: int
        :param trans_id: Transaction ID
        :type trans_id: str
        :param idx: The index of this document in the current sync process.
        :type idx: int

        :return: A deferred that will fire after the decrypted document has
                 been inserted in the sync db.
        :rtype: twisted.internet.defer.Deferred
        """
        soledad_assert(self._crypto is not None, "need a crypto object")

        key = self._crypto.doc_passphrase(doc_id)
        secret = self._crypto.secret
        args = doc_id, doc_rev, content, gen, trans_id, key, secret, idx
        # decrypt asynchronously
        doc = decrypt_doc_task(*args)
        # callback will insert it for later processing
        return self._decrypt_doc_cb(doc)
Exemple #8
0
    def _import_recovery_document(self, data):
        """
        Import storage secrets for symmetric encryption and uuid (if present)
        from a recovery document.

        Note that this method does not store the imported data on disk. For
        that, use C{self._store_secrets()}.

        :param data: The recovery document.
        :type data: dict

        :return: A tuple containing the number of imported secrets and the
                 secret_id of the last active secret.
        :rtype: (int, str)
        """
        soledad_assert(self.STORAGE_SECRETS_KEY in data)
        # include secrets in the secret pool.
        secret_count = 0
        secrets = data[self.STORAGE_SECRETS_KEY].items()
        active_secret = None
        # XXX remove check for existence of key (included for backwards
        # compatibility)
        if self.ACTIVE_SECRET_KEY in data:
            active_secret = data[self.ACTIVE_SECRET_KEY]
        for secret_id, encrypted_secret in secrets:
            if secret_id not in self._secrets:
                try:
                    self._secrets[secret_id] = \
                        self._decrypt_storage_secret(encrypted_secret)
                    secret_count += 1
                except SecretsException as e:
                    logger.error("Failed to decrypt storage secret: %s" %
                                 str(e))
        return secret_count, active_secret
Exemple #9
0
    def _put_secrets_in_shared_db(self):
        """
        Assert local keys are the same as shared db's ones.

        Try to fetch keys from shared recovery database. If they already exist
        in the remote db, assert that that data is the same as local data.
        Otherwise, upload keys to shared recovery database.
        """
        soledad_assert(
            self._has_secret(),
            'Tried to send keys to server but they don\'t exist in local '
            'storage.')
        # try to get secrets doc from server, otherwise create it
        doc = self._get_secrets_from_shared_db()
        if doc is None:
            doc = document.SoledadDocument(doc_id=self._shared_db_doc_id())
        # fill doc with encrypted secrets
        doc.content = self._export_recovery_document()
        # upload secrets to server
        user_data = self._get_user_data()
        events.emit_async(events.SOLEDAD_UPLOADING_KEYS, user_data)
        db = self._shared_db
        if not db:
            logger.warning('No shared db found')
            return
        db.put_doc(doc)
        events.emit_async(events.SOLEDAD_DONE_UPLOADING_KEYS, user_data)
Exemple #10
0
    def encrypt_doc(self, doc, workers=True):
        """
        Symmetrically encrypt a document.

        :param doc: The document with contents to be encrypted.
        :type doc: SoledadDocument

        :param workers: Whether to defer the decryption to the multiprocess
                        pool of workers. Useful for debugging purposes.
        :type workers: bool
        """
        soledad_assert(self._crypto is not None, "need a crypto object")
        docstr = doc.get_json()
        key = self._crypto.doc_passphrase(doc.doc_id)
        secret = self._crypto.secret
        args = doc.doc_id, doc.rev, docstr, key, secret

        try:
            if workers:
                res = self._pool.apply_async(
                    encrypt_doc_task, args,
                    callback=self.encrypt_doc_cb)
            else:
                # encrypt inline
                res = encrypt_doc_task(*args)
                self.encrypt_doc_cb(res)

        except Exception as exc:
            logger.exception(exc)
Exemple #11
0
    def _async_decrypt_doc(self, doc_id, rev, content, gen, trans_id, idx):
        """
        Dispatch an asynchronous document decrypting routine and save the
        result object.

        :param doc_id: The ID for the document with contents to be encrypted.
        :type doc: str
        :param rev: The revision of the document.
        :type rev: str
        :param content: The serialized content of the document.
        :type content: str
        :param gen: The generation corresponding to the modification of that
                    document.
        :type gen: int
        :param trans_id: The transaction id corresponding to the modification
                         of that document.
        :type trans_id: str
        :param idx: The index of this document in the current sync process.
        :type idx: int
        """
        soledad_assert(self._crypto is not None, "need a crypto object")

        content = json.loads(content)
        key = self._crypto.doc_passphrase(doc_id)
        secret = self._crypto.secret
        args = doc_id, rev, content, gen, trans_id, key, secret, idx
        # decrypt asynchronously
        self._async_results.append(
            self._pool.apply_async(decrypt_doc_task, args))
Exemple #12
0
def decrypt_sym(data, key, iv):
    """
    Decrypt some data previously encrypted using AES-256 cipher in CTR mode.

    :param data: The data to be decrypted.
    :type data: str
    :param key: The symmetric key used to decrypt data (must be 256 bits
                long).
    :type key: str
    :param iv: The initialization vector.
    :type iv: long

    :return: The decrypted data.
    :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))
    backend = MultiBackend([OpenSSLBackend()])
    iv = binascii.a2b_base64(iv)
    cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=backend)
    decryptor = cipher.decryptor()
    return decryptor.update(data) + decryptor.finalize()
Exemple #13
0
    def _decrypt_v1(self, data):
        # get encrypted secret from dictionary: the old format allowed for
        # storage of more than one secret, but this feature was never used and
        # soledad has been using only one secret so far. As there is a corner
        # case where the old 'active_secret' key might not be set, we just
        # ignore it and pop the only secret found in the 'storage_secrets' key.
        secret_id = data['storage_secrets'].keys().pop()
        encrypted = data['storage_secrets'][secret_id]

        # assert that we know how to decrypt the secret
        soledad_assert('cipher' in encrypted)
        cipher = encrypted['cipher']
        if cipher == 'aes256':
            cipher = ENC_METHOD.aes_256_ctr
        soledad_assert(cipher in ENC_METHOD)

        # decrypt
        salt = binascii.a2b_base64(encrypted['kdf_salt'])
        key = self._get_key(salt)
        separator = ':'
        iv, ciphertext = encrypted['secret'].split(separator, 1)
        ciphertext = binascii.a2b_base64(ciphertext)
        plaintext = self._decrypt(key, iv, ciphertext, encrypted, cipher)

        # create secrets dictionary
        secrets = {
            'remote_secret': plaintext[0:512],
            'local_salt': plaintext[512:576],
            'local_secret': plaintext[576:1024],
        }
        return secrets
Exemple #14
0
def decrypt_sym(data, key, method, **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

    :raise UnknownEncryptionMethodError: Raised when C{method} is unknown.
    """
    soledad_assert_type(key, str)
    # assert params
    soledad_assert(len(key) == 32, "Wrong key size: %s (must be 256 bits long)." % len(key))  # 32 x 8 = 256 bits.
    soledad_assert("iv" in kwargs, "%s needs an initial value." % method)
    _assert_known_encryption_method(method)
    # AES-256 in CTR mode
    if method == crypto.EncryptionMethods.AES_256_CTR:
        return AES(key=key, iv=binascii.a2b_base64(kwargs["iv"])).process(data)
    elif method == crypto.EncryptionMethods.XSALSA20:
        return XSalsa20(key=key, iv=binascii.a2b_base64(kwargs["iv"])).process(data)
Exemple #15
0
    def encrypt_doc(self, doc, workers=True):
        """
        Symmetrically encrypt a document.

        :param doc: The document with contents to be encrypted.
        :type doc: SoledadDocument

        :param workers: Whether to defer the decryption to the multiprocess
                        pool of workers. Useful for debugging purposes.
        :type workers: bool
        """
        soledad_assert(self._crypto is not None, "need a crypto object")
        docstr = doc.get_json()
        key = self._crypto.doc_passphrase(doc.doc_id)
        secret = self._crypto.secret
        args = doc.doc_id, doc.rev, docstr, key, secret

        try:
            if workers:
                res = self._pool.apply_async(encrypt_doc_task,
                                             args,
                                             callback=self.encrypt_doc_cb)
            else:
                # encrypt inline
                res = encrypt_doc_task(*args)
                self.encrypt_doc_cb(res)

        except Exception as exc:
            logger.exception(exc)
Exemple #16
0
    def _initialize_sync_db(self, opts):
        """
        Initialize the Symmetrically-Encrypted document to be synced database,
        and the queue to communicate with subprocess workers.

        :param opts:
        :type opts: SQLCipherOptions
        """
        soledad_assert(opts.sync_db_key is not None)
        sync_db_path = None
        if opts.path != ":memory:":
            sync_db_path = "%s-sync" % opts.path
        else:
            sync_db_path = ":memory:"

        # we copy incoming options because the opts object might be used
        # somewhere else
        sync_opts = sqlcipher.SQLCipherOptions.copy(opts,
                                                    path=sync_db_path,
                                                    create=True)
        self._sync_db = sqlcipher.getConnectionPool(
            sync_opts, extra_queries=self._sync_db_extra_init)
        if self._defer_encryption:
            # initialize syncing queue encryption pool
            self._sync_enc_pool = encdecpool.SyncEncrypterPool(
                self._crypto, self._sync_db)
            self._sync_enc_pool.start()
Exemple #17
0
def encrypt_sym(data, key, method):
    """
    Encrypt C{data} using a {password}.

    Currently, the only encryption methods supported are AES-256 in CTR
    mode and XSalsa20.

    :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)

    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:
        iv = os.urandom(16)
        ciphertext = AES(key=key, iv=iv).process(data)
    # 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
Exemple #18
0
    def _decrypt_v1(self, data):
        # get encrypted secret from dictionary: the old format allowed for
        # storage of more than one secret, but this feature was never used and
        # soledad has been using only one secret so far. As there is a corner
        # case where the old 'active_secret' key might not be set, we just
        # ignore it and pop the only secret found in the 'storage_secrets' key.
        secret_id = data['storage_secrets'].keys().pop()
        encrypted = data['storage_secrets'][secret_id]

        # assert that we know how to decrypt the secret
        soledad_assert('cipher' in encrypted)
        cipher = encrypted['cipher']
        if cipher == 'aes256':
            cipher = ENC_METHOD.aes_256_ctr
        soledad_assert(cipher in ENC_METHOD)

        # decrypt
        salt = binascii.a2b_base64(encrypted['kdf_salt'])
        key = self._get_key(salt)
        separator = ':'
        iv, ciphertext = encrypted['secret'].split(separator, 1)
        ciphertext = binascii.a2b_base64(ciphertext)
        plaintext = self._decrypt(key, iv, ciphertext, encrypted, cipher)

        # create secrets dictionary
        secrets = {
            'remote_secret': plaintext[0:512],
            'local_salt': plaintext[512:576],
            'local_secret': plaintext[576:1024],
        }
        return secrets
Exemple #19
0
    def _initialize_sync_db(self, opts):
        """
        Initialize the Symmetrically-Encrypted document to be synced database,
        and the queue to communicate with subprocess workers.

        :param opts:
        :type opts: SQLCipherOptions
        """
        soledad_assert(opts.sync_db_key is not None)
        sync_db_path = None
        if opts.path != ":memory:":
            sync_db_path = "%s-sync" % opts.path
        else:
            sync_db_path = ":memory:"

        # we copy incoming options because the opts object might be used
        # somewhere else
        sync_opts = sqlcipher.SQLCipherOptions.copy(
            opts, path=sync_db_path, create=True)
        self._sync_db = sqlcipher.getConnectionPool(
            sync_opts, extra_queries=self._sync_db_extra_init)
        if self._defer_encryption:
            # initialize syncing queue encryption pool
            self._sync_enc_pool = encdecpool.SyncEncrypterPool(
                self._crypto, self._sync_db)
            self._sync_enc_pool.start()
Exemple #20
0
    def __init__(self,
                 doc_info,
                 ciphertext_fd,
                 result=None,
                 secret=None,
                 armor=True,
                 start_stream=True,
                 tag=None):
        if not secret:
            raise EncryptionDecryptionError('no secret given')

        self.doc_id = doc_info.doc_id
        self.rev = doc_info.rev
        self.fd = ciphertext_fd
        self.armor = armor
        self._producer = None
        self.result = result or BytesIO()
        sym_key = _get_sym_key_for_doc(doc_info.doc_id, secret)
        self.size = None
        self.tag = None

        preamble, iv = self._consume_preamble()
        soledad_assert(preamble)
        soledad_assert(iv)

        self._aes = AESWriter(sym_key, iv, self.result, tag=tag or self.tag)
        self._aes.authenticate(preamble)
        if start_stream:
            self._start_stream()
Exemple #21
0
def encrypt_sym(data, key):
    """
    Encrypt data using AES-256 cipher in CTR mode.

    :param data: The data to be encrypted.
    :type data: str
    :param key: The key used to encrypt data (must be 256 bits long).
    :type key: str

    :return: A tuple with the initialization vector and the encrypted data.
    :rtype: (long, str)
    """
    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 = os.urandom(16)
    backend = MultiBackend([OpenSSLBackend()])
    cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=backend)
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(data) + encryptor.finalize()

    return binascii.b2a_base64(iv), ciphertext
Exemple #22
0
    def _init_config_with_defaults(self):
        """
        Initialize configuration using default values for missing params.
        """
        soledad_assert_type(self._passphrase, unicode)
        initialize = lambda attr, val: getattr(self, attr, None) is None and setattr(self, attr, val)

        initialize("_secrets_path", os.path.join(self.default_prefix, self.secrets_file_name))
        initialize("_local_db_path", os.path.join(self.default_prefix, self.local_db_file_name))
        # initialize server_url
        soledad_assert(self._server_url is not None, "Missing URL for Soledad server.")
Exemple #23
0
 def tearDownClass(cls):
     """
     Stop CouchDB instance for test.
     """
     # from BaseLeapTest
     soledad_assert(
         cls.tempdir.startswith('/tmp/leap_tests-'),
         "beware! tried to remove a dir which does not "
         "live in temporal folder!")
     shutil.rmtree(cls.tempdir)
     # from CouchDBTestCase
     cls.wrapper.stop()
Exemple #24
0
def set_init_pragmas(conn, opts=None, extra_queries=None):
    """
    Set the initialization pragmas.

    This includes the crypto pragmas, and any other options that must
    be passed early to sqlcipher db.
    """
    soledad_assert(opts is not None)
    extra_queries = [] if extra_queries is None else extra_queries
    with _db_init_lock:
        # only one execution path should initialize the db
        _set_init_pragmas(conn, opts, extra_queries)
Exemple #25
0
 def tearDownClass(cls):
     """
     Stop CouchDB instance for test.
     """
     # from BaseLeapTest
     soledad_assert(
         cls.tempdir.startswith('/tmp/leap_tests-'),
         "beware! tried to remove a dir which does not "
         "live in temporal folder!")
     shutil.rmtree(cls.tempdir)
     # from CouchDBTestCase
     cls.wrapper.stop()
Exemple #26
0
def set_init_pragmas(conn, opts=None, extra_queries=None):
    """
    Set the initialization pragmas.

    This includes the crypto pragmas, and any other options that must
    be passed early to sqlcipher db.
    """
    soledad_assert(opts is not None)
    extra_queries = [] if extra_queries is None else extra_queries
    with _db_init_lock:
        # only one execution path should initialize the db
        _set_init_pragmas(conn, opts, extra_queries)
Exemple #27
0
def _assert_known_encryption_method(method):
    """
    Assert that we can encrypt/decrypt the given C{method}

    :param method: The encryption method to assert.
    :type method: str

    :raise UnknownEncryptionMethodError: Raised when C{method} is unknown.
    """
    valid_methods = [crypto.EncryptionMethods.AES_256_CTR, crypto.EncryptionMethods.XSALSA20]
    try:
        soledad_assert(method in valid_methods)
    except AssertionError:
        raise crypto.UnknownEncryptionMethodError
Exemple #28
0
 def _delete_temporary_dirs():
     # XXX should not access "private" attrs
     for f in [self._soledad.local_db_path,
               self._soledad.secrets.secrets_path]:
         if os.path.isfile(f):
             os.unlink(f)
     # The following snippet comes from BaseLeapTest.setUpClass, but we
     # repeat it here because twisted.trial does not work with
     # setUpClass/tearDownClass.
     soledad_assert(
         self.tempdir.startswith('/tmp/leap_tests-'),
         "beware! tried to remove a dir which does not "
         "live in temporal folder!")
     shutil.rmtree(self.tempdir)
Exemple #29
0
    def delete_doc(self, doc):
        """
        Mark a document as deleted.

        Will abort if the current revision doesn't match doc.rev.
        This will also set doc.content to None.

        :param doc: A document to be deleted.
        :type doc: leap.soledad.common.document.Document
        :return: A deferred.
        :rtype: twisted.internet.defer.Deferred
        """
        soledad_assert(doc is not None, "delete_doc doesn't accept None.")
        return self._defer("delete_doc", doc)
Exemple #30
0
 def tearDown(self):
     self._delete_dbfiles()
     # the following come from BaseLeapTest.tearDownClass, because
     # twisted.trial doesn't support such class methods for tearing down
     # test classes.
     os.environ["PATH"] = self.old_path
     os.environ["HOME"] = self.old_home
     # safety check! please do not wipe my home...
     # XXX needs to adapt to non-linuces
     soledad_assert(
         self.tempdir.startswith("/tmp/leap_tests-") or self.tempdir.startswith("/var/folder"),
         "beware! tried to remove a dir which does not " "live in temporal folder!",
     )
     shutil.rmtree(self.tempdir)
Exemple #31
0
    def _decrypt_v2(self, encrypted):
        cipher = encrypted['cipher']
        soledad_assert(cipher in ENC_METHOD)

        salt = binascii.a2b_base64(encrypted['kdf_salt'])
        key = self._get_key(salt)
        iv = encrypted['iv']
        ciphertext = binascii.a2b_base64(encrypted['secrets'])
        plaintext = self._decrypt(key, iv, ciphertext, encrypted, cipher)
        encoded = json.loads(plaintext)
        secrets = {}
        for name, value in encoded.iteritems():
            secrets[name] = binascii.a2b_base64(value)
        return secrets
Exemple #32
0
def mac_doc(doc_id, doc_rev, ciphertext, enc_scheme, enc_method, enc_iv,
            mac_method, secret):
    """
    Calculate a MAC for C{doc} using C{ciphertext}.

    Current MAC method used is HMAC, with the following parameters:

        * key: sha256(storage_secret, doc_id)
        * msg: doc_id + doc_rev + ciphertext
        * digestmod: sha256

    :param doc_id: The id of the document.
    :type doc_id: str
    :param doc_rev: The revision of the document.
    :type doc_rev: str
    :param ciphertext: The content of the document.
    :type ciphertext: str
    :param enc_scheme: The encryption scheme.
    :type enc_scheme: str
    :param enc_method: The encryption method.
    :type enc_method: str
    :param enc_iv: The encryption initialization vector.
    :type enc_iv: str
    :param mac_method: The MAC method to use.
    :type mac_method: str
    :param secret: The Soledad storage secret
    :type secret: str

    :return: The calculated MAC.
    :rtype: str

    :raise crypto.UnknownMacMethodError: Raised when C{mac_method} is unknown.
    """
    try:
        soledad_assert(mac_method == crypto.MacMethods.HMAC)
    except AssertionError:
        raise crypto.UnknownMacMethodError
    template = "{doc_id}{doc_rev}{ciphertext}{enc_scheme}{enc_method}{enc_iv}"
    content = template.format(
        doc_id=doc_id,
        doc_rev=doc_rev,
        ciphertext=ciphertext,
        enc_scheme=enc_scheme,
        enc_method=enc_method,
        enc_iv=enc_iv)
    return hmac.new(
        doc_mac_key(doc_id, secret),
        content,
        hashlib.sha256).digest()
Exemple #33
0
    def _decrypt_v2(self, encrypted):
        cipher = encrypted['cipher']
        soledad_assert(cipher in ENC_METHOD)

        salt = binascii.a2b_base64(encrypted['kdf_salt'])
        key = self._get_key(salt)
        iv = encrypted['iv']
        ciphertext = binascii.a2b_base64(encrypted['secrets'])
        plaintext = self._decrypt(
            key, iv, ciphertext, encrypted, cipher)
        encoded = json.loads(plaintext)
        secrets = {}
        for name, value in encoded.iteritems():
            secrets[name] = binascii.a2b_base64(value)
        return secrets
Exemple #34
0
    def _init_config_with_defaults(self):
        """
        Initialize configuration using default values for missing params.
        """
        soledad_assert_type(self._passphrase, unicode)
        initialize = lambda attr, val: getattr(
            self, attr, None) is None and setattr(self, attr, val)

        initialize("_secrets_path",
                   os.path.join(self.default_prefix, self.secrets_file_name))
        initialize("_local_db_path",
                   os.path.join(self.default_prefix, self.local_db_file_name))
        # initialize server_url
        soledad_assert(self._server_url is not None,
                       'Missing URL for Soledad server.')
Exemple #35
0
 def tearDown(self):
     self._delete_dbfiles()
     # the following come from BaseLeapTest.tearDownClass, because
     # twisted.trial doesn't support such class methods for tearing down
     # test classes.
     os.environ["PATH"] = self.old_path
     os.environ["HOME"] = self.old_home
     # safety check! please do not wipe my home...
     # XXX needs to adapt to non-linuces
     soledad_assert(
         self.tempdir.startswith('/tmp/leap_tests-')
         or self.tempdir.startswith('/var/folder'),
         "beware! tried to remove a dir which does not "
         "live in temporal folder!")
     shutil.rmtree(self.tempdir)
Exemple #36
0
    def encrypt_doc(self, doc):
        """
        Encrypt document asynchronously then insert it on
        local staging database.

        :param doc: The document to be encrypted.
        :type doc: SoledadDocument
        """
        soledad_assert(self._crypto is not None, "need a crypto object")
        docstr = doc.get_json()
        key = self._crypto.doc_passphrase(doc.doc_id)
        secret = self._crypto.secret
        args = doc.doc_id, doc.rev, docstr, key, secret
        # encrypt asynchronously
        d = threads.deferToThread(encrypt_doc_task, *args)
        d.addCallback(self._encrypt_doc_cb)
Exemple #37
0
 def _delete_temporary_dirs():
     # XXX should not access "private" attrs
     for f in [
             self._soledad.local_db_path,
             self._soledad.secrets.secrets_path
     ]:
         if os.path.isfile(f):
             os.unlink(f)
     # The following snippet comes from BaseLeapTest.setUpClass, but we
     # repeat it here because twisted.trial does not work with
     # setUpClass/tearDownClass.
     soledad_assert(
         self.tempdir.startswith('/tmp/leap_tests-'),
         "beware! tried to remove a dir which does not "
         "live in temporal folder!")
     shutil.rmtree(self.tempdir)
Exemple #38
0
    def encrypt_doc(self, doc):
        """
        Encrypt document asynchronously then insert it on
        local staging database.

        :param doc: The document to be encrypted.
        :type doc: SoledadDocument
        """
        soledad_assert(self._crypto is not None, "need a crypto object")
        docstr = doc.get_json()
        key = self._crypto.doc_passphrase(doc.doc_id)
        secret = self._crypto.secret
        args = doc.doc_id, doc.rev, docstr, key, secret
        # encrypt asynchronously
        d = threads.deferToThread(encrypt_doc_task, *args)
        d.addCallback(self._encrypt_doc_cb)
Exemple #39
0
def mac_doc(doc_id, doc_rev, ciphertext, enc_scheme, enc_method, enc_iv,
            mac_method, secret):
    """
    Calculate a MAC for C{doc} using C{ciphertext}.

    Current MAC method used is HMAC, with the following parameters:

        * key: sha256(storage_secret, doc_id)
        * msg: doc_id + doc_rev + ciphertext
        * digestmod: sha256

    :param doc_id: The id of the document.
    :type doc_id: str
    :param doc_rev: The revision of the document.
    :type doc_rev: str
    :param ciphertext: The content of the document.
    :type ciphertext: str
    :param enc_scheme: The encryption scheme.
    :type enc_scheme: str
    :param enc_method: The encryption method.
    :type enc_method: str
    :param enc_iv: The encryption initialization vector.
    :type enc_iv: str
    :param mac_method: The MAC method to use.
    :type mac_method: str
    :param secret: The Soledad storage secret
    :type secret: str

    :return: The calculated MAC.
    :rtype: str

    :raise crypto.UnknownMacMethodError: Raised when C{mac_method} is unknown.
    """
    try:
        soledad_assert(mac_method == crypto.MacMethods.HMAC)
    except AssertionError:
        raise crypto.UnknownMacMethodError
    template = "{doc_id}{doc_rev}{ciphertext}{enc_scheme}{enc_method}{enc_iv}"
    content = template.format(doc_id=doc_id,
                              doc_rev=doc_rev,
                              ciphertext=ciphertext,
                              enc_scheme=enc_scheme,
                              enc_method=enc_method,
                              enc_iv=enc_iv)
    return hmac.new(doc_mac_key(doc_id, secret), content,
                    hashlib.sha256).digest()
Exemple #40
0
def encrypt_doc(crypto, doc):
    """
    Encrypt C{doc}'s content.

    Encrypt doc's contents using AES-256 CTR mode and return a valid JSON
    string representing the following:

        {
            ENC_JSON_KEY: '<encrypted doc JSON string>',
            ENC_SCHEME_KEY: 'symkey',
            ENC_METHOD_KEY: EncryptionMethods.AES_256_CTR,
            ENC_IV_KEY: '<the initial value used to encrypt>',
            MAC_KEY: '<mac>'
            MAC_METHOD_KEY: 'hmac'
        }

    :param crypto: A SoledadCryto instance used to perform the encryption.
    :type crypto: leap.soledad.crypto.SoledadCrypto
    :param doc: The document with contents to be encrypted.
    :type doc: SoledadDocument

    :return: The JSON serialization of the dict representing the encrypted
        content.
    :rtype: str
    """
    soledad_assert(doc.is_tombstone() is False)
    # encrypt content using AES-256 CTR mode
    iv, ciphertext = crypto.encrypt_sym(
        str(doc.get_json()),  # encryption/decryption routines expect str
        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: hex_ciphertext,
        ENC_SCHEME_KEY: EncryptionSchemes.SYMKEY,
        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,
    })
Exemple #41
0
def _assert_known_encryption_method(method):
    """
    Assert that we can encrypt/decrypt the given C{method}

    :param method: The encryption method to assert.
    :type method: str

    :raise UnknownEncryptionMethodError: Raised when C{method} is unknown.
    """
    valid_methods = [
        crypto.EncryptionMethods.AES_256_CTR,
        crypto.EncryptionMethods.XSALSA20,
    ]
    try:
        soledad_assert(method in valid_methods)
    except AssertionError:
        raise crypto.UnknownEncryptionMethodError
Exemple #42
0
 def _init_config(self, secrets_path, local_db_path, server_url):
     """
     Initialize configuration using default values for missing params.
     """
     # initialize secrets_path
     self._secrets_path = secrets_path
     if self._secrets_path is None:
         self._secrets_path = os.path.join(self.DEFAULT_PREFIX,
                                           self.STORAGE_SECRETS_FILE_NAME)
     # initialize local_db_path
     self._local_db_path = local_db_path
     if self._local_db_path is None:
         self._local_db_path = os.path.join(self.DEFAULT_PREFIX,
                                            self.LOCAL_DATABASE_FILE_NAME)
     # initialize server_url
     self._server_url = server_url
     soledad_assert(self._server_url is not None,
                    'Missing URL for Soledad server.')
Exemple #43
0
    def _encrypt_doc(self, doc):
        """
        Symmetrically encrypt a document.

        :param doc: The document with contents to be encrypted.
        :type doc: SoledadDocument

        :param workers: Whether to defer the decryption to the multiprocess
                        pool of workers. Useful for debugging purposes.
        :type workers: bool
        """
        soledad_assert(self._crypto is not None, "need a crypto object")
        docstr = doc.get_json()
        key = self._crypto.doc_passphrase(doc.doc_id)
        secret = self._crypto.secret
        args = doc.doc_id, doc.rev, docstr, key, secret
        # encrypt asynchronously
        self._pool.apply_async(encrypt_doc_task, args, callback=self._encrypt_doc_cb)
Exemple #44
0
 def _init_config(self, secrets_path, local_db_path, server_url):
     """
     Initialize configuration using default values for missing params.
     """
     # initialize secrets_path
     self._secrets_path = secrets_path
     if self._secrets_path is None:
         self._secrets_path = os.path.join(
             self.DEFAULT_PREFIX, self.STORAGE_SECRETS_FILE_NAME)
     # initialize local_db_path
     self._local_db_path = local_db_path
     if self._local_db_path is None:
         self._local_db_path = os.path.join(
             self.DEFAULT_PREFIX, self.LOCAL_DATABASE_FILE_NAME)
     # initialize server_url
     self._server_url = server_url
     soledad_assert(
         self._server_url is not None,
         'Missing URL for Soledad server.')
Exemple #45
0
    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_LENGTH} 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
        """
        soledad_assert(self.secret is not None)
        return hmac.new(self.secret[MAC_KEY_LENGTH:], doc_id, hashlib.sha256).digest()
Exemple #46
0
    def _encrypt_doc(self, doc):
        """
        Symmetrically encrypt a document.

        :param doc: The document with contents to be encrypted.
        :type doc: SoledadDocument

        :param workers: Whether to defer the decryption to the multiprocess
                        pool of workers. Useful for debugging purposes.
        :type workers: bool
        """
        soledad_assert(self._crypto is not None, "need a crypto object")
        docstr = doc.get_json()
        key = self._crypto.doc_passphrase(doc.doc_id)
        secret = self._crypto.secret
        args = doc.doc_id, doc.rev, docstr, key, secret
        # encrypt asynchronously
        self._pool.apply_async(encrypt_doc_task,
                               args,
                               callback=self._encrypt_doc_cb)
Exemple #47
0
def doc_mac_key(doc_id, secret):
    """
    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

    :param secret: The Soledad storage secret
    :type secret: str

    :return: The key.
    :rtype: str
    """
    soledad_assert(secret is not None)
    return hmac.new(secret[:MAC_KEY_LENGTH], doc_id, hashlib.sha256).digest()
Exemple #48
0
    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_LENGTH} 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
        """
        soledad_assert(self.secret is not None)
        return hmac.new(self.secret[MAC_KEY_LENGTH:], doc_id,
                        hashlib.sha256).digest()
Exemple #49
0
def doc_mac_key(doc_id, secret):
    """
    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

    :param secret: The Soledad storage secret
    :type secret: str

    :return: The key.
    :rtype: str
    """
    soledad_assert(secret is not None)
    return hmac.new(secret[:MAC_KEY_LENGTH], doc_id, hashlib.sha256).digest()
Exemple #50
0
def encrypt_sym(data, key):
    """
    Encrypt data using AES-256 cipher in CTR mode.

    :param data: The data to be encrypted.
    :type data: str
    :param key: The key used to encrypt data (must be 256 bits long).
    :type key: str

    :return: A tuple with the initialization vector and the encrypted data.
    :rtype: (long, str)
    """
    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 = os.urandom(16)
    ciphertext = AES(key=key, iv=iv).process(data)

    return binascii.b2a_base64(iv), ciphertext
Exemple #51
0
    def _import_recovery_document(self, data):
        """
        Import storage secrets for symmetric encryption from a recovery
        document.

        Note that this method does not store the imported data on disk. For
        that, use C{self._store_secrets()}.

        :param data: The recovery document.
        :type data: dict

        :return: A tuple containing the number of imported secrets, the
                 secret_id of the last active secret, and the recovery
                 document format version.
        :rtype: (int, str, int)
        """
        soledad_assert(self.STORAGE_SECRETS_KEY in data)
        version = data.get(self.RECOVERY_DOC_VERSION_KEY, 1)
        meth = getattr(self, '_import_recovery_document_version_%d' % version)
        secret_count, active_secret = meth(data)
        return secret_count, active_secret, version
Exemple #52
0
def decrypt_sym(data, key, iv):
    """
    Decrypt some data previously encrypted using AES-256 cipher in CTR mode.

    :param data: The data to be decrypted.
    :type data: str
    :param key: The symmetric key used to decrypt data (must be 256 bits
                long).
    :type key: str
    :param iv: The initialization vector.
    :type iv: long

    :return: The decrypted data.
    :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))
    return AES(key=key, iv=binascii.a2b_base64(iv)).process(data)
Exemple #53
0
def encrypt_sym(data, key):
    """
    Encrypt data using AES-256 cipher in CTR mode.

    :param data: The data to be encrypted.
    :type data: str
    :param key: The key used to encrypt data (must be 256 bits long).
    :type key: str

    :return: A tuple with the initialization vector and the encrypted data.
    :rtype: (long, str)
    """
    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 = os.urandom(16)
    ciphertext = AES(key=key, iv=iv).process(data)

    return binascii.b2a_base64(iv), ciphertext
Exemple #54
0
def decrypt_sym(data, key, iv):
    """
    Decrypt some data previously encrypted using AES-256 cipher in CTR mode.

    :param data: The data to be decrypted.
    :type data: str
    :param key: The symmetric key used to decrypt data (must be 256 bits
                long).
    :type key: str
    :param iv: The initialization vector.
    :type iv: long

    :return: The decrypted data.
    :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))
    return AES(
        key=key, iv=binascii.a2b_base64(iv)).process(data)
Exemple #55
0
 def _decrypt(self, key, iv, ciphertext, encrypted, method):
     # assert some properties of the stored secret
     soledad_assert(encrypted['kdf'] == 'scrypt')
     soledad_assert(encrypted['kdf_length'] == len(key))
     # decrypt
     plaintext = decrypt_sym(ciphertext, key, iv, method)
     soledad_assert(encrypted['length'] == len(plaintext))
     return plaintext
Exemple #56
0
 def _decrypt(self, key, iv, ciphertext, encrypted, method):
     # assert some properties of the stored secret
     soledad_assert(encrypted['kdf'] == 'scrypt')
     soledad_assert(encrypted['kdf_length'] == len(key))
     # decrypt
     plaintext = decrypt_sym(ciphertext, key, iv, method)
     soledad_assert(encrypted['length'] == len(plaintext))
     return plaintext
Exemple #57
0
def encrypt_sym(data, key):
    """
    Encrypt data using AES-256 cipher in CTR mode.

    :param data: The data to be encrypted.
    :type data: str
    :param key: The key used to encrypt data (must be 256 bits long).
    :type key: str

    :return: A tuple with the initialization vector and the encrypted data.
    :rtype: (long, str)
    """
    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 = os.urandom(16)
    cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=crypto_backend)
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(data) + encryptor.finalize()

    return binascii.b2a_base64(iv), ciphertext
Exemple #58
0
    def __init__(self, doc_info, ciphertext_fd, result=None,
                 secret=None, armor=True, start_stream=True, tag=None):
        if not secret:
            raise EncryptionDecryptionError('no secret given')

        self.doc_id = doc_info.doc_id
        self.rev = doc_info.rev
        self.fd = ciphertext_fd
        self.armor = armor
        self._producer = None
        self.result = result or BytesIO()
        sym_key = _get_sym_key_for_doc(doc_info.doc_id, secret)
        self.size = None
        self.tag = None

        preamble, iv = self._consume_preamble()
        soledad_assert(preamble)
        soledad_assert(iv)

        self._aes = AESWriter(sym_key, iv, self.result, tag=tag or self.tag)
        self._aes.authenticate(preamble)
        if start_stream:
            self._start_stream()
Exemple #59
0
    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)
        # 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:
            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)
Exemple #60
0
def encrypt_sym(data, key, method):
    """
    Encrypt C{data} using a {password}.

    Currently, the only encryption methods supported are AES-256 in CTR
    mode and XSalsa20.

    :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)

    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:
        iv = os.urandom(16)
        ciphertext = AES(key=key, iv=iv).process(data)
    # 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