Skip to content

Speed: using the native library

Using a local cache and the native library to perform local encryption and decryption operations considerably speeds them up.

Download or build the native library

The library is written in Rust and is open-sourced.

Pre-built versions are available here but in case no version matches your system, follow these instructions to build the library.

The cosmian_java_lib uses Java Native Access to call the native library.

In your java project, the library needs to be placed inside in

  • src/main/resources/linux-x86-64 folder for a Linux Intel machine
  • src/main/resources/linux-amd64 folder for a Linux AMD machine
  • src/main/resources/darwin folder for a Mac running MacOS
  • src/main/resources/win32-x86 folder for a Windows machine

To run tests only, place the library inside the corresponding folder above replacing main with test

Check the JNA documentation for alternative locations for the native library.

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)
    

Using the native library for attributes encryption

Recovering the keys from the KMS

In order to perform local encryption or decryption, respectively the public key or the user decryption key must be recovered from the KMS.

Abe abe = new Abe(new RestClient(KMS_SERVER_URL, API_KEY));

// public key (for encryption)
PublicKey publicMasterKey = abe.retrievePublicMasterKey(publicMasterKeyUniqueIdentifier);

// user decryption key
PrivateKey userDecryptionKey = abe.retrieveUserDecryptionKey(userDecryptionKeyUniqueIdentifier);

The keys can be serialized and deserialized for local storage.

// serializing
byte[] jsonBytes = publicKey.toJson().getBytes(StandardCharsets.UTF_8);

// de-serializing
PublicKey publicKey = PublicKey.fromJson(new String(jsonBytes, StandardCharsets.UTF_8));

Generate keys

def unwrap(wrapped_result):
    if isinstance(wrapped_result, Ok):
        return wrapped_result.value
    raise Exception(wrapped_result.value)

# Generate master key
master_key = unwrap(abe.generate_master_key(store, nb_revocation=20, policy))

# Generate user decryption key
user_decryption_key = unwrap(abe.generate_user_decryption_key(
  store,
  master_private_key=master_key.private_key,
  access_policy="Attribute1::Value1 && Attribute2::Value2",
  policy=master_key.policy_serialized,
))

Encrypting

1. create an encryption cache

To speed up repeated encryptions, the public key should be loaded in memory in an encryption cache

PublicKey publicKey = ....
int encryptionCacheHandle;
try {
    encryptionCacheHandle = Ffi.createEncryptionCache(publicKey);
} catch (FfiException e) {
    throw new AppException("Failed creating the encryption cache:" + e.getMessage(), e);
}
cache_handle = unwrap(abe.create_encryption_cache(
    store,
    master_key.public_key,
    master_key.policy_serialized
))

2. encrypt

Attributes encryption is an hybrid encryption scheme:

  • the header is a public key encryption of a randomly generated symmetric key
  • the encrypted content is the symmetric encryption of the clear text using the generated symmetric key
EncryptedHeader encryptedHeader;
try {
    encryptedHeader = Ffi.encryptHeaderUsingCache(encryptionCacheHandle, attributes);
} catch (FfiException | CosmianException e) {
    throw new AppException("Failed to encrypt the header: " + e.getMessage(), e);
}
byte[] encryptedContent;
try {
    encryptedContent = Ffi.encryptBlock(encryptedHeader.getSymmetricKey(), hash, 0, lineBytes);
} catch (FfiException e) {
    throw new AppException("Failed to encrypt the content: " + e.getMessage(), e);
}

If you wish to prepend the symmetrically encrypted content with the encrypted header, as is done by the KMS, you can use the following

// Create a full message with header+encrypted data. The length of the header
// is pre-pended.
ByteBuffer headerSize = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN)
        .putInt(encryptedHeader.getEncryptedHeaderBytes().length);
// Write the message
ByteArrayOutputStream bao = new ByteArrayOutputStream();
bao.write(headerSize.array());
bao.write(encryptedHeader.getEncryptedHeaderBytes());
bao.write(encryptedContent);
bao.flush();
byte[] ciphertext = bao.toByteArray();


// Parse a cipher text back into encrypted header and encrypted content
// First recover the header length
int headerSize = ByteBuffer.wrap(ciphertext).order(ByteOrder.BIG_ENDIAN).getInt(0);
// Then recover the encrypted header and encrypted content
byte[] encryptedHeader = Arrays.copyOfRange(ciphertext, 4, 4 + headerSize);
byte[] encryptedContent = Arrays.copyOfRange(ciphertext, 4 + headerSize, ciphertext.length);
plaintext = "confidential message"
uid = bytes([1, 2, 3, 4, 5, 6, 7, 8])

encryptedHeader = unwrap(
    abe.encrypt_hybrid_header(store, [
        Attribute("Department", "MKG"),
        Attribute("Security Level", "Protected"),
    ], cache_handle, uid)
)

sym_ciphertext = unwrap(abe.encrypt_hybrid_block(
    store, plaintext, encryptedHeader.symmetric_key, uid, 0)
)

header_len = len(encryptedHeader.encrypted_header_bytes).to_bytes(4, byteorder='big')

ciphertext = b"".join(
    [header_len, encryptedHeader.encrypted_header_bytes, sym_ciphertext]
)

header = b"".join(
  [header_len, encryptedHeader.encrypted_header_bytes]
)

3. destroy the encryption cache

When you are done encrypting, destroy the encryption cache to recover memory

try {
    Ffi.destroyEncryptionCache(encryptionCacheHandle);
} catch (FfiException e) {
    throw new AppException("Failed destroying the encryption cache:" + e.getMessage(), e);
}
abe.destroy_encryption_cache(store, cache_handle)

Decrypting

1. create a decryption cache

To speed up repeated decryptions, the user decryption key should be loaded in memory in a decryption cache

PrivateKey userDecryptionKey = ...
int decryptionCacheHandle;
try {
    decryptionCacheHandle = Ffi.Ffi.createDecryptionCache(userDecryptionKey);
} catch (FfiException e) {
    throw new AppException("Failed creating the decryption cache:" + e.getMessage(), e);
}
cache_handle = unwrap(abe.create_decryption_cache(
    store,
    bytes.fromhex(user_decryption_key),
))

2. decrypt

Attributes encryption is an hybrid encryption scheme:

  • the header is a public key encryption of a randomly generated symmetric key
  • the encrypted content is the symmetric encryption of the clear text using the generated symmetric key
byte[] clearText;
try {
    // If the cipher text was generated by the KMS, the encrypted header is pre-pended
    // to the encrypted content
    // Parse the message by first recovering the header length
    int headerSize = ByteBuffer.wrap(ciphertext).order(ByteOrder.BIG_ENDIAN).getInt(0);
    System.out.println("HEADER SIZE: " + headerSize);
    // Then recover the encrypted header and encrypted content
    byte[] encryptedHeader = Arrays.copyOfRange(ciphertext, 4, 4 + headerSize);
    byte[] encryptedContent = Arrays.copyOfRange(ciphertext, 4 + headerSize, ciphertext.length);

    // Decrypt he header to recover the symmetric AES key
    DecryptedHeader decryptedHeader = Ffi.decryptHeader(userKey, encryptedHeader);
    // decrypt the content, passing the unique id and block number
    clearText = Ffi.decryptBlock(decryptedHeader.getSymmetricKey(), encryptedContent);
} catch (FfiException | CosmianException e) {
    throw new AppException("Failed to decrypting the ciphertext: " + e.getMessage(), e);
}
# Header decryption: asymmetric decryption
cleartextSymmetricKey = unwrap(abe.decrypt_hybrid_header(store, cache_handle, header))

# AES Symmetric part decryption
cleartext = unwrap(abe.decrypt_hybrid_block(store, sym_ciphertext, bytes.fromhex(cleartextSymmetricKey), uid, 0)).decode()

3. destroy the decryption cache

When you are done decrypting, destroy the decryption cache to recover memory

try {
    Ffi.destroyDecryptionCache(decryptionCacheHandle);
} catch (FfiException e) {
    throw new AppException("Failed destroying the decryption cache:" + e.getMessage(), e);
}
abe.destroy_decryption_cache(store, cache_handle)

© Copyright 2018-2022 Cosmian. All rights reserved