ubiq-rust 1.0.0

SDK for the Ubiq Security platform
Documentation

Ubiq Security Rust Library

The Ubiq Security Rust library provides convenient interaction with the Ubiq Security Platform API from applications written in the Rust language. It includes a pre-defined set of types and builders that provide simple interfaces to encrypt and decrypt data.

Documentation

See the Ubiq API docs.

You can improve it by sending pull requests to this repository.

Requirements

  • Rust 1.85 or later
  • An async runtime — this library exposes async APIs. tokio is recommended but any runtime should work.
  • OpenSSL development libraries installed on your system (e.g. libssl-dev on Debian/Ubuntu, openssl via Homebrew on macOS)

Installation

Add the dependency to your Cargo.toml:

[dependencies]
ubiq-rust = "1.0.0"

Or use cargo add:

cargo add ubiq-rust

Ubiq Unstructured Encryption

The library needs to be configured with your account credentials which is available in your Ubiq Dashboard credentials. The credentials can be set using environment variables, loaded from an explicit file, or read from the default location (~/.ubiq/credentials).

Read credentials from a specific file and use a specific profile

use ubiq_rust::builder::{Builder, Unstructured};

let enc = Builder::<Unstructured>::new()
    .with_cred_path("some-credential-file")
    .with_profile("some-profile")
    .build_encryption()
    .await?;

Configuration information can also be passed when building. See Configuration below.

Read credentials from ~/.ubiq/credentials and use the default profile

use ubiq_rust::builder::{Builder, Unstructured};

let enc = Builder::<Unstructured>::new()
    .build_encryption()
    .await?;

Use environment variables to set the credential values

The following environment variables are supported:

UBIQ_ACCESS_KEY_ID
UBIQ_SECRET_SIGNING_KEY
UBIQ_SECRET_CRYPTO_ACCESS_KEY
UBIQ_SERVER            (optional, defaults to api.ubiqsecurity.com)

When no credentials file is found or a key is absent from the file, the library falls back to these environment variables automatically. No special builder call is required — simply ensure the variables are set and use the default builder:

use ubiq_rust::builder::{Builder, Unstructured};

let enc = Builder::<Unstructured>::new()
    .build_encryption()
    .await?;

Handling exceptions

All fallible operations return a Result<T, ubiq_rust::error::Error>. Use standard Rust error handling to inspect and recover from failures.

use ubiq_rust::builder::{Builder, Unstructured};

match Builder::<Unstructured>::new().build_encryption().await {
    Ok(mut enc) => {
        // use enc
    }
    Err(e) => {
        eprintln!("Error building encryptor: {}", e);
    }
}

Encrypt a simple block of data

Pass data into the encrypt method on an Encryption object. The encrypted data will be returned as a Vec<u8>. The plaintext input must be a &[u8] slice.

use ubiq_rust::builder::{Builder, Unstructured};

let plaintext_data: &[u8] = b"plaintext data";

let mut enc = Builder::<Unstructured>::new()
    .build_encryption()
    .await?;

let encrypted_data: Vec<u8> = enc.encrypt(plaintext_data)?;

Decrypt a simple block of data

Pass encrypted data into the decrypt method on a Decryption object. The plaintext data will be returned as a Vec<u8>.

use ubiq_rust::builder::{Builder, Unstructured};

let mut dec = Builder::<Unstructured>::new()
    .build_decryption()?;

// encrypted_data obtained from a previous encrypt call
let plaintext_data: Vec<u8> = dec.decrypt(&encrypted_data).await?;

Encrypt a large data element where data is loaded in chunks

  • Build an Encryption object using the builder.
  • Call begin to initialize the encryption and receive any header bytes.
  • Call update repeatedly with each chunk of data until all data is processed.
  • Call end to retrieve any remaining encrypted bytes and finalize.
use ubiq_rust::builder::{Builder, Unstructured};

// Process 1 MiB of plaintext data at a time
const BLOCK_SIZE: usize = 1024 * 1024;

// Rest of the program
// ....

let mut enc = Builder::<Unstructured>::new()
    .build_encryption()
    .await?;

// Write out the header information
let mut encrypted_data = enc.begin()?;

// Loop until the end of the input is reached
for chunk in plaintext_data.chunks(BLOCK_SIZE) {
    encrypted_data.extend(enc.update(chunk)?);
}

// Retrieve any remaining encrypted data and finalize
encrypted_data.extend(enc.end()?);

Decrypt a large data element where data is loaded in chunks

  • Build a Decryption object using the builder.
  • Call begin to initialize the decryption and receive any header bytes.
  • Call update repeatedly with each chunk of data until all data is processed.
  • Call end to retrieve any remaining plaintext bytes and finalize.
use ubiq_rust::builder::{Builder, Unstructured};

// Process 1 MiB of encrypted data at a time
const BLOCK_SIZE: usize = 1024 * 1024;

// Rest of the program
// ....

let mut dec = Builder::<Unstructured>::new()
    .build_decryption()?;

// Initialize the decryption state
let mut plaintext_data = dec.begin()?;

// Loop until the end of the input is reached
// encrypted_data obtained from a previous encrypt call
for chunk in encrypted_data.chunks(BLOCK_SIZE) {
    plaintext_data.extend(dec.update(chunk).await?);
}

// Retrieve any remaining plaintext data and finalize
plaintext_data.extend(dec.end()?);

Encrypt and Decrypt with Reuse

To reuse encryption/decryption objects, build them once and call begin / update / end in a loop. Encryption takes an extra parameter via with_uses, specifying the number of separate encryptions to perform with the same key. This number may be limited by the server. If with_uses is not called, the default is 1 (a single use per key fetch).

use ubiq_rust::builder::{Builder, Unstructured};

let raw_data = ["alligator", "otter", "eagle owl", "armadillo", "dormouse", "ground hog"];

let mut enc = Builder::<Unstructured>::new()
    .with_uses(6)
    .build_encryption()
    .await?;

let mut dec = Builder::<Unstructured>::new()
    .build_decryption()?;

let mut encrypted_data: Vec<Vec<u8>> = Vec::new();

for animal in &raw_data {
    let mut cipher = enc.begin()?;
    cipher.extend(enc.update(animal.as_bytes())?);
    cipher.extend(enc.end()?);
    encrypted_data.push(cipher);
}

for data in &encrypted_data {
    let mut plain = dec.begin()?;
    plain.extend(dec.update(data).await?);
    plain.extend(dec.end()?);
    println!("{}", String::from_utf8_lossy(&plain));
}

Ubiq Structured Encryption

This library incorporates Ubiq Structured Encryption.

Requirements

  • Please follow the same requirements as described above for unstructured encryption/decryption.

Usage

You will need to obtain account credentials in the same way as described above for conventional encryption/decryption. When you do this in your Ubiq Dashboard credentials, you'll need to enable access to structured datasets. The credentials can be set using environment variables, loaded from an explicitly specified file, or read from the default location (~/.ubiq/credentials).

Caching

When performing encryption/decryption, keys are retrieved from the Ubiq API. To speed up performance and reduce the number of calls to the API, keys are stored in a cache inside the structured::Encryption object. It is recommended to reuse the object for multiple operations rather than rebuilding it each time.

Encrypt a social security text field — simple interface

Pass the dataset name and plaintext into the encrypt method. The encrypted text will be returned as a String. A single Encryption object handles both encrypt and decrypt operations.

use ubiq_rust::builder::{Builder, Structured};

let dataset_name = "SSN";
let plain_text = "123-45-6789";

let mut enc = Builder::<Structured>::new()
    .with_cred_path("./credentials")
    .build()?;

let ciphertext = enc.encrypt(dataset_name, plain_text, None).await?;

println!("ENCRYPTED ciphertext= {}", ciphertext);

Decrypt a social security text field — simple interface

Pass the dataset name and ciphertext into the decrypt method on the same Encryption object. The decrypted text will be returned as a String.

use ubiq_rust::builder::{Builder, Structured};

let dataset_name = "SSN";
let cipher_text = "300-0E-274t";

let mut enc = Builder::<Structured>::new()
    .with_cred_path("./credentials")
    .build()?;

let plaintext = enc.decrypt(dataset_name, cipher_text, None).await?;

println!("DECRYPTED plaintext= {}", plaintext);

Additional information on how to use these models in your own applications is available by contacting Ubiq.

Configuration

A sample configuration file is shown below. The configuration is in JSON format (example). Configuration can be set either as a file read from a path, or as a pre-built Configuration object.

Reading from File

By default, configuration is read from ~/.ubiq/configuration. To use a different file, pass its path to the builder with with_config_path:

use ubiq_rust::builder::{Builder, Unstructured};

let enc = Builder::<Unstructured>::new()
    .with_cred_path("path/to/credentials")
    .with_config_path("path/to/configuration")
    .build_encryption()
    .await?;

Pre-built Configuration Object

You can construct a Configuration value directly and supply it to the builder with with_config. This is useful when you want to set configuration programmatically rather than loading from a file:

use ubiq_rust::builder::{Builder, Unstructured};
use ubiq_rust::config::Configuration;

let mut config = Configuration::default();
config.logging.verbose = true;

let enc = Builder::<Unstructured>::new()
    .with_config(config)
    .build_encryption()
    .await?;

Note: You cannot supply both with_config_path and with_config at the same time; doing so will return a BuilderError at build time.

Disallowing Default Configuration

By default, if no configuration file is found at the default file location the library falls back to a built-in Configuration::default(). To require that a configuration file be present and return an error when it cannot be loaded, call disallow_default_config:

use ubiq_rust::builder::{Builder, Unstructured};

let enc = Builder::<Unstructured>::new()
    .disallow_default_config()
    .build_encryption()
    .await?;

Event Reporting

The event_reporting section controls how often usage is reported to the server.

  • wake_interval — number of seconds to sleep before waking to check whether there has been enough activity to report usage
  • minimum_count — minimum number of usage records that must be queued before sending
  • flush_interval — seconds to sleep before all queued usage is flushed to the server
  • trap_exceptions — whether exceptions encountered while reporting usage are silently ignored (true) or propagated as errors (false)
  • timestamp_granularity — how granular the timestamp will be when reporting events. Valid values are:
    • "MICROS" — DEFAULT: values reported down to microsecond resolution
    • "MILLIS" — values reported to the millisecond
    • "SECONDS" — values reported to the second
    • "MINUTES" — values reported to the minute
    • "HOURS" — values reported to the hour
    • "HALF_DAYS" — values reported to half day
    • "DAYS" — values reported to the day

Key Caching

The key_caching section controls how and when keys are cached.

  • unstructured — whether keys are cached for unstructured decryption (default: true)
  • structured — whether keys are cached for structured encryption/decryption (default: true)
  • encrypt — whether cached keys are stored encrypted. Encrypted keys are harder to read from memory but require decryption on each use (default: false)
  • ttl_seconds — how many seconds before a cache entry expires and the key is re-fetched (default: 1800)

Logging

The logging section controls logging output.

  • verbose — enables or disables verbose logging such as event processing and caching activity

Example Configuration JSON

{
  "event_reporting": {
    "wake_interval": 1,
    "minimum_count": 2,
    "flush_interval": 2,
    "trap_exceptions": false,
    "timestamp_granularity": "MICROS"
  },
  "key_caching": {
    "unstructured": true,
    "structured": true,
    "encrypt": false,
    "ttl_seconds": 1800
  }
}

Zeroization

This library takes care to scrub sensitive cryptographic material from memory when it is no longer needed, reducing the risk of secrets leaking through memory dumps, core files, or swap.

How it works

  • The zeroize crate and its ZeroizeOnDrop derive macro are used throughout the library.
  • Credentials — API key/secret fields are zeroized when the Credentials struct is dropped.
  • Unstructured encryption/decryption — key material, plaintext buffers, and cached private keys are zeroized on drop.
  • Structured encryptionKeyResolver and pipeline OpContext zeroize their key caches and plaintext values on drop.
  • Transient key material — decrypted key bytes are wrapped in Zeroizing<Vec<u8>> so they are scrubbed as soon as the wrapper is dropped.
  • Cache — a custom Zeroize implementation on the generic Cache<T> type ensures all cached secrets are scrubbed when the cache is cleared or dropped.
  • AES/CBC ciphers — built with zeroize-enabled feature flags so internal cipher state is also cleared.

What this means for you

No action is required — zeroization is fully automatic. Sensitive material is scrubbed when encryption and decryption objects go out of scope. To get the full benefit, simply let these objects drop naturally and avoid using std::mem::forget, which prevents destructors (and therefore zeroization) from running.

Ubiq API Error Reference

Occasionally, you may encounter issues when interacting with the Ubiq API.

Status Code Meaning Solution
400 Bad Request Check name of datasets and credentials are complete.
401 Authentication issue Check you have the correct API keys, and it has access to the datasets you are using. Check dataset name.
426 Upgrade Required You are using an out-of-date version of the library, or are trying to use newer features not supported by the library you are using. Update the library and try again.
429 Rate Limited You are performing operations too quickly. Either slow down, or contact support@ubiqsecurity.com to increase your limits.
500 Internal Server Error Something went wrong. Contact support if this persists.
504 Internal Error Possible API key issue. Check credentials or contact support.

Benchmarks

Currently benchmarks are contained in a single file, meant to be ran via CLI parameters - benches/load_test.rs. This file is meant to be fed a json file containing example data. Currently, these files are contained in the ubiq-test-data submodule. Credentials will be needed to run this as they are live dataset examples.

Example:

cargo bench --bench load_test -- -i ubiq-test-data/prod/1m-part-001.json -P "Max Benchmarks"