eter_secure_buffer 1.0.0 copy "eter_secure_buffer: ^1.0.0" to clipboard
eter_secure_buffer: ^1.0.0 copied to clipboard

Secure off-heap memory buffer for Flutter via FFI. Prevents plaintext from touching the Dart GC heap — malloc/mlock/memset/munlock outside Dart heap.

example/lib/main.dart

import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:eter_secure_buffer/eter_secure_buffer.dart';

void main() {
  runApp(const SecureBufferExampleApp());
}

class SecureBufferExampleApp extends StatelessWidget {
  const SecureBufferExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SecureBuffer Example',
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      home: const SecureBufferDemo(),
    );
  }
}

class SecureBufferDemo extends StatefulWidget {
  const SecureBufferDemo({super.key});

  @override
  State<SecureBufferDemo> createState() => _SecureBufferDemoState();
}

class _SecureBufferDemoState extends State<SecureBufferDemo> {
  SecureBuffer? _buffer;
  String _status = 'No buffer allocated.';
  String _mlockStatus = '';
  String _readPreview = '';

  void _allocate() {
    _wipeExisting();
    try {
      final buf = SecureBuffer.alloc(32);
      setState(() {
        _buffer = buf;
        _mlockStatus = 'mlock: attempted (best-effort on Android)';
        _status = 'Allocated 32 bytes of off-heap secure memory.';
        _readPreview = '';
      });
    } catch (e) {
      setState(() {
        _status = 'Allocation failed: $e';
      });
    }
  }

  void _write() {
    final buf = _buffer;
    if (buf == null || buf.isWiped) {
      setState(() => _status = 'No active buffer. Allocate first.');
      return;
    }
    final random = Random.secure();
    final bytes = Uint8List.fromList(
      List.generate(32, (_) => random.nextInt(256)),
    );
    try {
      buf.writeEncrypted(bytes);
      setState(() {
        _status = 'Wrote 32 random bytes into native memory.';
        _readPreview = '';
      });
    } catch (e) {
      setState(() => _status = 'Write failed: $e');
    }
  }

  void _read() {
    final buf = _buffer;
    if (buf == null || buf.isWiped) {
      setState(() => _status = 'No active buffer. Allocate first.');
      return;
    }
    try {
      // asView() returns a live pointer — read immediately, do not store
      final view = buf.asView();
      final hex = view
          .take(4)
          .map((b) => b.toRadixString(16).padLeft(2, '0'))
          .join(' ');
      setState(() {
        _readPreview = 'First 4 bytes: $hex  (use and discard immediately)';
        _status = 'Read successful. WARNING: asView() is a live native pointer.';
      });
    } catch (e) {
      setState(() => _status = 'Read failed: $e');
    }
  }

  void _wipe() {
    final buf = _buffer;
    if (buf == null) {
      setState(() => _status = 'No buffer to wipe.');
      return;
    }
    buf.wipe();
    setState(() {
      _status = 'Buffer wiped: memset(0x00) → munlock → free. Memory cleared.';
      _mlockStatus = '';
      _readPreview = '';
    });
  }

  void _wipeExisting() {
    final buf = _buffer;
    if (buf != null && !buf.isWiped) buf.wipe();
    _buffer = null;
  }

  @override
  void dispose() {
    _wipeExisting();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final buf = _buffer;
    final hasActiveBuffer = buf != null && !buf.isWiped;

    return Scaffold(
      appBar: AppBar(title: const Text('SecureBuffer Demo')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            _InfoCard(
              label: 'Status',
              value: _status,
              color: Colors.indigo.shade50,
            ),
            if (_mlockStatus.isNotEmpty)
              _InfoCard(
                label: 'mlock',
                value: _mlockStatus,
                color: Colors.green.shade50,
              ),
            if (_readPreview.isNotEmpty)
              _InfoCard(
                label: 'Read preview',
                value: _readPreview,
                color: Colors.orange.shade50,
              ),
            const SizedBox(height: 24),
            _ActionButton(
              label: 'Allocate (32 bytes)',
              icon: Icons.memory,
              onPressed: _allocate,
            ),
            const SizedBox(height: 12),
            _ActionButton(
              label: 'Write random bytes',
              icon: Icons.edit,
              onPressed: hasActiveBuffer ? _write : null,
            ),
            const SizedBox(height: 12),
            _ActionButton(
              label: 'Read (first 4 bytes)',
              icon: Icons.visibility,
              onPressed: hasActiveBuffer ? _read : null,
            ),
            const SizedBox(height: 12),
            _ActionButton(
              label: 'Wipe buffer',
              icon: Icons.delete_forever,
              color: Colors.red,
              onPressed: hasActiveBuffer ? _wipe : null,
            ),
            const SizedBox(height: 24),
            const Text(
              'Memory lifecycle: alloc → mlock → write → use → wipe (memset + munlock + free).\n'
              'The buffer lives outside the Dart GC heap — the garbage collector never sees plaintext.',
              style: TextStyle(fontSize: 12, color: Colors.grey),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}

class _InfoCard extends StatelessWidget {
  final String label;
  final String value;
  final Color color;

  const _InfoCard({
    required this.label,
    required this.value,
    required this.color,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.only(bottom: 8),
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(label,
              style:
                  const TextStyle(fontSize: 11, fontWeight: FontWeight.bold)),
          const SizedBox(height: 4),
          Text(value, style: const TextStyle(fontSize: 13)),
        ],
      ),
    );
  }
}

class _ActionButton extends StatelessWidget {
  final String label;
  final IconData icon;
  final VoidCallback? onPressed;
  final Color? color;

  const _ActionButton({
    required this.label,
    required this.icon,
    this.onPressed,
    this.color,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton.icon(
      icon: Icon(icon),
      label: Text(label),
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
        foregroundColor: color != null ? Colors.white : null,
        padding: const EdgeInsets.symmetric(vertical: 14),
      ),
      onPressed: onPressed,
    );
  }
}
0
likes
130
points
72
downloads

Publisher

unverified uploader

Weekly Downloads

Secure off-heap memory buffer for Flutter via FFI. Prevents plaintext from touching the Dart GC heap — malloc/mlock/memset/munlock outside Dart heap.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

unknown (license)

Dependencies

ffi, flutter

More

Packages that depend on eter_secure_buffer

Packages that implement eter_secure_buffer