An open API service indexing awesome lists of open source software.

https://github.com/harrytmthy/safebox

A blazing-fast drop-in replacement for EncryptedSharedPreferences on Android
https://github.com/harrytmthy/safebox

aes-gcm android android-keystore android-library android-sdk android-security chacha20-poly1305 cryptography encryptedsharedpreferences encryption kotlin mmap safebox secure-storage sharedpreferences

Last synced: about 1 month ago
JSON representation

A blazing-fast drop-in replacement for EncryptedSharedPreferences on Android

Awesome Lists containing this project

README

          

# SafeBox

[![Build](https://img.shields.io/github/actions/workflow/status/harrytmthy/safebox/ci.yml?branch=main&label=build&logo=githubactions&logoColor=white&style=flat-square)](https://github.com/harrytmthy/safebox/actions)
[![License](https://img.shields.io/github/license/harrytmthy/safebox?label=license&color=blue&style=flat-square)](https://github.com/harrytmthy/safebox/blob/main/LICENSE)
[![Release](https://img.shields.io/github/v/release/harrytmthy/safebox?include_prereleases&label=release&color=orange&style=flat-square)](https://github.com/harrytmthy/safebox/releases)

A secure, blazing-fast alternative to `EncryptedSharedPreferences`, designed for Android projects which demand both **speed** and **security**.

## 🚨 EncryptedSharedPreferences is Deprecated
As of **Jetpack Security 1.1.0-alpha07 (April 9, 2025)**, `EncryptedSharedPreferences` has been deprecated with no official replacement. Without continued support from Google, it may fall behind in cryptography standards, leaving sensitive data exposed.

SafeBox can help you [migrate](docs/MIGRATION.md) easily using the same `SharedPreferences` API. Since v1.2.0, SafeBox delivers **~184× faster init**, **~50× faster reads**, and **~9× faster writes** than EncryptedSharedPreferences.

## Why SafeBox?

| | SafeBox | EncryptedSharedPreferences |
|----------------|------------------------------------------------------|----------------------------------------------|
| Encryption | **Modern ChaCha20-Poly1305 with secure key vault** | Older AES setup tied to deprecated MasterKey |
| Storage format | **Memory-mapped binary file with minimal headers** | XML text file with tag/attribute overhead |
| I/O model | **New data is tail-appended** | New data rewrites the whole XML |
| Concurrency | **Stays smooth on concurrent writes** | Gets slower on concurrent writes |
| Scalability | **Stable performance on large size** | Keeps getting heavier as data grows |
| Durability | **Low-storage failures fallback to a recovery file** | Low-storage failures = data loss |
| Customization | **Cipher providers are replaceable** | Ciphers are not customizable |

SafeBox uses **deterministic encryption** for reference keys (for fast lookup) and **non-deterministic encryption** for values (for strong security). Both powered by a single ChaCha20 key protected via AES-GCM and stored securely.

🔑 SafeBox Key Derivation & Encryption Flow

```
[Android Keystore-backed AES-GCM Key]

[ChaCha20-Poly1305 Key]
↙ ↘
Reference Keys Entry Values
(deterministic IV) (randomized IV)
```

Compared to EncryptedSharedPreferences:

```
[Android Keystore MasterKey (deprecated)]
↙ ↘
[AES-SIV Key] [AES-GCM Key]
↓ ↓
Reference Keys Entry Values

```

## Installation

```kotlin
dependencies {
implementation("io.github.harrytmthy:safebox:1.3.0")

// Optional: standalone crypto helper
implementation("io.github.harrytmthy:safebox-crypto:1.3.0")
}
```

## Basic Usage

Create the instance:

```kotlin
val prefs: SharedPreferences = SafeBox.create(context, PREF_FILE_NAME)
```

Then use it like any `SharedPreferences`:

```kotlin
prefs.edit()
.putInt("userId", 123)
.putString("name", "Luna Moonlight")
.apply()

val userId = prefs.getInt("userId", -1)
val email = prefs.getString("email", null)
```

Once created, you can retrieve the same instance without a `Context`:

```kotlin
SafeBox.get(PREF_FILE_NAME) // or SafeBox.create(context, PREF_FILE_NAME)
.edit()
.clear()
.commit()
```

> Prefer `SafeBox.getOrNull(fileName)` if you need a safe retrieval without throwing.

### Understanding SafeBox Behavior

SafeBox returns the same instance per filename:

```kotlin
val a1 = SafeBox.create(context, "fileA")
val a2 = SafeBox.create(context, "fileA")
val a3 = SafeBox.get("fileA")

assertTrue(a1 === a2) // same reference
assertTrue(a1 === a3) // same reference

val b = SafeBox.create(context, "fileB")
assertTrue(a1 !== b) // different filenames = different instances
```

> Repeating `SafeBox.create(context, fileName)` returns the existing instance for that `fileName`. When an instance already exists, **all parameters are ignored** except a non-null `stateListener`, which replaces the current listener.

> Need lifecycle hooks for diagnostics or analytics? ➡️ [Read the Observability Guide](docs/OBSERVABILITY.md)

## Migrating from EncryptedSharedPreferences

SafeBox is a drop-in replacement for `EncryptedSharedPreferences`.

➡️ [Read the Migration Guide](docs/MIGRATION.md)

## Text Encryption Support

SafeBox includes a simple text encryption helper (`SafeBoxCrypto`) you can use for things like
encrypting values before writing to Room or sending over the network:

```kotlin
// 1. Create a secret and store it in your own vault
val secret: String = SafeBoxCrypto.createSecret()

// 2. Use it to encrypt
val userEntity = UserEntity(
id = SafeBoxCrypto.encrypt(userId, secret),
// ...
)

// 3. Retrieve it later
val userId: String = SafeBoxCrypto.decrypt(userEntity.id, secret)
```

If you only need the helper, use the standalone `:safebox-crypto` module.

## Performance Benchmarks

Average times measured over **100 samples** on an emulator:

📊 v1.3.0 Benchmark

![Get Performance](docs/charts/v1_3_get_performance_chart.png)

![Put Performance](docs/charts/v1_3_put_performance_chart.png)

![Put then Commit Performance](docs/charts/v1_3_put_and_commit_performance_chart.png)

| Operation | SafeBox v1.3.0 | EncryptedSharedPreferences |
|-----------------------------|------------------------------|----------------------------|
| Initialization | **0.19ms** (*201.1× faster*) | 38.7ms |
| Get 1 entry | **0.01ms** (*76.9× faster*) | 0.50ms |
| Get 3 entries | **0.02ms** (*68.5× faster*) | 1.27ms |
| Get 5 entries | **0.03ms** (*76.8× faster*) | 2.25ms |
| Get 10 entries | **0.06ms** (*66.4× faster*) | 4.07ms |
| Put 1 entry, then commit | **0.17ms** (*7.8× faster*) | 1.31ms |
| Put 3 entries, then commit | **0.46ms** (*4.7× faster*) | 2.16ms |
| Put 5 entries, then commit | **0.73ms** (*4.5× faster*) | 3.32ms |
| Put 10 entries, then commit | **1.46ms** (*4.3× faster*) | 6.28ms |

Even on **multiple single commits**, SafeBox remains faster:

| Operation | SafeBox v1.3.0 | EncryptedSharedPreferences |
|---------------------------|-----------------------------|----------------------------|
| Commit 3 single entries | **0.52ms** (*9.5× faster*) | 4.90ms |
| Commit 5 single entries | **0.85ms** (*8.2× faster*) | 6.91ms |
| Commit 10 single entries | **1.71ms** (*6.6× faster*) | 11.27ms |
| Commit 100 single entries | **16.51ms** (*4.3× faster*) | 71.34ms |

📊 v1.2.0 Benchmark

![Get Performance](docs/charts/v1_2_get_performance_chart.png)

![Put Performance](docs/charts/v1_2_put_performance_chart.png)

![Put then Commit Performance](docs/charts/v1_2_put_and_commit_performance_chart.png)

| Operation | SafeBox v1.2.0 | EncryptedSharedPreferences |
|-----------------------------|------------------------------|----------------------------|
| Initialization | **0.21ms** (*184.3× faster*) | 38.7ms |
| Get 1 entry | **0.01ms** (*50.0× faster*) | 0.50ms |
| Get 3 entries | **0.04ms** (*31.8× faster*) | 1.27ms |
| Get 5 entries | **0.07ms** (*32.1× faster*) | 2.25ms |
| Get 10 entries | **0.15ms** (*27.1× faster*) | 4.07ms |
| Put 1 entry, then commit | **0.22ms** (*5.95× faster*) | 1.31ms |
| Put 3 entries, then commit | **0.52ms** (*4.15× faster*) | 2.16ms |
| Put 5 entries, then commit | **0.98ms** (*3.39× faster*) | 3.32ms |
| Put 10 entries, then commit | **1.64ms** (*3.83× faster*) | 6.28ms |

Even on **multiple single commits**, SafeBox remains faster:

| Operation | SafeBox v1.2.0 | EncryptedSharedPreferences |
|---------------------------|------------------------------|----------------------------|
| Commit 3 single entries | **0.53ms** (*9.25× faster*) | 4.90ms |
| Commit 5 single entries | **0.95ms** (*7.27× faster*) | 6.91ms |
| Commit 10 single entries | **1.96ms** (*5.75× faster*) | 11.27ms |
| Commit 100 single entries | **17.41ms** (*4.10× faster*) | 71.34ms |

📊 v1.1.0 Benchmark

![Get Performance](docs/charts/v1_1_get_performance_chart.png)

![Put Performance](docs/charts/v1_1_put_performance_chart.png)

![Put then Commit Performance](docs/charts/v1_1_put_and_commit_performance_chart.png)

| Operation | SafeBox v1.1.0 | EncryptedSharedPreferences |
|-----------------------------|----------------|----------------------------|
| Initialization | **0.38ms** | 38.7ms (*10,079% slower*) |
| Get 1 entry | **0.33ms** | 0.50ms (*52% slower*) |
| Get 3 entries | **0.94ms** | 1.27ms (*35% slower*) |
| Get 5 entries | **1.56ms** | 2.25ms (*44% slower*) |
| Get 10 entries | **3.06ms** | 4.07ms (*33% slower*) |
| Put 1 entry, then commit | **0.49ms** | 1.31ms (*167% slower*) |
| Put 3 entries, then commit | **1.34ms** | 2.16ms (*61% slower*) |
| Put 5 entries, then commit | **2.36ms** | 3.32ms (*41% slower*) |
| Put 10 entries, then commit | **4.20ms** | 6.28ms (*50% slower*) |

Even on **multiple single commits**, SafeBox remains faster:

| Operation | SafeBox v1.1.0 | EncryptedSharedPreferences |
|------------------------------|----------------|----------------------------|
| Commit 3 single entries | **1.50ms** | 4.90ms (*227% slower*) |
| Commit 5 single entries | **2.39ms** | 6.91ms (*189% slower*) |
| Commit 10 single entries | **5.07ms** | 11.27ms (*122% slower*) |
| Commit 100 single entries | **38.12ms** | 71.34ms (*87% slower*) |

📊 v1.0.0 Benchmark

![Get Performance](docs/charts/read_performance_chart.png)

![Put Performance](docs/charts/write_performance_chart.png)

![Put then Commit Performance](docs/charts/write_commit_performance_chart.png)

| Operation | SafeBox v1.0.0 | EncryptedSharedPreferences |
|-----------------------------|----------------|----------------------------|
| Get 1 entry | **0.39ms** | 0.50ms (*28% slower*) |
| Get 3 entries | **0.94ms** | 1.27ms (*35% slower*) |
| Get 5 entries | **1.37ms** | 2.25ms (*64% slower*) |
| Get 10 entries | **3.29ms** | 4.07ms (*24% slower*) |
| Put 1 entry, then commit | **0.55ms** | 1.31ms (*138% slower*) |
| Put 3 entries, then commit | **1.25ms** | 2.16ms (*73% slower*) |
| Put 5 entries, then commit | **2.33ms** | 3.32ms (*42% slower*) |
| Put 10 entries, then commit | **4.73ms** | 6.28ms (*33% slower*) |

Even on **multiple single commits**, SafeBox remains faster:

| Operation | SafeBox v1.0.0 | EncryptedSharedPreferences |
|------------------------------|----------------|----------------------------|
| Commit 3 single entries | **1.94ms** | 4.90ms (*152% slower*) |
| Commit 5 single entries | **2.84ms** | 6.91ms (*143% slower*) |
| Commit 10 single entries | **5.47ms** | 11.27ms (*106% slower*) |
| Commit 100 single entries | **33.19ms** | 71.34ms (*115% slower*) |

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, formatting, testing, and PR guidelines.

## 💖 Support SafeBox

If SafeBox helped secure your app or saved your time, consider sponsoring to support future improvements and maintenance!

[![Sponsor](https://img.shields.io/badge/sponsor-%F0%9F%92%96-blueviolet?style=flat-square)](https://github.com/sponsors/harrytmthy)

## License

```
MIT License
Copyright (c) 2025 Harry Timothy Tumalewa
```