πŸ›‘οΈ nemo_crypto

pub package License: MIT

The core cryptography engine for the Nemo Crypto ecosystem. It provides a production-ready key hierarchy, Argon2id key stretching, and authenticated encryption (XChaCha20-Poly1305) built on libsodium.

Built on Dart. Without Flutter dependency and enforced storage engine.

Core of the Nemo Crypto.

πŸ“¦ Ecosystem Adapters

While nemo_crypto provides the cryptographic primitives, it requires external storage interfaces to persist data. Check out the official adapters for Flutter apps:

  • πŸ”‘ nemo_crypto_keystore: Enable biometric/silent unlock using iOS Keychain, Android Keystore, and other OS-level secure storage.
  • 🐝 nemo_crypto_hive: Persist your wrapped keys using a Hive box.

πŸš€ Installation

dependencies:
  nemo_crypto: ^0.1.0

πŸ› οΈ Usage Guide

1. Initialize the Engine

Initialization must occur once before interacting with any cryptographic functions.

import 'package:nemo_crypto/nemo_crypto.dart';

await Nemo.initialize();

2. Configure the Keyring

The Keyring manages the key hierarchy and lifecycle state. It requires a WrapStore to persist encrypted keys (use HiveWrapStore in production).

final store = InMemoryWrapStore();
final keyring = Keyring(store);

await keyring.init(trySilentUnlock: false);

3. Create or Unlock a Store

if (keyring.status == NemoStatus.none) {
  // Calibrates Argon2id to the device hardware and creates the master key
  final recoveryKey = await keyring.create(passphrase: 'correct horse battery staple');
  print('Save this recovery key: $recoveryKey');
} else {
  // Unlocks an existing store
  final success = await keyring.unlockWithPassphrase('correct horse battery staple');
}

4. Encrypt and Decrypt Records

Every record gets its own 32-byte content key. sealPadded implements ISO/IEC 7816-4 padding to mask the true length of your plaintext.

// --- Encrypt ---
final contentKey = keyring.newContentKey();
final sealed = NemoCipher.sealPadded(plainTextBytes, contentKey);
// We wrap the content key for storage, not the data itself
final wrap = keyring.wrapContentKey(contentKey);
contentKey.dispose(); // Always dispose of keys after use

// --- Decrypt ---
final unlockedKey = keyring.unwrapContentKey(wrap, WrapSource.primary);
final decrypted = NemoCipher.openPadded(sealed, unlockedKey);
unlockedKey.dispose();

πŸ”’ Security Architecture

  • Key Wrapping: Master keys are encrypted under KEKs (Key Encryption Keys), enabling multiple independent unlock paths (passphrase, recovery key, biometrics).
  • Length Obfuscation: Ciphertext sizes are rounded to predefined buckets to prevent metadata leaks based on payload length.
  • The Vault: An isolated inner compartment with its own passphrase. Opening the primary keyring does not unlock the Vault, making it perfect for highly sensitive records.
  • Memory Hygiene: Long-lived key material is stored in libsodium SecureKey instances, utilizing guarded native memory pages that zero out automatically upon dispose().

For detailed information on the Threat Model and architecture, refer to the Main Workspace Repository.

Libraries

nemo_crypto
Key hierarchy and authenticated encryption primitives for E2EE local stores