Skip to content

Encrypted Indexes API

Cosmian Findex, a Symmetric Searchable Encryption (SSE) scheme, allows building encrypted indexes, that can be efficiently searched using encrypted queries and responses.

Benefits

Since the environment cannot learn anything about the content of the index, nor the queries, nor the responses, these indexes can be safely stored in a Zero Trust environment such as the public cloud.

sse

Typical workflow

  1. Using a client library (java, python, js, ios, android, etc…), the user encrypts its search by supplying a searched word and a secret key.

  2. The client library issues 2 encrypted requests to the key-value store storing the index (the key value store can be any store of your choice - see below)

  3. The client library decrypts the response, typically the records identifiers of the main DB where the word is found.

The user will then query the main DB for the given records, encrypted with attributes encryption and decrypt what his/her private key allows.

Get the Findex library

The library is not publicly available during the patenting process. Cosmian will however provide the source code on request after entering a non-disclosure agreement. Please contact Cosmian for details.

  1. install the library locally

    mvn install:install-file \
        -Dfile=cosmian-findex-1.0.0.jar \
        -DgroupId=com.cosmian \
        -DartifactId=cosmian-findex \
        -Dversion=1.0.0 \
        -Dpackaging=jar
    
  2. then add it to your project

    <dependency>
        <groupId>com.cosmian</groupId>
        <artifactId>cosmian-findex</artifactId>
        <version>1.0.0</version>
    </dependency>
    

Implement the access to your key-value store.

The key-value must be able to store 2 “tables” - the Entry Table and the Chain Table - both made of an encrypted key and an encrypted value.

Implement the com.cosmian.sse.DbInterface, here is a minimal example for Datastax Cassandra to perform both updates and queries.

public class DseDB implements DBInterface, AutoCloseable {

    private static final Logger logger = Logger.getLogger(DseDB.class.getName());

    public static class Configuration {
        private final String ip;
        private final int port;
        private final String dataCenter;

        public Configuration(String ip, int port, String dataCenter) {
            this.ip = ip;
            this.port = port;
            this.dataCenter = dataCenter;
        }
    }

    final CqlSession session;

    /**
     * Connect to the local cassandra on 127.0.0.1:9042 at data center 'dc1'
     * 
     * @throws CosmianException if the local DSE cannot be contacted
     */
    public DseDB() throws CosmianException {
        this("127.0.0.1", 9042, "dc1");
    }

    /**
     * Instantiate a new DSE instance To know the data center: run 'select
     * data_center from system.local;'
     * 
     * @param config use the {@link Configuration} object to pass DSE config
     *               data
     * @throws CosmianException if the contact point canot be contacted
     */
    public DseDB(Configuration configuration) throws CosmianException {
        this(configuration.ip, configuration.port, configuration.dataCenter);
    }

    /**
     * Instantiate a new DSE instance To know the data center: run 'select
     * data_center from system.local;'
     * 
     * @param ipAddress  the address of the contact point
     * @param port       the port of the contact point
     * @param dataCenter the data center
     * @throws CosmianException if the contact point canot be contacted
     */
    public DseDB(String ipAddress, int port, String dataCenter) throws CosmianException {
        try {
            CqlSession session = CqlSession.builder().addContactPoint(new InetSocketAddress(ipAddress, port))
                    .withLocalDatacenter(dataCenter).build();
            {
                session.execute("CREATE KEYSPACE IF NOT EXISTS cosmian_sse " + "WITH REPLICATION = { "
                        + "'class' : 'SimpleStrategy', " + "'replication_factor' : 1 " + "};");
                session.execute("CREATE TABLE IF NOT EXISTS cosmian_sse.entry_table "
                        + "(key text , ciphertext blob, PRIMARY KEY(key));");
                session.execute("CREATE TABLE IF NOT EXISTS cosmian_sse.chain_table "
                        + "(key text , ciphertext blob, PRIMARY KEY(key));");
            }
            this.session = session;
        } catch (Exception e) {
            throw new CosmianException("Failed initializing the DSE DB: " + e.getMessage(), e);
        }
    }

    @Override
    public HashMap<WordHash, byte[]> getEntryTableEntries(Set<WordHash> wordHashes) throws CosmianException {
        try {
            PreparedStatement prepared = session
                    .prepare("SELECT key, ciphertext from cosmian_sse.entry_table " + "WHERE key IN ?;");
            List<String> list = new ArrayList<String>();
            for (WordHash wh : wordHashes) {
                list.add(wh.toString());
            }
            BoundStatement bound = prepared.bind(list);
            ResultSet rs = this.session.execute(bound);
            HashMap<WordHash, byte[]> results = new HashMap<>();
            for (Row row : rs.all()) {
                results.put(WordHash.fromString(row.get(0, String.class)), row.get(1, ByteBuffer.class).array());
            }
            return results;
        } catch (Exception e) {
            throw new CosmianException("select in Entry Table failed: " + e.getMessage(), e);
        }
    }

    @Override
    public void upsertEntryTableEntries(HashMap<WordHash, byte[]> entries) throws CosmianException {
        try {
            PreparedStatement upsert = session
                    .prepare("INSERT INTO cosmian_sse.entry_table (key, ciphertext) " + "VALUES (:key, :ciphertext)");
            BatchStatementBuilder batchBuilder = BatchStatement.builder(DefaultBatchType.LOGGED);
            for (Map.Entry<WordHash, byte[]> entry : entries.entrySet()) {
                batchBuilder.addStatement(upsert.bind(entry.getKey().toString(), ByteBuffer.wrap(entry.getValue())));
            }
            BatchStatement batch = batchBuilder.build();
            this.session.execute(batch);
        } catch (Exception e) {
            throw new CosmianException("upsert in Entry Table failed: " + e.getMessage(), e);
        }
    }

    @Override
    public Set<byte[]> getChainTableEntries(Set<Key> chainTableKeys) throws CosmianException {
        try {
            PreparedStatement prepared = session
                    .prepare("SELECT key, ciphertext from cosmian_sse.chain_table " + "WHERE key IN ?;");
            List<String> list = new ArrayList<String>();
            for (Key key : chainTableKeys) {
                list.add(key.toString());
            }
            BoundStatement bound = prepared.bind(list);
            ResultSet rs = this.session.execute(bound);
            Set<byte[]> results = new HashSet<>();
            for (Row row : rs.all()) {
                results.add(row.get(1, ByteBuffer.class).array());
            }
            return results;
        } catch (Exception e) {
            throw new CosmianException("select in Chain Table failed: " + e.getMessage(), e);
        }
    }

    @Override
    public void upsertChainTableEntries(HashMap<Key, byte[]> entries) throws CosmianException {
        try {
            PreparedStatement upsert = session
                    .prepare("INSERT INTO cosmian_sse.chain_table (key, ciphertext) " + "VALUES (:key, :ciphertext)");
            BatchStatementBuilder batchBuilder = BatchStatement.builder(DefaultBatchType.LOGGED);
            for (Map.Entry<Key, byte[]> entry : entries.entrySet()) {
                batchBuilder.addStatement(upsert.bind(entry.getKey().toString(), ByteBuffer.wrap(entry.getValue())));
            }
            BatchStatement batch = batchBuilder.build();
            this.session.execute(batch);
        } catch (Exception e) {
            throw new CosmianException("upsert in Chain Table failed: " + e.getMessage(), e);
        }

    }

    @Override
    public void close() {
        if (this.session != null) {
            try {
                this.session.close();
            } catch (Exception e) {
                logger.warning(() -> "Failed closing the DSE session: " + e.getMessage());
            }
        }
    }

}

You may also want to add methods to truncate the tables and return table sizes.