Getting started with DataSafe: Encrypt, share, and version files securely

9 min read• By Assah Bismark
Blog
Learn how to set up DataSafe with Docker to transparently encrypt file contents and paths, share files securely using public keys, and manage document versions.

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

DataSafe provides a complete stack for encrypted data management. The architecture consists of four layers that work together to provide end-to-end encryption.
Datasafe provides a complete stack for encrypted data management

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:

docker-compose.yml
1services:
2 datasafe:
3 image: adorsys/datasafe:latest
4 container_name: datasafe-demo
5 ports:
6 - "8080:8080"
7 environment:
8 - EXPOSE_API_CREDS=true
9 - DEFAULT_USER=admin
10 - DEFAULT_PASSWORD=admin
11 - USE_FILESYSTEM=file:///usr/app/ROOT_BUCKET
12JWT_SECRET=ThisIsAVeryLongSecretKeyForJWTTokenGenerationThatMustBeAtLeast64CharactersLong123456
13 volumes:
14 - datasafe-data:/usr/app/ROOT_BUCKET
15volumes:
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:

docker-compose.yml
1services:
2 minio:
3 image: minio/minio
4 container_name: minio
5 ports:
6 - "9000:9000"
7 - "9001:9001"
8 environment:
9 MINIO_ROOT_USER: accessKey
10 MINIO_ROOT_PASSWORD: secretKey
11 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:

multipart/form-data
1# Create a test file
2echo "This is highly confidential information." > secret.txt
3# Upload the encrypted file
4curl -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 share
2echo "Hello Bob, this is a shared file from Alice!" > report.txt
3# Share it with Bob
4curl -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 inbox
2curl -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.txt
2curl -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 1
2echo "Contract v1: Initial terms" > contract.txt
3curl -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'
8
9# Version 2
10echo "Contract v2: Updated payment terms" > contract.txt
11curl -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

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

Secure File Sharing 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

/api/authenticate

POST

Get JWT token

Body: {"userName":"...","password":"..."}

/user

PUT

Create user

Body: {"userName":"...","password":"..."}

/user

DELETE

Delete user

user, password

/documents/{path}

GET

List private files

user, password

/documents/{path}

GET

Read private files

user, password

/documents/{path}

PUT

Write private files

user, password

/documents/{path}

DELETE

Delete private files

user, password, From: file

/inbox/documents/{path}

GET

List inbox

user, password

/inbox/documents/{path}

GET

Read from inbox

user, password

/inbox/documents/{path}

PUT

Share file

user, password, users, From: file

/inbox/documents/{path}

DELETE

Delete from inbox

user, password

/versioned/{path}

GET

List/Read versioned

user, password

/versioned/{path}

PUT

Write versioned

user, password, From: file

/versioned/list/{path}

GET

List file versions

user, password

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 containers
2docker 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;
8
9import java.io.InputStream;
10import java.io.OutputStream;
11import java.nio.charset.StandardCharsets;
12import java.nio.file.Path;
13import java.nio.file.Paths;
14
15public class DatasafeExample {
16 public static void main(String[] args) throws Exception {
17 Path root = Paths.get("./encrypted-data");
18
19 // Initialize DataSafe with filesystem storage
20 DefaultDatasafeServices datasafe = DaggerDefaultDatasafeServices.builder()
21 .config(new DefaultDFSConfig(root.toAbsolutePath().toUri(), "secret"::toCharArray))
22 .storage(new FileSystemStorageService(root))
23 .build();
24 // Create a user
25 UserIDAuth alice = new UserIDAuth("alice", "aliceSecret"::toCharArray);
26 datasafe.userProfile().registerUsingDefaults(alice);
27
28 // Write encrypted file
29 try (OutputStream os = datasafe.privateService()
30 .write(WriteRequest.forDefaultPrivate(alice, "documents/secret.txt"))) {
31 os.write("Confidential data".getBytes(StandardCharsets.UTF_8));
32 }
33
34 // Read it back
35 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;
4
5// Create Bob
6UserIDAuth bob = new UserIDAuth("bob", "bobSecret"::toCharArray);
7datasafe.userProfile().registerUsingDefaults(bob);
8
9// Alice shares a file with Bob
10try (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}
17
18// Bob reads from his inbox
19try (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>
6
7<!-- 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

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.

Explore more
Securing Identity Management: Integrating Keycloak with Wazuh through Syslog

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.

Explore more
Keycloak × Stripe: Building Invisible Marketplace Onboarding with Event-Driven Architecture

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.

Explore more
© 2026 adorsys. Alle Rechte vorbehalten.
Certificate TopCompany Kununu
Certificate ISO 27001
Certificate ISO 9001