Getting started with DataSafe: Encrypt, share, and version files securely
Organizations handling sensitive data face a fundamental challenge: how do you store files securely while maintaining the ability to search, share, and manage them? Traditional encryption approaches force developers to build complex key management, implement secure sharing protocols, and handle encrypted path structures from scratch.
DataSafe is an open source encryption library that solves these problems. It provides transparent file encryption where both content and file paths are encrypted, secure sharing using public key cryptography, and document versioning for audit trails all through a simple API. The encryption happens at the application layer, meaning your storage backend never sees plaintext data.
In this article, you'll set up a complete DataSafe environment in 5 minutes using Docker, store and retrieve encrypted files, share files securely between users, and see exactly what encrypted data looks like in storage.
Architecture overview

At the top, the client layer provides a web browser with an Angular UI for demonstration purposes. This connects to the API layer, a Spring Boot REST API with JWT authentication that exposes DataSafe functionality over HTTP. The core of the system is the DataSafe library itself, which provides encryption services including user profile management, private storage, and inbox functionality for secure sharing. Finally, the storage layer persists encrypted blobs to S3, Minio, or a local filesystem.
The REST API and UI exist purely for demonstration. In production, you'd integrate the DataSafe library directly into your application. The encryption happens at the library level storage backends never see plaintext data.
Quick start
Prerequisites
Docker and Docker Compose installed
Option 1: Filesystem storage (simplest)
Create a docker-compose.yml file:
1services:2 datasafe:3 image: adorsys/datasafe:latest4 container_name: datasafe-demo5 ports:6 - "8080:8080"7 environment:8 - EXPOSE_API_CREDS=true9 - DEFAULT_USER=admin10 - DEFAULT_PASSWORD=admin11 - USE_FILESYSTEM=file:///usr/app/ROOT_BUCKET12JWT_SECRET=ThisIsAVeryLongSecretKeyForJWTTokenGenerationThatMustBeAtLeast64CharactersLong12345613 volumes:14 - datasafe-data:/usr/app/ROOT_BUCKET15volumes:16 datasafe-data:
Start the service:
docker compose up -d
Open http://localhost:8080/static/index.html in your browser.
Option 2: Minio (S3-compatible) storage
To visualize encrypted data in an S3-like interface, use this docker-compose.yml:
1services:2 minio:3 image: minio/minio4 container_name: minio5 ports:6 - "9000:9000"7 - "9001:9001"8 environment:9 MINIO_ROOT_USER: accessKey10 MINIO_ROOT_PASSWORD: secretKey11 command: server /data --console-address ":9001"12 volumes:13 - minio-data:/data
createbucket:
image: minio/mc
depends_on:
- minio
entrypoint: >
/bin/sh -c "
sleep 5;
/usr/bin/mc alias set myminio http://minio:9000 accessKey secretKey;
/usr/bin/mc mb myminio/datasafe --ignore-existing;
exit 0;
"
datasafe:
image: adorsys/datasafe:latest
container_name: datasafe-demo
depends_on:
- minio
- createbucket
ports:
- "8080:8080"
environment:
- EXPOSE_API_CREDS=true
- DEFAULT_USER=admin
- DEFAULT_PASSWORD=admin
- DATASAFE_AMAZON_URL=http://minio:9000
- AWS_ACCESS_KEY_ID=accessKey
- AWS_SECRET_ACCESS_KEY=secretKey
- AWS_BUCKET=datasafe
- AWS_REGION=us-east-1
- DATASAFE_SYSTEM_ROOT=s3://datasafe/
- DATASAFE_S3_STORAGE=true
JWT_SECRET=ThisIsAVeryLongSecretKeyForJWTTokenGenerationThatMustBeAtLeast64CharactersLong123456
volumes:
minio-data:
Start the service:
docker compose up -d
This starts three services: the DataSafe API at , the DataSafe UI at , and the Minio Console at (credentials: accessKey / secretKey).
Understanding API authentication
The DataSafe REST API has two layers of authentication. The first layer is a JWT token managed by Spring Security that protects the REST endpoints. The second layer consists of DataSafe user credentials (username and password) passed as headers, which are used for encryption operations. This separation ensures that even if someone obtains a valid JWT token, they cannot access encrypted data without the user's encryption credentials.
Step 1: Get a JWT token
1# Authenticate with the REST API (default: admin/admin)2curl -i -X POST 'http://localhost:8080/api/authenticate' \3 -H 'Content-Type: application/json' \4 -d '{"userName":"admin","password":"admin"}'
The token is returned in the token header:
1token: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9...
Save this token for subsequent requests:
1TOKEN="Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9..."
Using private storage
Private storage encrypts files so only the owner can access them. Both the file content and the file path are encrypted.
Step 1: Create a DataSafe user
Use the UI (click "Register") or via API:
1curl -X PUT 'http://localhost:8080/user' \2 -H "token: $TOKEN" \3 -H 'Content-Type: application/json' \4 -d '{"userName":"alice","password":"aliceSecret"}'
This creates a unique keystore for Alice containing encryption keys for both path and content encryption, along with a private storage space for her files and an inbox for receiving shared files from other users.
Step 2: Store an encrypted file
The write endpoint uses multipart/form-data:
1# Create a test file2echo "This is highly confidential information." > secret.txt3# Upload the encrypted file4curl -X PUT 'http://localhost:8080/document/documents/secret-report.txt' \5 -H "token: $TOKEN" \6 -H 'user: alice' \7 -H 'password: aliceSecret' \8 -F 'file=@secret.txt'
Step 3: List private files
1curl -X GET 'http://localhost:8080/documents/' \2 -H "token: $TOKEN" \3 -H 'user: alice' \4 -H 'password: aliceSecret'
Response:
1["documents/secret-report.txt"]
Step 4: Read the file back
1curl -X GET 'http://localhost:8080/document/documents/secret-report.txt' \2 -H "token: $TOKEN" \3 -H 'user: alice' \4 -H 'password: aliceSecret' \5 -H 'Accept: application/octet-stream'
Response:
1This is highly confidential information.
What happened behind the scenes?
When Alice stored her file, DataSafe first encrypted the path documents/secret-report.txt into something like aX9kL.../mN2pQ.../Zr7tY..., then encrypted the content using AES-256-GCM with a unique key, and finally stored both the encrypted path and content in the configured storage backend (Minio S3 or filesystem).
See it in action
What Alice sees in the DataSafe UI:
The user sees familiar, readable file names just like any file manager.
What's actually stored in Minio (S3):
The storage backend only sees encrypted blobs with names like PBJs3F98q8f63jip4WHB8IdUiHE4KmiMiLA=. Even the path users/alice/private/files/SIV reveals nothing about the actual file structure.
This is the power of DataSafe: even if someone gains access to your storage, they see only encrypted gibberish. The encryption happens in the application layer storage never sees plaintext.
Using secure sharing (Inbox)
The Inbox allows users to share files securely. Files are encrypted with the recipient's public key—only they can decrypt.
Step 1: Create another user (Bob)
Use the UI to register Bob, or via API:
1curl -X PUT 'http://localhost:8080/user' \2 -H "token: $TOKEN" \3 -H 'Content-Type: application/json' \4 -d '{"userName":"bob","password":"bobSecret"}'
Step 2: Alice shares a file with Bob
1# Create a file to share2echo "Hello Bob, this is a shared file from Alice!" > report.txt3# Share it with Bob4curl -X PUT 'http://localhost:8080/inbox/document/shared-report.txt' \5 -H "token: $TOKEN" \6 -H 'user: alice' \7 -H 'password: aliceSecret' \8 -H 'users: bob' \9 -F 'file=@report.txt'
When Alice shares the file, DataSafe fetches Bob's public key from the registry, encrypts the file using CMS envelope encryption (RFC 5652), and stores the encrypted blob in Bob's inbox.
Step 3: Bob reads his inbox
1# List inbox2curl -X GET 'http://localhost:8080/inbox/documents/' \3 -H "token: $TOKEN" \4 -H 'user: bob' \5 -H 'password: bobSecret'
Response:
1["shared-report.txt"]
1# Read the shared file (decrypted by DataSafe)2curl -X GET 'http://localhost:8080/inbox/document/shared-report.txt' \3 -H "token: $TOKEN" \4 -H 'user: bob' \5 -H 'password: bobSecret' \6 -H 'Accept: application/octet-stream'
Response:
1Hello Bob, this is a shared file from Alice!
What's stored in Minio?
In Minio, the file is stored at users/bob/public/inbox/shared-report.txt but the content is encrypted (468 bytes for a small text message).
Important: If you download directly from Minio, you get the encrypted CMS envelope not readable text. Only Bob can decrypt it by calling the DataSafe API with his credentials.
Multi-recipient sharing
DataSafe supports sharing with multiple recipients in a single operation
1echo "Team update: Project milestone achieved!" > team-update.txt2curl -X PUT 'http://localhost:8080/inbox/document/team-update.txt' \3 -H "token: $TOKEN" \4 -H 'user: alice' \5 -H 'password: aliceSecret' \6 -H 'users: bob,charlie,david' \7 -F 'file=@team-update.txt'
Using versioned documents
DataSafe supports document versioning useful for audit trails and ransomware protection.
Store multiple versions
1# Version 12echo "Contract v1: Initial terms" > contract.txt3curl -X PUT 'http://localhost:8080/versioned/contract.txt' \4 -H "token: $TOKEN" \5 -H 'user: alice' \6 -H 'password: aliceSecret' \7 -F 'file=@contract.txt'89# Version 210echo "Contract v2: Updated payment terms" > contract.txt11curl -X PUT 'http://localhost:8080/versioned/contract.txt' \12 -H "token: $TOKEN" \13 -H 'user: alice' \14 -H 'password: aliceSecret' \15 -F 'file=@contract.txt'
List Versioned documents
1curl -X GET 'http://localhost:8080/versioned/' \2 -H "token: $TOKEN" \3 -H 'user: alice' \4 -H 'password: aliceSecret'
Response (shows latest version with UUID):
1["contract.txt/ed527d70-2179-402d-b391-5f30e5270183"]
List all versions of a file
1curl -X GET 'http://localhost:8080/versions/list/contract.txt' \2 -H "token: $TOKEN" \3 -H 'user: alice' \4 -H 'password: aliceSecret'
Response (all versions with their UUIDs):
1["contract.txt/ed527d70-2179-402d-b391-5f30e5270183","contract.txt/58956c6d-cd30-42ce-b045-eaedd6350fd9"]
Read latest version
1curl -X GET 'http://localhost:8080/versioned/contract.txt' \2 -H "token: $TOKEN" \3 -H 'user: alice' \4 -H 'password: aliceSecret' \5 -H 'Accept: application/octet-stream'
Response:
1Contract v2: Updated payment terms
Each version is stored separately with encrypted timestamps, allowing you to track document history, recover from ransomware attacks (since old versions remain intact), and meet compliance requirements for audit trails.
How it works
Private storage flow

During a write operation, the user requests to write a file (for example, "documents/secret.txt"). DataSafe retrieves the path encryption key from the user's keystore and encrypts the file path segment-by-segment using AES-SIV. It then retrieves the document encryption key from the keystore, encrypts the content using AES-256-GCM (authenticated encryption), and stores the encrypted blob at the encrypted path.
During a read operation, the user requests a file by its logical path. DataSafe encrypts the path to locate the actual file in storage, fetches the encrypted blob, decrypts the content using the document key, and returns the plaintext to the user.
Secure sharing (Inbox) flow

When Alice shares a file with Bob, DataSafe fetches Bob's public key from the registry, encrypts the file using a CMS envelope (RFC 5652) with Bob's public key, and stores the encrypted file in Bob's inbox.
When Bob reads from his inbox, the encrypted CMS envelope is fetched, Bob's private key decrypts the envelope, and the plaintext content is returned to Bob. Only Bob can decrypt files in his inbox because only he has the matching private key.
Encryption reference
DataSafe uses industry-standard encryption algorithms:
Layer | Algorithm | Key Size | Purpose |
|---|---|---|---|
Keystore | BCFKS | - | Secure storage of user keys |
Key derivation | PBKDF2 (SHA-512) | 32 bytes salt, 20480 iterations | Derive keys from passwords |
Keystore auth | HmacSHA3-512 | - | Protect Keystore from tampering |
Path encryption | AES-SIV | 256-bit | Encrypt file paths (deterministic) |
Private files | AES-256-GCM | 256-bit | Encrypt file content |
Large files | ChaCha20-Poly1305 | 256-bit | Files >350GB |
Shared files | CMS + ECDH | secp256r1 | Public key encryption |
Signing | SHA256withECDSA | secp256r1 | Digital signatures |
Why these choices?
AES-GCM provides authenticated encryption, which detects tampering rather than just preventing unauthorized reading. AES-SIV is used for path encryption because it's deterministic the same path always encrypts to the same value, enabling directory listing without decrypting everything. ECDH is preferred over RSA because it offers smaller keys (256-bit vs 2048-bit) with equivalent security and better performance. CMS envelopes are an industry standard (RFC 5652) for encrypted messages and support multiple recipients in a single encrypted package.
REST API reference
All endpoints (except /api/authenticate) require the token header with the JWT token.
Endpoint | Method | Purpose | Additional Headers |
|---|---|---|---|
| POST | Get JWT token | Body: |
| PUT | Create user | Body: |
| DELETE | Delete user |
|
| GET | List private files |
|
| GET | Read private files |
|
| PUT | Write private files |
|
| DELETE | Delete private files |
|
| GET | List inbox |
|
| GET | Read from inbox |
|
| PUT | Share file |
|
| DELETE | Delete from inbox |
|
| GET | List/Read versioned |
|
| PUT | Write versioned |
|
| GET | List file versions |
|
Cleanup
Delete a user and all their data
1curl -X DELETE 'http://localhost:8080/user' \2 -H "token: $TOKEN" \3 -H 'user: alice' \4 -H 'password: aliceSecret'
This removes Alice's keystore, all private files, all inbox files, and her user profile.
GDPR Note: Deleting the user destroys the encryption keys. Even if encrypted data remains in backups, it's cryptographically inaccessible true data erasure.
Stop the demo
1# Stop and remove containers2docker compose down
# To also remove volumes (deletes all data)
docker compose down -v
Using the Java API directly
The REST API wraps the DataSafe library for demonstration. In production, you'd use the library directly:
1import de.adorsys.datasafe.business.impl.service.DaggerDefaultDatasafeServices;2import de.adorsys.datasafe.business.impl.service.DefaultDatasafeServices;3import de.adorsys.datasafe.directory.impl.profile.config.DefaultDFSConfig;4import de.adorsys.datasafe.encrypiton.api.types.UserIDAuth;5import de.adorsys.datasafe.storage.impl.fs.FileSystemStorageService;6import de.adorsys.datasafe.types.api.actions.ReadRequest;7import de.adorsys.datasafe.types.api.actions.WriteRequest;89import java.io.InputStream;10import java.io.OutputStream;11import java.nio.charset.StandardCharsets;12import java.nio.file.Path;13import java.nio.file.Paths;1415public class DatasafeExample {16 public static void main(String[] args) throws Exception {17 Path root = Paths.get("./encrypted-data");1819 // Initialize DataSafe with filesystem storage20 DefaultDatasafeServices datasafe = DaggerDefaultDatasafeServices.builder()21 .config(new DefaultDFSConfig(root.toAbsolutePath().toUri(), "secret"::toCharArray))22 .storage(new FileSystemStorageService(root))23 .build();24 // Create a user25 UserIDAuth alice = new UserIDAuth("alice", "aliceSecret"::toCharArray);26 datasafe.userProfile().registerUsingDefaults(alice);2728 // Write encrypted file29 try (OutputStream os = datasafe.privateService()30 .write(WriteRequest.forDefaultPrivate(alice, "documents/secret.txt"))) {31 os.write("Confidential data".getBytes(StandardCharsets.UTF_8));32 }3334 // Read it back35 try (InputStream is = datasafe.privateService()36 .read(ReadRequest.forDefaultPrivate(alice, "documents/secret.txt"))) {37 String content = new String(is.readAllBytes());38 System.out.println(content); // "Confidential data"39 }40 }41}
Sharing files with another user
1import de.adorsys.datasafe.types.api.actions.WriteInboxRequest;2import de.adorsys.datasafe.encrypiton.api.types.UserID;3import java.util.Collections;45// Create Bob6UserIDAuth bob = new UserIDAuth("bob", "bobSecret"::toCharArray);7datasafe.userProfile().registerUsingDefaults(bob);89// Alice shares a file with Bob10try (OutputStream os = datasafe.inboxService()11 .write(WriteInboxRequest.forDefaultPublic(12 alice,13 Collections.singleton(new UserID("bob")),14 "shared-report.txt"))) {15 os.write("Hello Bob!".getBytes(StandardCharsets.UTF_8));16}1718// Bob reads from his inbox19try (InputStream is = datasafe.inboxService()20 .read(ReadRequest.forDefaultPrivate(bob, "shared-report.txt"))) {21 String content = new String(is.readAllBytes());22 System.out.println(content); // "Hello Bob!"23}
Maven dependency
1<dependency>2 <groupId>de.adorsys</groupId>3 <artifactId>datasafe-business</artifactId>4 <version>${datasafe.version}</version>5</dependency>67<!-- For filesystem storage -->8<dependency>9 <groupId>de.adorsys</groupId>10 <artifactId>datasafe-storage-impl-fs</artifactId>11 <version>${datasafe.version}</version>12</dependency>
Things to know
DataSafe is open source and available under AGPL v3 license with commercial licensing options.
Encryption in DataSafe is transparent—you store and retrieve files normally while DataSafe handles encryption automatically. This includes path encryption, meaning not just content but file names and paths are encrypted. Each user has independent encryption keys, so one compromise doesn't affect others. Secure sharing is enabled through CMS envelopes, which allow encrypted sharing without sharing keys. And when you delete a user, their keys are destroyed, making encrypted data cryptographically inaccessible even if it persists in backups—true data erasure.
To learn more and get started, visit the . The repository includes full cryptographic specifications in SECURITY.WHITEPAPER.md, examples for different storage backends (S3, Minio, filesystem), and integration patterns in datasafe-examples/.
For background on the challenges DataSafe addresses, see our previous articles: Understanding the Problem of Data at Rest explores why traditional storage encryption falls short, and Advanced Data Security: Challenges of Encrypted Database Indexing dives into the complexities of searching and indexing encrypted data.
Next posts

Mastering Keycloak Configuration with GitOps and keycloak-config-cli
Eliminate Keycloak Click-Ops. Learn how the Keycloak Tenant Accelerator (KTA) uses GitOps and keycloak-config-cli to build a scalable, automated multi-tenant CaC workflow.

Securing Identity Management: Integrating Keycloak with Wazuh through Syslog
Integrating Keycloak with Wazuh via Syslog provides free, real-time security monitoring for identity management. It maps authentication events to the MITRE ATT&CK framework to quickly detect threats.

Keycloak × Stripe: Building Invisible Marketplace Onboarding with Event-Driven Architecture
Discover how to build a seamless marketplace onboarding experience by integrating Keycloak and Stripe Connect using event-driven reactive Kotlin and AMQP.
Keywords