cipherlib 0.7.2 copy "cipherlib: ^0.7.2" to clipboard
cipherlib: ^0.7.2 copied to clipboard

Pure-Dart symmetric ciphers (AES modes, ChaCha20, XChaCha20, Salsa20, XSalsa20), their Poly1305 AEAD variants, and ML-KEM post-quantum key encapsulation (FIPS 203).

example/cipherlib_example.dart

// Cipherlib examples: authenticated encryption, tamper detection, and
// post-quantum key exchange.
//
// Each AEAD example encrypts a short message, decrypts it back, and verifies
// the round-trip. The tamper example shows how a modified ciphertext is
// rejected, and the ML-KEM example performs a post-quantum key exchange.
//
// Run with: dart run example/cipherlib_example.dart

import 'package:cipherlib/cipherlib.dart';
import 'package:cipherlib/codecs.dart';
import 'package:cipherlib/random.dart';

void main() {
  mlkemExample();
  aesGcmExample();
  chacha20Poly1305Example();
  xchacha20Poly1305Example();
  tamperedMessageExample();
}

// ML-KEM (FIPS 203) post-quantum key encapsulation: Alice publishes an
// encapsulation key; Bob derives a shared secret and a ciphertext from it;
// Alice recovers the same secret from that ciphertext.
void mlkemExample() {
  print('----- ML-KEM-768 (post-quantum key exchange) -----');
  final kem = MLKEM.kem768();

  final alice = kem.keygen(); // Alice's key pair
  final bob = kem.encaps(alice.encapsulationKey); // Bob encapsulates a secret
  final shared = kem.decaps(alice.decapsulationKey, bob.cipherText);

  print('   Bob: ${bob.sharedSecret.hex()}');
  print(' Alice: ${shared.hex()}');
  // Compare shared secrets in constant time, not with `==`.
  print(' Match: ${shared.isEqual(bob.sharedSecret)}');
  print('');
}

// AES-256-GCM via the object API: build a cipher instance, then encrypt and
// decrypt with it. A solid default for most applications.
void aesGcmExample() {
  print('----- AES-256-GCM (recommended for most apps) -----');
  final text = 'A practical message payload';
  final key = randomBytes(32); // 256-bit key
  final nonce = randomBytes(12); // fresh per message; never reuse (key, nonce)
  final aad = toUtf8('request-id=42'); // authenticated, but not encrypted

  final aes = AES(key).gcm(nonce, aad: aad);
  final sealed = aes.encrypt(toUtf8(text));
  final opened = aes.decrypt(sealed);

  print('   Key: ${toHex(key)}');
  print(' Nonce: ${toHex(nonce)}');
  print('   AAD: ${fromUtf8(aad)}');
  print('Cipher: ${toHex(sealed)}');
  print(' Plain: ${fromUtf8(opened)}');
  print(' Match: ${fromUtf8(opened) == text}');
  print('');
}

// ChaCha20-Poly1305 via the one-shot function API: the result carries both the
// ciphertext (`.data`) and the authentication tag (`.mac`).
void chacha20Poly1305Example() {
  print('----- ChaCha20-Poly1305 (mobile/network friendly) -----');
  final text = 'Hide me with ChaCha20';
  final key = randomBytes(32);
  final nonce = randomBytes(12); // fresh per message; never reuse (key, nonce)
  final aad = toUtf8('content-type:text');

  final sealed = chacha20poly1305(
    toUtf8(text),
    key,
    nonce: nonce,
    aad: aad,
  );
  // Decrypt by passing the tag back in via `mac:`; a mismatch throws.
  final opened = chacha20poly1305(
    sealed.data,
    key,
    nonce: nonce,
    aad: aad,
    mac: sealed.mac.bytes,
  );

  print('  Text: $text');
  print('   Key: ${toHex(key)}');
  print(' Nonce: ${toHex(nonce)}');
  print('Cipher: ${toHex(sealed.data)}');
  print('   Tag: ${sealed.mac.hex()}');
  print(' Plain: ${fromUtf8(opened.data)}');
  print(' Match: ${fromUtf8(opened.data) == text}');
  print('');
}

// XChaCha20-Poly1305 takes a 24-byte nonce — large enough to choose at random
// for every message without worrying about the birthday bound.
void xchacha20Poly1305Example() {
  print('----- XChaCha20-Poly1305 (extended nonce) -----');
  final text = 'Hide me!';
  final key = randomBytes(32);
  final nonce = randomBytes(24); // 192-bit nonce; safe to randomize per message
  final aad = toUtf8('demo-aad');

  final sealed = xchacha20poly1305(
    toUtf8(text),
    key,
    nonce: nonce,
    aad: aad,
  );
  final opened = xchacha20poly1305(
    sealed.data,
    key,
    nonce: nonce,
    aad: aad,
    mac: sealed.mac.bytes,
  );

  print('  Text: $text');
  print('   Key: ${toHex(key)}');
  print(' Nonce: ${toHex(nonce)}');
  print('Cipher: ${toHex(sealed.data)}');
  print('   Tag: ${sealed.mac.hex()}');
  print(' Plain: ${fromUtf8(opened.data)}');
  print(' Match: ${fromUtf8(opened.data) == text}');
  print('');
}

// Flipping a single tag bit makes decryption fail: authenticated ciphers
// reject tampered input instead of returning corrupted plaintext.
void tamperedMessageExample() {
  print('----- Tamper detection -----');
  final key = randomBytes(32);
  final nonce = randomBytes(24);
  final sealed = xchacha20poly1305(
    toUtf8('integrity protected'),
    key,
    nonce: nonce,
  );
  final badTag = List<int>.from(sealed.mac.bytes)..[0] ^= 0xff;

  try {
    xchacha20poly1305(
      sealed.data,
      key,
      nonce: nonce,
      mac: badTag,
    );
    print('Unexpected: tampered message accepted');
  } on StateError catch (e) {
    print('Rejected tampered message: ${e.message}');
  }
  print('');
}
6
likes
160
points
1.52k
downloads

Documentation

API reference

Publisher

verified publisherbitanon.dev

Weekly Downloads

Pure-Dart symmetric ciphers (AES modes, ChaCha20, XChaCha20, Salsa20, XSalsa20), their Poly1305 AEAD variants, and ML-KEM post-quantum key encapsulation (FIPS 203).

Repository (GitHub)
View/report issues

Funding

Consider supporting this project:

paypal.me

License

BSD-3-Clause (license)

Dependencies

hashlib

More

Packages that depend on cipherlib