abdm_fidelius

pub package CI License: MIT

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:

  1. ECDH — Elliptic Curve Diffie-Hellman key agreement
  2. HKDF-SHA256 — Key derivation
  3. 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.

Live Demo →

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:

  1. Public APIFideliusCurve25519, FideliusX25519, FideliusP256 (one class per curve)
  2. Crypto EnginesECDHEngine, HKDFEngine, AESGCMEngine, NonceProcessor (stateless, testable)
  3. 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:

  1. Fork the repository
  2. Create a feature branch
  3. Run dart test, dart analyze, and dart format --output=none --set-exit-if-changed .
  4. Submit a pull request

License

MIT — see LICENSE.

Libraries

abdm_fidelius
Dart implementation of ABDM Fidelius encryption (ECDH + HKDF + AES-256-GCM).