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 machinesrc/main/resources/linux-amd64
folder for a Linux AMD machinesrc/main/resources/darwin
folder for a Mac running MacOSsrc/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.
-
Clone the
abe_gspw
repository: -
Generate the wasm file and python’s bindings:
We are using
*.wit
format to provide a generic interface, and generate different languages bindings. After theabe.wit
has been generated, we usewit-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 withwit-bindgen
. To complete all those steps, you can simply run : -
Add the generated
abe_gspw.wasm
andbindings.py
files to your python project. -
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.
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
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
Decrypting¶
1. create a decryption cache¶
To speed up repeated decryptions, the user decryption key should be loaded in memory in a decryption cache
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);
}
3. destroy the decryption cache¶
When you are done decrypting, destroy the decryption cache to recover memory