Encrypted local storage for mobile apps. Envelope encryption with a hardware-gated key on one side and a recovery path on the other, so the everyday flow is a single biometric prompt and a lost device is not a lost vault.
+-------------------+ +-------------------+ +-------------------+
| Enclave secret | | Provider material | | Recovery factor |
| envelock-owned | | host-supplied | | user-held |
| OS biometric gate | | via callback | | recovery only |
+---------+---------+ +---------+---------+ +---------+---------+
| | |
+----------+-----------+ |
v v
HKDF -> KEK_primary [Argon2id +] HKDF -> KEK_recovery
| |
Wrapped DEK (A) Wrapped DEK (B)
| |
+-----------------+----------------+
v
DEK -> encrypted records
Two independent wrappings of the same DEK. Either opens the vault, which is what keeps the everyday flow to one prompt while staying recoverable.
The cryptographic core is a single Rust implementation shared by every platform, so a vault written on iOS opens on Android byte-for-byte. This repository holds the platform wrappers, the generated bindings and the runnable examples; the core ships as a prebuilt binary.
Current release: 0.2.0
.package(url: "https://github.com/ShareRing/envelock.git", from: "0.2.0")EnvelockCore is distributed as a GitHub Release asset rather than through CocoaPods trunk, so
the podspec is referenced by URL:
pod 'EnvelockCore', :podspec => 'https://github.com/ShareRing/envelock/releases/download/v0.2.0/EnvelockCore.podspec'React Native and Flutter both depend on EnvelockCore, so that line belongs in the host app's
Podfile for those integrations too.
dependencies {
implementation("network.sharering:envelock-android:0.2.0")
}npm install @sharering/react-native-envelockThen add the EnvelockCore Podfile line above and run pod install.
Published to ShareRing's private OnePub registry:
dart pub global activate onepub
onepub auth
flutter pub add envelockenvelock makes no network calls and holds no tokens. It calls you when it needs provider material, and the contract that matters is determinism: for a given key id, the bytes must be identical every single call. Break that and the vault stops opening.
Safe sources are a server-held per-user pepper, or WebAuthn PRF output for a fixed salt. What bricks user data: bearer or refresh tokens, anything with a timestamp or request id in it, device fingerprints, and anything derived from a PIN the user can change.
examples/reference-backend/ is a working endpoint that gets this right. It returns a key id
alongside the material, so a pepper can be rotated without stranding existing vaults.
There is deliberately no token parameter and no endpoint option in the API. If fetching material needs a bearer token, use that token inside your own callback. Keeping it out of the signature makes "the token fetches the material, it is never the material" structural rather than a warning someone skips.
Start from examples/: one runnable demo per platform, each showing the library used directly and wrapped inside a host SDK.
envelock always contributes its own enclave factor to KEK_primary, generated by envelock and
never supplied or influenced by the caller. A provider callback can only ever add a factor,
never lower the floor.
The type system enforces this rather than the docs. The enclave key store is a required constructor argument with no default, and the software stand-in exists only in test builds. Every released binary is checked for the absence of those symbols before it is published.
So the worst integration anyone can write, a callback returning 32 constant bytes, still leaves you with a device-bound key behind an OS biometric gate. Never plaintext-equivalent.
| Path | What |
|---|---|
platform/ios/ |
Secure Enclave shim and the generated Swift bindings |
platform/android/ |
Keystore shim and the generated Kotlin bindings |
bindings/react-native/ |
Native module, TypeScript, JSI buffer bridge |
bindings/flutter/ |
Pigeon + Dart |
examples/ |
Runnable demos, one per platform |
Apache-2.0. See LICENSE.