Encrypted onchain storage
Encrypted onchain storage is an abstraction for data encryption using vetKeys. It builds on top of the decentralized key management service.
Below is an example implementation of encrypted onchain storage using the EncryptedMaps
library, which offers vetKey derivation and access control over vetKeys and encrypted data.
Why use EncryptedMaps
?
EncryptedMaps
is a tool that provides a collection of access-controlled maps for storing user-encrypted data, where encryption is performed using vetKeys.
It is a ready-to-use solution. It offers:
Simple, cleartext frontend APIs for key derivation and data encryption. No need to define or manage your own cryptographic primitives.
Efficiency in design. Reduce the number of
vetKey
derivation calls, saving on compute, time, and API fees.Deterministic key generation. Built on the strong randomness of vetKeys.
Built-in access control for keys and encrypted data. No peer-to-peer interaction required. Parties receiving access permissions don’t need to be online during the process.
Enhanced security when fetching keys on demand. Avoids persisting sensitive keys locally, reducing the risk of key compromise.
How do EncryptedMaps
work?
EncryptedMaps
(similar to KeyManager
) identify their maps based on two pieces of information:
- Map owner's principal.
- An arbitrary byte string, e.g., a human-readable string encoded as bytes.
Each encrypted map is associated with a single vetKey and one access control scope. Regardless of how many key-value pairs it contains, only one vetKey
is needed per map. All key-value pairs inherit the same access permissions as the map itself.
An encrypted map functions similarly to a standard map in most programming languages, with keys and values represented as byte strings. However, the values are encrypted. The key difference is that encrypted maps are not meant to be used directly by the canister. Any data encrypted or decrypted by the canister is considered public. Instead, users handle encryption and decryption to maintain privacy.
A key advantage of EncryptedMaps
is that encryption is entirely opaque to developers using the frontend APIs. Developers specify where the data should go, namely, the map name and key, but not how it's encrypted or decrypted. The encryption is designed to be secure, efficient, and to minimize the number of vetKeys
that need to be fetched via the mainnet management canister.
Beyond the inherited features of KeyManager
, EncryptedMaps
has also the following features:
Encrypted key-value storage: Store and retrieve key-encrypted-value pairs within named maps.
Cleartext APIs: The frontend library for
EncryptedMaps
offers developers cleartext APIs.Store data anywhere:
EncryptedMaps
provides a possibility to implement encrypted data store outside canisters, e.g., locally, while using vetKeys.Shared maps access information: Query which maps a user has access to.
Manage user access: Assign, modify, and revoke user rights on stored maps.
How to use EncryptedMaps
?
Step 1: Instantiate the EncryptedMaps
component in a backend canister
- Rust
Below is an example of how to instantiate the EncryptedMaps
component inside a backend canister.
loading...
Step 2: Define EncryptedMaps
canister APIs for the frontend to communicate with
- Rust
Below is an example of how to implement a backend canister API that allows the frontend to interact with the set_user_rights
method of the EncryptedMaps
component.
You can follow a similar approach to expose other methods of EncryptedMaps
through the canister interface.
loading...
Step 3: Implement EncryptedMapsClient
in the frontend
- Typescript
Below is an example of how to implement a backend canister API that allows the frontend to interact with the set_user_rights
method of the EncryptedMaps
component.
You can follow a similar approach to expose other methods of EncryptedMaps
through the canister interface.
loading...
Step 4: Use EncryptedMaps
in the frontend
Your application's frontend can communicate with a backend canister that exposes EncryptedMaps
API methods using the following TypeScript code:
- Typescript
import { EncryptedMaps } from "ic_vetkeys/encrypted_maps";
// Initialize the EncryptedMaps Client
const encryptedMaps = new EncryptedMaps(encryptedMapsClientInstance);
// Retrieve shared maps
const sharedMaps = await encryptedMaps.getAccessibleSharedMapNames();
const mapOwner = Principal.fromText("aaaaa-aa");
const mapName = "passwords";
const mapKey = "email_account";
// Store an encrypted value
const value = new TextEncoder().encode("my_secure_password");
const result = await encryptedMaps.setValue(mapOwner, mapName, mapKey, value);
// Retrieve a stored value
const storedValue = await encryptedMaps.getValue(mapOwner, mapName, mapKey);
// Manage user access rights
const user = Principal.fromText("bbbbbb-bb");
const accessRights = { ReadWrite: null };
const result = await encryptedMaps.setUserRights(mapOwner, mapName, user, accessRights);
Examples
Password manager: Use the default
EncryptedMaps
canister with a frontend that implements a simple password manager, where the passwords are encrypted using vetKeys.Password manager with metadata: Store additional metadata in the canister (both user-provided and canister-generated) and include this metadata alongside the encrypted data in atomic password addition or removal operations.
Extensibility
A developer using EncryptedMaps
can add new functionalities to or around EncryptedMaps
in a few ways:
Code extensions that don't require functional modifications of
EncryptedMaps
can be implemented very simply. For example, if the developer wants to add a counter that would count how many times a particular user called a particular API.The existing methods of
EncryptedMaps
can be used in different ways. For example, one may want to add a user to multiple keys in one go. To achieve that, the frontend and canister API would need to define the batch addition of access permissions, but there would need to be no changes to the backendEncryptedMaps
component.The internal fields of the
EncryptedMaps
backend components are public, and thus, it is possible to add new functions toEncryptedMaps
. For example,EncryptedMaps
provide a method to remove user's access permissions for a key. The developer can easily implement a function that removes user access from all keys in one method.