Skip to content

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 cloudproof_java 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.

Coming soon!

Initialize the FFI library

The native code can be found in the: - on Linux, libcosmian_cover_crypt.so - on MacOS, libcosmian_cover_crypt.dylib - on Windows, cosmian_cover_crypt.dll

static final Implementation abeImplementation =
  Implementation.CoverCrypt;
static final FfiWrapper INSTANCE =
  (FfiWrapper) Native.load(
    "cosmian_cover_crypt",
    FfiWrapper.class
  );
static final Ffi ffi =
  new Ffi(
    INSTANCE,
    new Specifications(abeImplementation)
  );

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], [YOUR_API_KEY]),
  new Specifications(Implementation.CoverCrypt)
);

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

// 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));

Coming soon!

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);
}

Coming soon!

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;
// The data we want to encrypt/decrypt
byte[] data = "This s a test message".getBytes(StandardCharsets.UTF_8);
// A unique ID associated with this message. The unique id is used to
// authenticate the message in the AES encryption scheme.
// Typically this will be a hash of the content if it is unique, a unique
// filename or a database unique key
byte[] uid = MessageDigest.getInstance("SHA-256").digest(data);

try {
    encryptedContent = ffi.encryptBlock(encryptedHeader.getSymmetricKey(), uid, 0, data);
} 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);

Coming soon!

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);
}

Coming soon!

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.createDecryptionCache(userDecryptionKey);
} catch (FfiException e) {
    throw new AppException("Failed creating the decryption cache:" + e.getMessage(), e);
}

Coming soon!

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);
  }

Coming soon!

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);
}

Coming soon!

© Copyright 2018-2022 Cosmian. All rights reserved