air_link_secure 0.2.1
air_link_secure: ^0.2.1 copied to clipboard
End-to-end encryption (E2EE) with Diffie-Hellman key exchange for Air Link.
Air Link Secure #
End-to-end encryption for Air Link: ECDSA identity keys, mutual auth handshake, and AES-256-GCM session encryption.
final identity = await Identity.loadOrCreate();
final session = await connectSecure('192.168.1.10', identity,
heartbeat: HeartbeatConfig(), autoReconnect: true);
session.peer.onMessage.listen((msg) {
print('🔐 $msg'); // automatically decrypted
});
Features:
| Area | Algorithm / Protocol |
|---|---|
| Identity keys | ECDSA secp256r1 (persisted to disk) |
| Key exchange | Diffie-Hellman (RFC 3526 Group 14) |
| Key derivation | HKDF-SHA256 |
| Auth protocol | SIGMA-like (3 messages), mutual authentication |
| Trust model | Trust On First Use (TOFU) |
| Session encryption | AES-256-GCM, random 12-byte IV per packet |
Quick Start #
1. Add the dependency #
dependencies:
air_link_secure: ^0.2.0
2. Generate an identity #
Every device needs a persistent ECDSA identity key pair. It is automatically generated on first run and stored in a JSON file:
import 'package:air_link_secure/air_link_secure.dart';
final identity = await Identity.loadOrCreate(storagePath: 'identity.json');
print('My fingerprint: ${identity.fingerprint}');
// → "a1b2c3d4e5f6..."
3. Connect with automatic authentication #
// On device A — host
final identity = await Identity.loadOrCreate();
final sessions = await listenSecure(identity, port: 8080);
sessions.listen((session) {
print('Securely connected: ${session.peer.id}');
});
// On device B — connect
final identity = await Identity.loadOrCreate();
final session = await connectSecure('192.168.1.10', identity, port: 8080);
await session.peer.sendText('This message is encrypted');
The handshake happens automatically:
- Client sends its DH public key + identity public key
- Server responds with its DH key, identity key, and a signature
- Client verifies the signature, stores the server's key (TOFU)
- Client sends its own signature
- Server verifies, stores client's key (TOFU)
- Both sides derive the shared AES-256-GCM key and wrap the transport
API #
Identity #
| Method / Property | Description |
|---|---|
Identity.loadOrCreate({String? storagePath}) |
Load from disk or generate a fresh key pair |
Identity.generate({String? storagePath}) |
Generate a brand-new key pair |
Uint8List get publicKeyBytes |
Raw public key (65 bytes, uncompressed) |
String get fingerprint |
SHA-256 fingerprint of the public key (hex) |
static String fingerprintOf(Uint8List pubKeyBytes) |
Compute fingerprint of any public key |
Uint8List sign(Uint8List data) |
Sign data with the private key |
static bool verify(Uint8List data, Uint8List sig, Uint8List pubKey) |
Verify an ECDSA signature |
Uint8List? trustedPeerKey(String fingerprint) |
Look up a trusted peer's public key |
void trustPeer(String fingerprint, Uint8List pubKeyBytes) |
Manually trust a peer |
Future<void> save() |
Persist identity (including TOFU database) to disk |
KeyExchange #
| Method | Description |
|---|---|
KeyExchange() |
Generate ephemeral DH key pair |
Uint8List get publicKeyBytes |
DH public key (256 bytes, RFC 3526 Group 14) |
BigInt computeSharedSecret(BigInt peerPublic) |
Compute shared secret |
Uint8List deriveKey(BigInt sharedSecret) |
Derive 32-byte AES-256 key via HKDF-SHA256 |
SecureTransport #
| Feature | Description |
|---|---|
SecureTransport(Transport inner, Uint8List key) |
Wrap any transport with AES-256-GCM |
send(Uint8List data) |
Encrypt and send |
onData |
Receive decrypted data |
| Wire format | [4B encLen][12B IV][ciphertext + 16B GCM tag] |
Convenience functions #
| Function | Description |
|---|---|
connectSecure(host, identity, {port, heartbeat, autoReconnect}) |
Connect with full auth + encryption |
listenSecure(identity, {port, heartbeat}) |
Listen for secure connections |
Auto-Reconnect #
Pass autoReconnect: true to automatically re-establish a broken secure
connection. The handshake runs again on reconnect:
final session = await connectSecure('192.168.1.10', identity,
autoReconnect: true,
heartbeat: HeartbeatConfig(interval: Duration(seconds: 5)));
Platform Support #
| Platform | Status |
|---|---|
| Android | ✅ |
| iOS | ✅ |
| macOS | ✅ |
| Windows | ✅ |
| Linux | ✅ |
Zero Dependencies #
All cryptography is built with pointycastle: ^4.0.0 (pure Dart, no native
bindings). No OpenSSL, no BoringSSL, no platform channels.
Module Integration #
// Secure + Discovery: find peers, then connect securely
final fp = identity.fingerprint;
await DiscoveryService.advertise(port: 8080, identityFingerprint: fp);
DiscoveryService.discover().listen((peer) {
if (peer.identityFingerprint != null) {
connectSecure(peer.ip, identity, port: peer.port);
}
});
// Secure + File: encrypted file transfer
final session = await connectSecure('192.168.1.10', identity);
final sender = FileSender(session.peer);
await sender.share(confidentialFile); // encrypted automatically
Requirements #
- Dart SDK
>=3.0.0 <4.0.0 - Depends on
air_link_coreandpointycastle: ^4.0.0
License #
MIT