Blind Index Planning

CipherSweet includes a planner to assist developers in determining the safe sizes for an additional blind index on an encrypted field.

Using the planner is straightforward:

const {FieldIndexPlanner} = require('ciphersweet-js');

// First, instantiate the planner for a given field
let planner = new FieldIndexPlanner();

// How many rows do you anticipate?
planner.setEstimatedPopulation(50000);

// # Next, add some information about existing fields
planner.addExistingIndex('name_goes_here', 4, 16);
// ... etc.

let recommended = planner.recommend();
console.log(recommended);

This code snippet should yield the following:

{ min: 4, max: 11 }

In the above example, the existing index ("name_goes_here") has an output size of 4 bits and an input domain of 2^16 possible inputs (16 bits). That is where the 4 and 16 come from, respectively.

Input domain is the set of all possible distinct inputs.

For example: Calendar years consisting of 4-digit integers (2019) have an input domain of 10,000 possible values. The log (base 2) of 10,000 is 13.2877; you would want to always round up (so 14).

Output size is the number of bits (not bytes) of a blind index.

How to interpret this data:

Furthermore, you can use recommendLow() to only get the lower number, and recommendHigh() to only get the higher number.

Note: If there is no safe value for an additional index, the recommend methods will throw a PlannerException.

I Don't Know What Any Of That Means

Let's walk through using the planner one step at a time.

Let's say you're building an encrypted database for a local government that will service between 45,000 and 50,000 people in the next 10 years. Your Population can be assumed to be 50,000.

let planner = new FieldIndexPlanner();
planner.setEstimatedPopulation(50000);

Starting from scratch, you won't have any existing indexes. The previous example included some, but we'll skip that for now.

So let's say you want to add an index just based on the population.

let recommended = planner.recommend();
console.log(recommended);
/*
{ min: 8, max: 15 }
*/

This means if you create your first index with an output size between 8 and 15, you're safe.

But let's say this first index is based on a smaller input domain (e.g. calendar year, as explained above) than your total population.

If we specify an input domain of 14 bits (smallest power of 2, where 2^n is larger than 10,000), we get a lower max recommendation than 15.

const {FieldIndexPlanner} = require('ciphersweet-js');
let planner = new FieldIndexPlanner();
planner.setEstimatedPopulation(50000);
let recommended = planner.recommend(14);
console.log(recommended);
/*
{ min: 8, max: 14 }
*/

Let's add a blind index with an output size of 8 and move on.

field.addBlindIndex(
    new BlindIndex('birth_year_idx', [], 8, true)
);

And update our planner accordingly...

// 8 is the output size (in bits) we decided previously
// 14 because log_2(10000) is larger than 13 but smaller than 14
planner.addExistingIndex('birth_year_idx', 8, 14);

Next, we want to know what the safe bounds are for an additional index on birth year (e.g. a compound index combining birth year with gender).

Since we used addExistingIndex(), we can simply run recommend() again to see what the safe range is.

const {FieldIndexPlanner} = require('ciphersweet-js');
let planner = new FieldIndexPlanner();
planner.setEstimatedPopulation(50000);
planner.addExistingIndex('birth_year_idx', 8, 14);
let recommended = planner.recommend();
console.log(recommended);
/*
{ min: 1, max: 7 }
*/

So if we want to create a second blind index on the same field, the only safe values are between 1 and 7 bits. Anything else will be useful for attackers.

That's all you need to know to use the planner safely. The jargon isn't super important.

Next: Key/Backend Rotation