abdm_fidelius 0.1.0
abdm_fidelius: ^0.1.0 copied to clipboard
Dart implementation of ABDM Fidelius encryption (ECDH + HKDF + AES-256-GCM) for India's health data exchange.
abdm_fidelius #
The first pure-Dart implementation of India's ABDM Fidelius encryption protocol. Byte-for-byte interoperable with Java fidelius-cli. Supports three elliptic curves, includes a CLI tool, and ships with a Flutter web showcase.
What is Fidelius? #
Fidelius is the encryption protocol mandated by India's Ayushman Bharat Digital Mission (ABDM) for exchanging health data between providers (HIP) and consumers (HIU). It combines:
- ECDH — Elliptic Curve Diffie-Hellman key agreement
- HKDF-SHA256 — Key derivation
- AES-256-GCM — Authenticated encryption
This package implements the complete protocol as a Dart library, CLI tool, and Flutter showcase.
Why Does This Matter? (For Clinicians & Health-Tech Builders) #
If you are a doctor, clinic owner, or health-tech builder — not necessarily a software engineer — here is what this package does in plain language:
The problem: When a patient's health records move between hospitals, labs, or health apps in India's ABDM network, that data must be encrypted so no one in the middle can read it. The government mandates a specific encryption method called "Fidelius" for this.
What this package does: It is a ready-to-use encryption toolkit for Dart and Flutter apps. You give it a patient's health record, and it locks it with Fidelius encryption. The receiving hospital or app can then unlock it. No one in between can read it.
How other developers do it today: The only existing implementation is a Java library called fidelius-cli. That means every Flutter or Dart developer building ABDM-compliant apps has to either:
- Write a Java backend just for encryption, or
- Use platform channels to call Java/Kotlin code from Flutter, or
- Re-implement the protocol from scratch (error-prone and risky)
What this changes: With abdm_fidelius, you add one line to your pubspec.yaml and call encrypt() / decrypt(). No Java, no backend, no platform channels. It works on web, mobile, and server — pure Dart.
About key storage: This package generates and uses encryption keys, but it does not store them. Think of it like a lock-and-key machine — it makes the locks and keys, but you need your own safe to keep the keys in. Your app is responsible for securely storing private keys (e.g. using Flutter Secure Storage on mobile, or encrypted database on server). See the Key Management section below for ABDM guidance.
Scope of this package:
- Encrypt and decrypt health data per ABDM Fidelius specification
- Generate cryptographic key pairs for ABDM gateway integration
- Interoperate with existing Java fidelius-cli implementations
- CLI tool for testing encryption without writing code
Not in scope: This package handles encryption only. It does not handle ABDM registration, consent management, FHIR formatting, network communication, or key storage — those are separate concerns in your app.
Quick Start #
import 'package:abdm_fidelius/abdm_fidelius.dart';
final fidelius = FideliusCurve25519();
// Both parties generate key pairs
final sender = await fidelius.generateKeyPair();
final receiver = await fidelius.generateKeyPair();
// Sender encrypts
final encrypted = await fidelius.encrypt(
plaintext: 'Patient health record',
senderKeyPair: sender,
receiverPublicKey: receiver.publicKey,
receiverNonce: receiver.nonce,
);
// Receiver decrypts
final decrypted = await fidelius.decrypt(
payload: encrypted,
receiverKeyPair: receiver,
);
print(decrypted); // "Patient health record"
Supported Curves #
| Class | Curve | Key size | ABDM accepted | When to use |
|---|---|---|---|---|
FideliusCurve25519 |
Weierstrass Curve25519 | 65B pub / 32B priv | Yes (default) | ABDM gateway integration |
FideliusX25519 |
X25519 (RFC 7748) | 32B pub / 32B priv | Not yet | Non-ABDM or future ReBIT 2.0 |
FideliusP256 |
P-256 / secp256r1 | 65B pub / 32B priv | Not yet | NIST-required environments |
All three curves use identical HKDF + AES-256-GCM downstream steps — only the ECDH key agreement differs.
Installation #
dart pub add abdm_fidelius
Or add to pubspec.yaml:
dependencies:
abdm_fidelius: ^0.1.0
Protocol Flow #
sequenceDiagram
participant S as Sender (HIP)
participant R as Receiver (HIU)
Note over S,R: Key Exchange
S->>S: generateKeyPair()
R->>R: generateKeyPair()
S-->>R: publicKey + nonce
R-->>S: publicKey + nonce
Note over S: Encryption
S->>S: 1. ECDH: senderPriv × receiverPub → sharedSecret
S->>S: 2. XOR nonces → salt (20B) + IV (12B)
S->>S: 3. HKDF-SHA256(sharedSecret, salt) → AES key (32B)
S->>S: 4. AES-256-GCM(plaintext, key, IV) → ciphertext + tag
S-->>R: encryptedData + IV + senderPublicKey + senderNonce
Note over R: Decryption
R->>R: 1. ECDH: receiverPriv × senderPub → sharedSecret
R->>R: 2. XOR nonces → salt + IV (same as sender)
R->>R: 3. HKDF-SHA256(sharedSecret, salt) → AES key
R->>R: 4. AES-256-GCM decrypt → plaintext
Usage #
Basic Encryption & Decryption #
import 'package:abdm_fidelius/abdm_fidelius.dart';
const fidelius = FideliusCurve25519();
final sender = await fidelius.generateKeyPair();
final receiver = await fidelius.generateKeyPair();
final encrypted = await fidelius.encrypt(
plaintext: 'Sensitive data',
senderKeyPair: sender,
receiverPublicKey: receiver.publicKey,
receiverNonce: receiver.nonce,
);
final decrypted = await fidelius.decrypt(
payload: encrypted,
receiverKeyPair: receiver,
);
Choosing a Curve #
// X25519 — standard Montgomery Curve25519
const x25519 = FideliusX25519();
final keys = await x25519.generateKeyPair();
// P-256 — NIST secp256r1
const p256 = FideliusP256();
final keys = await p256.generateKeyPair();
All three classes share the same generateKeyPair(), encrypt(), decrypt() API.
Error Handling #
decrypt() returns null on GCM authentication failure (wrong key, tampered data). Use the optional onError callback for logging:
final result = await fidelius.decrypt(
payload: encrypted,
receiverKeyPair: receiver,
onError: (error) => print('Decryption failed: $error'),
);
if (result == null) {
// Authentication failed — data was tampered or wrong key
}
JSON Serialization #
Both FideliusKeyPair and FideliusEncryptedPayload support JSON round-trips:
// Key pair — safe for transmission (excludes private key)
final publicJson = keyPair.toJson();
// Key pair — full (for local storage only)
final fullJson = keyPair.toJsonWithPrivateKey();
final restored = FideliusKeyPair.fromJson(fullJson);
// Encrypted payload
final payloadJson = encrypted.toJson();
final parsed = FideliusEncryptedPayload.fromJson(payloadJson);
ABDM Health Data Exchange #
A complete ABDM workflow: HIP encrypts a FHIR bundle, HIU decrypts after consent.
import 'dart:convert';
import 'package:abdm_fidelius/abdm_fidelius.dart';
// 1. Key exchange: HIP and HIU generate and share public keys + nonces
const fidelius = FideliusCurve25519();
final hipKeys = await fidelius.generateKeyPair();
final hiuKeys = await fidelius.generateKeyPair();
// Exchange hipKeys.toJson() ↔ hiuKeys.toJson() via ABDM gateway
// 2. HIP encrypts the FHIR bundle
final fhirBundle = jsonEncode({
'resourceType': 'Bundle',
'type': 'collection',
'entry': [
{'resource': {'resourceType': 'Patient', 'name': [{'given': ['Rahul']}]}},
{'resource': {'resourceType': 'Observation', 'code': {'text': 'BP'}}},
],
});
final encrypted = await fidelius.encrypt(
plaintext: fhirBundle,
senderKeyPair: hipKeys,
receiverPublicKey: hiuKeys.publicKey,
receiverNonce: hiuKeys.nonce,
);
// Send encrypted.toJson() to HIU via ABDM data flow
// 3. HIU decrypts with their key pair
final decrypted = await fidelius.decrypt(
payload: encrypted,
receiverKeyPair: hiuKeys,
);
final bundle = jsonDecode(decrypted!);
print(bundle['entry'].length); // 2
Low-Level Primitives #
For debugging interop issues or building custom protocols, individual crypto steps are available:
import 'package:abdm_fidelius/abdm_fidelius.dart';
// Generate key pair for any curve
final keys = await generateKeyPair(FideliusCurveType.curve25519);
// Step-by-step protocol execution
final sharedSecret = await deriveSharedSecret(
privateKeyBytes: senderPrivBytes,
publicKeyBytes: receiverPubBytes,
curve: FideliusCurveType.curve25519,
);
final (:salt, :iv) = xorAndSplitNonces(senderNonce, receiverNonce);
final aesKey = await deriveKey(sharedSecret: sharedSecret, salt: salt);
final ciphertext = await aesGcmEncrypt(plaintext: data, key: aesKey, iv: iv);
final plaintext = await aesGcmDecrypt(ciphertext: ciphertext, key: aesKey, iv: iv);
// DER encoding for X.509 SubjectPublicKeyInfo
final der = encodeX509SpkiDer(rawPublicKeyBytes, FideliusCurveType.curve25519);
final raw = decodeX509SpkiDer(derBytes);
CLI #
The package includes a command-line tool that replaces Java fidelius-cli — no JRE required.
Generate Key Pair #
dart run abdm_fidelius generate
dart run abdm_fidelius generate --curve x25519
dart run abdm_fidelius generate --curve p256
Encrypt #
dart run abdm_fidelius encrypt \
--plaintext "Health record data" \
--sender-private-key "base64..." \
--sender-nonce "base64..." \
--receiver-public-key "base64..." \
--receiver-nonce "base64..."
Decrypt #
dart run abdm_fidelius decrypt \
--encrypted-data "base64..." \
--iv "base64..." \
--sender-public-key "base64..." \
--sender-nonce "base64..." \
--receiver-private-key "base64..." \
--receiver-nonce "base64..."
All commands output JSON. Pipe to jq for formatting:
dart run abdm_fidelius generate | jq .
Showcase #
A Flutter web app demonstrating the protocol with an interactive playground and guided wizard.
Features:
- 7-step guided wizard explaining each protocol phase
- Interactive playground: generate keys, encrypt, decrypt with custom input
- Intermediate values display: shared secret, XOR'd nonces, HKDF salt, AES key, GCM tag
- Responsive layout (768px breakpoint)
Architecture #
abdm_fidelius/
├── lib/
│ ├── abdm_fidelius.dart # Barrel export
│ └── src/
│ ├── fidelius_curve25519.dart # Weierstrass Curve25519 (ABDM default)
│ ├── fidelius_x25519.dart # X25519 (RFC 7748)
│ ├── fidelius_p256.dart # P-256 / secp256r1
│ ├── primitives.dart # Low-level primitive exports
│ ├── crypto/
│ │ ├── ecdh_engine.dart # ECDH key generation + shared secret
│ │ ├── hkdf_engine.dart # HKDF-SHA256 key derivation
│ │ ├── aes_gcm_engine.dart # AES-256-GCM encrypt/decrypt
│ │ ├── nonce_processor.dart # XOR nonces → salt + IV
│ │ └── curve25519_params.dart # Weierstrass curve parameters
│ ├── encoding/
│ │ ├── der_encoder.dart # X.509 SPKI DER encode/decode
│ │ └── base64_utils.dart # Strict base64 validation
│ └── models/
│ ├── key_pair.dart # FideliusKeyPair
│ ├── encrypted_payload.dart # FideliusEncryptedPayload
│ └── curve_type.dart # FideliusCurveType enum
├── bin/
│ └── abdm_fidelius.dart # CLI tool
├── example/
│ └── example.dart # Complete usage examples
└── test/ # 188 tests
Three layers:
- Public API —
FideliusCurve25519,FideliusX25519,FideliusP256(one class per curve) - Crypto Engines —
ECDHEngine,HKDFEngine,AESGCMEngine,NonceProcessor(stateless, testable) - Encoding — DER encoder, base64 utilities
Interoperability #
This package produces byte-for-byte identical output to Java fidelius-cli for Weierstrass Curve25519.
Field mapping:
| abdm_fidelius | fidelius-cli |
|---|---|
encryptedData |
encryptedData |
iv |
iv |
senderPublicKey |
keyValue |
senderNonce |
senderNonce |
Cross-implementation tested:
- Dart-encrypted → Java-decrypted
- Java-encrypted → Dart-decrypted
Key Management #
This package performs cryptographic operations only — it does not store, persist, or manage keys. Key storage is your application's responsibility.
What ABDM Recommends #
ABDM's protocol design mandates ephemeral keys — a fresh key pair and nonce for every single data exchange transaction. The API schema uses the literal parameter value "Ephemeral" and includes a dhPublicKey.expiry timestamp tied to the consent artifact's validity window.
| Guideline | ABDM stance |
|---|---|
| Key pair lifetime | One per transaction — generate, use, discard |
| Nonce reuse | Never. Each transaction gets a fresh 32-byte random nonce |
| Forward secrecy | Mandated by architecture — achieved only if keys are truly ephemeral |
| Key storage mechanism | Unspecified — left to the implementer |
| HSM / secure enclave | Not required, but recommended for enterprise deployments |
Best Practices for Your App #
HIP (sending hospital/lab): Keys are truly ephemeral. Generate a key pair, encrypt the FHIR bundle, transmit, then discard the private key immediately. The private key is needed only for the single encrypt() call.
HIU (receiving hospital/app): Keys are "short-lived" rather than instant-use. The HIU generates its key pair when requesting data, but must hold the private key until the encrypted response arrives (minutes to hours). During this window, store the private key securely:
| Platform | Recommended storage |
|---|---|
| Flutter mobile | flutter_secure_storage (iOS Keychain / Android Keystore) |
| Flutter web | Encrypted in backend database, never in browser localStorage |
| Dart server | Cloud KMS-wrapped storage (GCP KMS, AWS KMS) with auto-delete |
| Enterprise / govt | HSM-backed storage (GCP Cloud HSM, AWS CloudHSM) |
Critical rules:
- Never reuse a key pair across multiple transactions
- Never reuse a nonce — always generate fresh 32 bytes via
SecureRandom - Delete private keys as soon as decryption is complete
- Never store raw (unwrapped) private keys in a database
- Log all key generation and deletion events for audit compliance
Dart runtime note: Private keys exist in memory during crypto operations. Dart's garbage collector does not guarantee memory zeroing, so there is no way to explicitly wipe key material from RAM. This is the same limitation as Java's fidelius-cli. For security-critical deployments, consider performing crypto operations via platform channels to native code (iOS CommonCrypto, Android Keystore) where memory can be explicitly cleared.
Performance #
Pure-Dart benchmarks (Apple M-series, Dart VM, 50 iterations):
| Operation | Curve25519 | X25519 | P-256 |
|---|---|---|---|
| Key generation | 305 ops/s | 1.5K ops/s | 330 ops/s |
| ECDH shared secret | 368 ops/s | 1.3K ops/s | 390 ops/s |
| Full encrypt (1KB) | 362 ops/s | 1.2K ops/s | 384 ops/s |
| Full decrypt (1KB) | 358 ops/s | 1.2K ops/s | 390 ops/s |
Standalone operations: HKDF-SHA256 20K ops/s, AES-GCM encrypt 7.4K ops/s (1KB), DER encode 159K ops/s.
Run benchmarks: dart run benchmark/fidelius_benchmark.dart
Uses pointy_castle for Weierstrass Curve25519 and P-256 ECDH, and cryptography for X25519, HKDF-SHA256, and AES-256-GCM.
For production Flutter apps, add cryptography_flutter to use platform-native crypto (Web Crypto API on web, CommonCrypto on iOS/macOS) for a significant speed boost on HKDF and AES operations.
API Reference #
Full API documentation is available on pub.dev.
For deeper technical documentation, see the doc/ folder:
- Architecture — component design, dependency graph, design decisions
- Protocol — full Fidelius protocol specification with diagrams
- Interop — interoperability guide (fidelius-cli, pyfidelius, ABDM gateway)
- Curves — curve comparison, when to use each, key format differences
- Security — security considerations and recommendations
Contributing #
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Run
dart test,dart analyze, anddart format --output=none --set-exit-if-changed . - Submit a pull request
License #
MIT — see LICENSE.