1. Docs
  2. Cloaked AI
  3. Getting Started
  1. Docs
  2. Cloaked AI
  3. Getting Started

Getting Started

The Cloaked AI functionality is included in the IronCore Labs Alloy SDK, which is available for use from various programming languages. We currently have SDKs in Rust, Python, and Kotlin. The Kotlin SDK can be used directly in Java applications. The SDKs include functionality for encrypting/decrypting embeddings as well as encrypting/decrypting associated documents. These documents could be data such as Pinecone metadata or any other related data that should be protected.

(See product descriptions, use cases, pricing, and other information here).

You can get a hands-on look at how Cloaked AI works with our demo. We built a Docker container around the SDK that exposes a REST interface you can use to experiment with Cloaked AI.

When you are ready to add Cloaked AI’s privacy and security features to your application, the SDK pages mentioned above detail the steps required to reference the SDK in your implementation. You can find links to the developer documentation on the language-specific SDK pages as well.

The Alloy SDK provides a method to construct a API object that is used to invoke the SDK operations. You choose whether you want to use the Standalone or the SaasShield API - both API objects provide a collection of methods for encrypting the vector and for encrypting associated data fields. The vector is encrypted using property-preserving encryption that allows nearest neighbor searches, clustering, and other vector operations to continue to function on the encrypted vector. Associated data is processed as a document (a map of field name to field value) by the SDKs; each document can be encrypted using either deterministic encryption (to be used if you need to be able to perform filtering operations on the vectors using the one of the fields in the document) or standard encryption (which provides the best security if you don’t need to filter on the fields). You can encrypt one document deterministically and another with standard encryption if you have both types of data associated with a vector.

The API object also provides the inverse methods to decrypt the vector, the deterministically encrypted associated fields, and the standard encrypted associated fields. And there are methods to prepare a vector for use as a query to find nearest neighbors in a vector database, and to prepare a data string to find matches in deterministically encrypted data fields.

The constructor for the object takes a set of secrets and an approximation factor (in SaaS Shield mode the secrets come from the TSP). The secrets are used to derive keys for the encryption and decryption operations - one secret for vector operations, one for standard encryption, and one for deterministic encryption. If you don’t need one of these types of encryption, you can omit that secret.

The approximation factor allows you to trade off additional security against more precision in searches over the encrypted vectors. A larger approximation factor will introduce more changes into the elements of the vector, making it more difficult to match patterns in the encrypted data against expected patterns for unencrypted data. The greater changes in the vectors will decrease the relevance of results returned by searches somewhat; we have an analysis of this in our search accuracy section.

Example use case

Suppose you are generating vector embeddings using one of the SBERT models like all-mpnet-base-v2 and storing them in a vector database. Maybe you have some associated data with each vector, including a sensitive field like a name or phone number that you want to use for filtering, and a sensitive field like the source data that produced the embedding that is not required for filtering, but which should be stored with the vector in the database.

First, you need to construct a IronCoreAlloy object, providing a set of secrets and an approximation factor to the constructor. The secret should be managed like any other secret, encryption key, or API credentials in your application; you might wrap and unwrap it using a KSM or store it in an encrypted secret in your Kubernetes environment, for instance.

Once you have the API object, you can just call encrypt on it for each vector embedding, getting back an encrypted vector. You then create a map of field name to value for the fields you want to use for filtering and encrypt that deterministically. Construct a similar map for the fields you want to protect without filtering and encrypt that using standard encryption. Combine the two maps into one object that you will store as associated data along with the encrypted vector. Write the vector and data object to the database.

Searching for vectors

You are probably storing the encrypted vectors in a vector database so you can do nearest neighbor searches on them. When you are ready to query the database, you will use the API object to prepare the query. Encrypt your query vector just as you encrypted the embeddings. If you have any filters to apply to the search, construct a document containing the field(s) and deterministically encrypt it. You now have the pieces you need to search the database - it will match the deterministically encrypted fields for exact match filters, and the nearest neighbor search will return similar encrypted vectors. You can use the API object to decrypt any associated data returned with the vectors, or to decrypt the vectors themselves if you need them after the search.

Consult the API docs for your language of choice for detailed explanations of the objects and methods available in the SDK.

IronCore Labs Alloy SDK Code Examples

The SDK can be initialized in either SaaS Shield or Standalone mode. The function calls will be identical between the two, but they differ in their creation.

For SaaS Shield, secrets will be derived using the Tenant Security Proxy. For Standalone, secrets need to be specified manually.

SaaS Shield mode:

Python
tsp_uri = "tenant-security-proxy.local" # URI for TSP api_key = "my_api_key " # Retrieve from Configuration Broker approximation_factor = 7.2 # security vs. performance tradeoff config = SaasShieldConfiguration(tsp_uri, api_key, False, approximation_factor) sdk = SaasShield(config)
Java
String tspUri = "tenant-security-proxy.local"; // URI for TSP String apiKey = "my_api_key "; // Retrieve from Configuration Broker Float approximationFactor = 7.2f; // security vs. performance tradeoff SaasShieldConfiguration config = new SaasShieldConfiguration(tspUri, apiKey, false, approximationFactor); SaasShield sdk = new SaasShield(config);

Standalone mode:

Python
key_bytes = b"" # 32 cryptographically-random bytes approximation_factor = 7.2 # security vs. performance tradeoff vector_secrets = { "contacts": VectorSecret( approximation_factor, RotatableSecret(StandaloneSecret(1, Secret(key_bytes)), None), ) } standard_secrets = StandardSecrets(None, []) deterministic_secrets = {} config = StandaloneConfiguration(standard_secrets, deterministic_secrets, vector_secrets) sdk = Standalone(config)
Java
byte[] keyBytes = "".getBytes(); // 32 cryptographically-random bytes Float approximationFactor = 7.2f; // security vs. performance tradeoff StandaloneSecret standaloneSecret = new StandaloneSecret(1, new Secret(keyBytes)); VectorSecret vectorSecret = new VectorSecret(approximationFactor, new RotatableSecret(standaloneSecret, null)); Map<String, VectorSecret> vectorSecrets = Collections.singletonMap("contacts", vectorSecret); StandardSecrets standardSecrets = new StandardSecrets(null, new ArrayList<>()); Map<String, RotatableSecret> deterministicSecrets = new HashMap<String, RotatableSecret>(); StandaloneConfiguration config = new StandaloneConfiguration(standardSecrets, deterministicSecrets, vectorSecrets); Standalone sdk = new Standalone(config);

Once the client has been initialized, the encrypt function will derive a key for the provided tenant and encrypt the vector to them. The paired ICL info is needed if you want to be able to decrypt the vector later. If that is something you’d like to do, we recommend storing it alongside the encrypted vector.

Python
plaintext = PlaintextVector([1.2, -1.23, 3.24, 2.37], "contacts", "conversation-sentiment") metadata = AlloyMetadata.new_simple("tenant-123") encrypted = await sdk.vector().encrypt(plaintext, metadata) # Store off encrypted_vector and paired_icl_info
Java
PlaintextVector plaintext = new PlaintextVector(List.of(1.2f, -1.23f, 3.24f, 2.37f), "contacts", "conversation-sentiment"); AlloyMetadata metadata = AlloyMetadata.Companion.newSimple("tenant-123"); EncryptedVector encrypted = sdk.vector().encrypt(plaintext, metadata, null); // Store off encryptedVector and pairedIclInfo

When querying the data store, you must similarly encrypt the query in order to match against encrypted vectors.

Because vectors could have been encrypted with different keys, all resulting encrypted vectors must be used in conjunction when querying.

Python
vector_to_search = PlaintextVector([2.9, 3.1, -2.93, 1.0], "contacts", "conversation-sentiment") metadata = AlloyMetadata.new_simple("tenant-123") vector_id = "vec_1" vectors_to_query = {vector_id: vector_to_search} query_vectors = await sdk.vector().generate_query_vectors(vectors_to_query, metadata) final_query = query_vectors[vector_id] # Use all resulting vectors from final_query in conjunction when querying
Java
PlaintextVector vectorToSearch = new PlaintextVector(List.of(1.2f, -1.23f, 3.24f, 2.37f), "contacts", "conversation-sentiment"); AlloyMetadata metadata = AlloyMetadata.Companion.newSimple("tenant-123"); String vectorId = "vec_1"; Map<String, PlaintextVector> vectorsToQuery = Collections.singletonMap(vectorId, vectorToSearch); Map<String, List<EncryptedVector>> queryVectors = sdk.vector().generateQueryVectors(vectorsToQuery, metadata); List<EncryptedVector> finalQuery = queryVectors.get(vectorId); // Use all resulting vectors from finalQuery in conjunction when querying