Skip to content

Attributes Encryption API

Cloudproof Encryption provides librairies and tools to encrypt and securely index large repositories of data - Big Data - with high performance and advanced security primitives.

See the use cases and benefits and a description of the crypto systems used.

The librairies are available in multiple languages facilitating encryption close to the data source and decryption close to the decryption target, including mobile devices and browsers.

This section describes the Attributes Encryption API, the Searchable Encryption API is on this page

Developer Quick start

The best way to quickly get started with Java is to run the Java lib demo and hack from there.

This code uses the Cosmian KMS for encryption/decryption

The code below is pure java code which encrypts and decrypts data inside the KMS. This works fine and is secure but is rather slow as it does I/O from/to the KMS on all operations. A much faster alternative is to use the native library to perform local encryption and decryption. See using the native library for details.

The complete source code is available in the cosmian_java_lib demo.java file. The code contains detailed explanation however a complete description is provided below.

  1. Create a free account on https://console.cosmian.com to get access to Cosmian KMS server, so you are able to create keys.

    Recover your API Key

  2. Clone the cosmian_java_lib repository:

    git clone https://github.com/Cosmian/cosmian_java_lib.git
    
  3. Edit the line in test_abe() to setup the URL of the KMS Server and your API Key

    Abe abe = new Abe(new RestClient([KMS_SERVER_URL], [YOUR_API_KEY]));
    
  4. Run the code

    mvn clean test -Dtest=com.cosmian.Demo
    

    You need Java 8 and Maven version 3+

The complete source code for python is available here. The code contains two examples in jupyter notebooks.

  1. Clone the abe_gspw repository:

    git clone https://github.com/Cosmian/abe_gpsw.git
    
  2. Generate the wasm file and python’s bindings:

    We are using *.wit format to provide a generic interface, and generate different languages bindings. After the abe.wit has been generated, we use wit-bindgen to generate Rust-bindings which provide a ABE trait to be implemented. Once done, we can build the final WebAssembly file, and generate Python-bindings with wit-bindgen. To complete all those steps, you can simply run :

    sh python/wasi/wasi.sh
    
  3. Add the generated abe_gspw.wasm and bindings.py files to your python project.

  4. Import the necessary elements in your python file :

    from wasmtime import Linker, Module, Store, WasiConfig
    from bindings import Abe, Attribute, Ok, Policy, PolicyAxis
    
    store = Store()
    wasi_config = WasiConfig()
    wasi_config.inherit_stderr()
    wasi_config.inherit_stdin()
    wasi_config.inherit_stdout()
    store.set_wasi(wasi_config)
    
    linker = Linker(store.engine)
    linker.define_wasi()
    
    my_module = Module.from_file(store.engine, "abe_gpsw.wasm")
    abe = Abe(store, linker, my_module)
    

Creating a policy

A Policy is specified by Policy Axes, defining a n dimensional matrix of authorizations/partitions. A user must possess keys with access policies satisfying the attributes from these n axes to be able to decrypt files.

In the example below, we create a Policy which combines two axes, a security level axis and a department axis. A user will be able to decrypt data only if it possesses a key with a sufficient security level and the code for the department.

Policy policy = new Policy(20)
    .addAxis("Security Level",
                    new String[] { "Protected", "Low Secret", "Medium Secret",
                                    "High Secret", "Top Secret", },
                    true) // <- hierarchical axis
    .addAxis("Department", new String[] { "R&D", "HR", "MKG", "FIN" }, false);
policy = Policy(
    primary_axis = PolicyAxis(
        name="Security Level", 
        attributes=["Protected", "Low Secret", "Medium Secret","High Secret", "Top Secret"],
        hierarchical=True,
    ),
    secondary_axis = PolicyAxis(
        name="Department",
        attributes=["R&D", "HR", "MKG", "FIN"],
        hierarchical=False,
    )
)
Attributes rotations

An integer parameter fixes the maximum number of rotations for attributes of this Policy. This number influences the number of keys which will be ultimately generated for this Policy and must be kept to a “reasonable” level to reduce security risks associated with multiplying keys.

Security Level Axis

The first Policy Axis is the ‘Security Level’ axis and is a hierarchical axis made of 5 levels: Protected, Low Secret , ...,Top Secret.

It is hierarchical: a user being granted access to level n is automatically granted access to all levels below n. The attributes must be provided in ascending order.

Department Security Axis

The second Policy Axis is the Department axis and is made of 4 values: R&D, HR, MKG, FIN. This axis is not hierarchical: granting access to an attribute of this axis to a user does not give access to any other attribute. Each attribute must be granted individually.

Generating the master keys

To generate keys you need access to a Cosmian KMS server and to an API Key to authenticate to the server.

The simplest solution is to create a free account on Cosmian public platform and get an API key. Alternatively, contact Cosmian for a local installation.

Connecting to the KMS Server

The connection details, KMS server URL and API key are passed to the instantiation of the ABE object.

Abe abe = new Abe(new RestClient([KMS_SERVER_URL], [YOUR_API_KEY]));
Import

To access ABE’s methods, you need to import the necessary elements in your file.

from wasmtime import Linker, Module, Store, WasiConfig
from bindings import Abe, Attribute, Ok, Policy, PolicyAxis

store = Store()
wasi_config = WasiConfig()
wasi_config.inherit_stderr()
wasi_config.inherit_stdin()
wasi_config.inherit_stdout()
store.set_wasi(wasi_config)

linker = Linker(store.engine)
linker.define_wasi()

my_module = Module.from_file(store.engine, "abe_gpsw.wasm")
abe = Abe(store, linker, my_module)

def unwrap(wrapped_result):
    if isinstance(wrapped_result, Ok):
        return wrapped_result.value
    raise Exception(wrapped_result.value)
Creating the Master Public and Private Keys

The Master Authority possesses the key pair for the given Policy:

  • a Private Key which is used to generate user keys
  • and a Public Key which is used to encrypt files with any combination of attributes. The public key can be freely distributed to encrypting systems.

The call returns the KMS UIDs of the 2 keys

String[] ids = abe.createMasterKeyPair(policy);
String privateMasterKeyUID = ids[0];
String publicMasterKeyUID = ids[1];

The second parameter fixes the maximum number of revocations of attributes for this Policy (explained above). This number influences the number of public keys which will be ultimately generated for this Security Group and must be kept to a “reasonable” level to reduce security risks associated with multiplying the number of keys.

master_key = unwrap(abe.generate_master_key(store, nb_revocation=20, policy))

Encrypting Data

Data is encrypted using the Master Authority Public Key and at least on attribute from each Policy axis.

Anyone who has access to the Public Key, can encrypt data, however, only users possessing user keys with the right access policy can decrypt data.

This shows the encryption of 3 messages with different attributes.

A low secret marketing message
// - a low secret marketing message
byte[] low_secret_mkg_data = "low_secret_mkg_message".getBytes(StandardCharsets.UTF_8);
Attr[] low_secret_mkg_attributes = new Attr[] { new Attr("Department", "MKG"),
                new Attr("Security Level", "Low Secret") };
byte[] low_secret_mkg_ct = abe.kmsEncrypt(publicMasterKeyUID, low_secret_mkg_data,
                low_secret_mkg_attributes);
low_secret_mkg_msg = unwrap(abe.encrypt(
    store,
    plaintext="low_secret_mkg_message",
    master_public_key=master_key.public_key,
    attributes=[
        Attribute("Department", "MKG"),
        Attribute("Security Level", "Low Secret"),
    ],
    policy=master_key.policy_serialized,
    uid=bytes([1, 2, 3, 4, 5, 6, 7, 8])
))
A top secret marketing message
byte[] top_secret_mkg_data = "top_secret_mkg_message".getBytes(StandardCharsets.UTF_8);
Attr[] top_secret_mkg_attributes = new Attr[] { new Attr("Department", "MKG"),
                new Attr("Security Level", "Top Secret") };
byte[] top_secret_mkg_ct = abe.kmsEncrypt(publicMasterKeyUID, top_secret_mkg_data,
                top_secret_mkg_attributes);
top_secret_mkg_msg = unwrap(abe.encrypt(
    store,
    plaintext="top_secret_mkg_message",
    master_public_key=master_key.public_key,
    attributes=[
        Attribute("Department", "MKG"),
        Attribute("Security Level", "Top Secret"),
    ],
    policy=master_key.policy_serialized,
    uid=bytes([1, 2, 3, 4, 5, 6, 7, 8])
))
A low secret finance message
byte[] low_secret_fin_data = "low_secret_fin_message".getBytes(StandardCharsets.UTF_8);
Attr[] low_secret_fin_attributes = new Attr[] { new Attr("Department", "FIN"),
                new Attr("Security Level", "Low Secret") };
byte[] low_secret_fin_ct = abe.kmsEncrypt(publicMasterKeyUID, low_secret_fin_data,
                low_secret_fin_attributes);
low_secret_finance_msg = unwrap(abe.encrypt(
    store,
    plaintext="low_secret_finance_message",
    master_public_key=master_key.public_key,
    attributes=[
        Attribute("Department", "FIN"),
        Attribute("Security Level", "Low Secret"),
    ],
    policy=master_key.policy_serialized,
    uid=bytes([1, 2, 3, 4, 5, 6, 7, 8])
))

Generating User Keys and Decrypting with them

User Decryption Keys are generated from the Master Private Key using Access Policies. Access Policies are monotonous boolean expressions combining policy attributes.

Let us create 2 different user keys with 2 different access policies to illustrate their effect.

The medium secret marketing user

This user can decrypt messages from the marketing department only with a security level of Medium Secret or below :

AccessPolicy medium_secret_mkg_access = new And(
    new Attr("Department", "MKG"),
    new Attr("Security Level", "Medium Secret")
);
String medium_secret_mkg_user_key_uid = abe.createUserDecryptionKey(
    medium_secret_mkg_access,
    privateMasterKeyUID
);
medium_secret_mkg_user_key = unwrap(abe.generate_user_decryption_key(
    store,
    master_private_key=master_key.private_key,
    access_policy="Department::MKG && Security Level::Medium Secret",
    policy=master_key.policy_serialized,
))

The medium secret marketing user can successfully decrypt a low security marketing message :

assertArrayEquals(
    low_secret_mkg_data,
    abe.kmsDecrypt(medium_secret_mkg_user_key_uid, low_secret_mkg_ct)
);
message = unwrap(
    abe.decrypt(store, medium_secret_mkg_user_key, low_secret_mkg_msg)
)

.. however it can neither decrypt a marketing message with higher security :

try {
    abe.kmsDecrypt(medium_secret_mkg_user_key_uid, top_secret_mkg_ct);
    throw new RuntimeException("Oh... something is wrong !");
} catch (CosmianException e) {
    // fine: the user is not be able to decrypt
}
try :
    message = unwrap(
        abe.decrypt(store, medium_secret_mkg_user_key, top_secret_mkg_msg)
    )
except:
    print("Oh... something is wrong !")

… nor decrypt a message from another department even with a lower security :

try {
    abe.kmsDecrypt(medium_secret_mkg_user_key_uid, low_secret_fin_ct);
    throw new RuntimeException("Oh... something is wrong !");
} catch (CosmianException e) {
    // fine: the user is not be able to decrypt
}
try :
    message = unwrap(
        abe.decrypt(store, medium_secret_mkg_user_key, low_secret_finance_msg)
    )
except:
    print("Oh... something is wrong !")
The top secret marketing financial user

This user can decrypt messages from the marketing department OR the financial department that have a security level of Top Secret or below

AccessPolicy top_secret_mkg_fin_access = new And(
    new Or(new Attr("Department", "MKG"), new Attr("Department", "FIN")),
    new Attr("Security Level", "Top Secret")
);
String top_secret_mkg_fin_user_key_uid = abe.createUserDecryptionKey(
    top_secret_mkg_fin_access,
    privateMasterKeyUID
);
top_secret_mkg_fin_user_key = unwrap(abe.generate_user_decryption_key(
    store,
    master_private_key=master_key.private_key,
    access_policy="(Department::MKG || Department::FIN) && Security Level::Top Secret",
    policy=master_key.policy_serialized,
))

As expected, the top secret marketing financial user can successfully decrypt all messages

assertArrayEquals(
    low_secret_mkg_data,
    abe.kmsDecrypt(top_secret_mkg_fin_user_key_uid, low_secret_mkg_ct)
);
assertArrayEquals(
    top_secret_mkg_data,
    abe.kmsDecrypt(top_secret_mkg_fin_user_key_uid, top_secret_mkg_ct)
);
assertArrayEquals(
    low_secret_fin_data,
    abe.kmsDecrypt(top_secret_mkg_fin_user_key_uid, low_secret_fin_ct)
);
decrypted_mkg_low_secret_msg = unwrap(
    abe.decrypt(store, top_secret_mkg_fin_user_key, low_secret_mkg_msg)
)
decrypted_mkg_top_secret_msg = unwrap(
    abe.decrypt(store, top_secret_mkg_fin_user_key, top_secret_mkg_msg)
)
decrypted_fin_low_secret_msg = unwrap(
    abe.decrypt(store, top_secret_mkg_fin_user_key, low_secret_finance_msg)
)

Attribute rotation

At anytime the Master Authority can rotate an attribute.

When that happens future encryption of data for a given attribute cannot be decrypted with keys which are not “refreshed” for that attribute.

We are going to revoke the MKG attribute.

As long as a key is active in the KMS (the key has not been revoked), it will be automatically rekeyed when an attribute is revoked.

Before, we will make a local copy of the current medium secret marketing user to show what happens to non-refreshed keys after the attribute rotation.

// retrieve the key
PrivateKey original_medium_secret_mkg_user_key = 
    abe.retrieveUserDecryptionKey(medium_secret_mkg_user_key_uid);

// Now revoke the MKG attribute - all active keys will be rekeyed
abe.revokeAttributes(privateMasterKeyUID, new Attr[] { new Attr("Department", "MKG") });

// ... and reimport the non rekeyed original medium secret marketing user key
// under a new UID
abe.importUserDecryptionKey("original_medium_secret_mkg_user_key_uid",
                original_medium_secret_mkg_user_key,
                true);

Update the policy, with attribute rotation :

updated_policy = abe.rotate_attributes(
    store,
    policy=master_key.policy_serialized,
    attributes=[
        Attribute("Department", "MKG"),
    ],
)
updated_policy = unwrap(updated_policy)

Create a new medium secret marketing message :

byte[] medium_secret_mkg_data = "medium_secret_mkg_message".getBytes(StandardCharsets.UTF_8);
Attr[] medium_secret_mkg_attributes = new Attr[] { 
    new Attr("Department", "MKG"),
    new Attr("Security Level", "Medium Secret") 
};
byte[] medium_secret_mkg_ct = abe.kmsEncrypt(
    publicMasterKeyUID, 
    medium_secret_mkg_data,
    medium_secret_mkg_attributes
);
medium_secret_mkg_msg = abe.encrypt(
    store,
    plaintext="medium_secret_mkg_message",
    master_public_key=master_key.public_key,
    attributes=[
        Attribute("Department", "MKG"),
        Attribute("Security Level", "Medium Secret"),
    ],
    policy=updated_policy,
    uid=bytes([1, 2, 3, 4, 5, 6, 7, 8])
)
medium_secret_mkg_msg = unwrap(medium_secret_mkg_msg)

Decrypt message :

The automatically rekeyed medium secret marketing user key can still decrypt the “old” low secret marketing message, as well as the new medium secret marketing message.

assertArrayEquals(
    low_secret_mkg_data,
    abe.kmsDecrypt(medium_secret_mkg_user_key_uid, low_secret_mkg_ct)
);
assertArrayEquals(
    medium_secret_mkg_data,
    abe.kmsDecrypt(medium_secret_mkg_user_key_uid, medium_secret_mkg_ct)
);

The user can still decrypt the old marketing low secret message :

# decryption of the old message
decrypted_old_low_secret_mkg_msg = unwrap(
    abe.decrypt(store, medium_secret_mkg_user_key, low_secret_mkg_msg)
)

No simple user can decrypt the message until its key is refreshed :

try :
    decrypted_medium_secret_mkg_msg = unwrap(
        abe.decrypt(store, medium_secret_mkg_user_key, medium_secret_mkg_msg)
    )
except :
    print("Oh... something is wrong !")

Key refresh and decryption :

# key refresh
medium_secret_mkg_user_key = unwrap(abe.generate_user_decryption_key(
    store,
    master_private_key=master_key.private_key,
    access_policy="Department::MKG && Security Level::Medium Secret",
    policy=updated_policy,
))

# decryption of the new message
decrypted_medium_secret_mkg_msg = unwrap(
    abe.decrypt(store, medium_secret_mkg_user_key, medium_secret_mkg_msg)
)
# decryption of the old message
decrypted_old_low_secret_mkg_msg = unwrap(
    abe.decrypt(store, medium_secret_mkg_user_key, low_secret_mkg_msg)
)

Likewise, the top secret marketing financial user can decrypt all messages old and new :

// old
assertArrayEquals(
    low_secret_mkg_data,
    abe.kmsDecrypt(top_secret_mkg_fin_user_key_uid, low_secret_mkg_ct)
);
assertArrayEquals(
    top_secret_mkg_data,
    abe.kmsDecrypt(top_secret_mkg_fin_user_key_uid, top_secret_mkg_ct)
);
assertArrayEquals(
    low_secret_fin_data,
    abe.kmsDecrypt(top_secret_mkg_fin_user_key_uid, low_secret_fin_ct)
);
// and new
assertArrayEquals(
    medium_secret_mkg_data,
    abe.kmsDecrypt(top_secret_mkg_fin_user_key_uid, medium_secret_mkg_ct)
);

However, the old, non rekeyed medium secret marketing user key can still decrypt the old low secret marketing message but NOT the new medium secret marketing message :

// the old
assertArrayEquals(
    low_secret_mkg_data,
    abe.kmsDecrypt("original_medium_secret_mkg_user_key_uid", low_secret_mkg_ct)
);
// .but NOT the new
try {
        abe.kmsDecrypt("original_medium_secret_mkg_user_key_uid", medium_secret_mkg_ct);
        throw new RuntimeException("Oh... something is wrong !");
} catch (CosmianException e) {
        // fine: the user is not be able to decrypt
}
# refresh user key
top_secret_mkg_fin_user_key = unwrap(abe.generate_user_decryption_key(
    store,
    master_private_key=master_key.private_key,
    access_policy="(Department::MKG || Department::FIN) && Security Level::Top Secret",
    policy=updated_policy,
))

# the old message
decrypted_old_low_secret_mkg_msg = unwrap(
    abe.decrypt(store, top_secret_mkg_fin_user_key, low_secret_mkg_msg)
)

# the new message
decrypted_medium_secret_mkg_msg = unwrap(
    abe.decrypt(store, top_secret_mkg_fin_user_key, medium_secret_mkg_msg)
)