multienc (Dart Client)

pub package

Dart client for multienc hybrid encryption. This package provides client-side encryption functionality compatible with the multienc Python package.

Use this package to encrypt data in your Dart application before sending it to a backend server that uses the multienc Python library for decryption.

Features

  • Provides a MultiEncClient class to manage configuration and encryption.
  • Implements the client-side encryption steps of the multienc protocol.
  • Uses AES-256-CBC for data encryption with keys derived from UID, timestamp window, and a shared key.
  • Uses RSA-OAEP (SHA-512) to encrypt the AES key/IV using the server's public key.
  • Handles necessary XOR operations and base64 encoding.

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  multienc: ^0.1.0

Then run dart pub get.

Setup: Getting Server Keys

Before using this package, you need the server's RSA public key and the shared client key used for AES derivation. You can obtain these from your multienc Python backend setup.

  1. Install the Python package:

    pip install multienc
    
  2. Generate/Load Keys in Python: Create a simple Python script to initialize the RSAKeyManager. This will generate private_key.pem and public_key.pem if they don't exist.

    # get_keys.py
    from multienc import RSAKeyManager
    import os
    
    # Use the same password and paths as your server config
    # Load from environment variables or secure config in a real app
    PRIVATE_KEY_PASSWORD = os.environ.get('YOUR_PK_PASSWORD', 'secure-password').encode()
    PRIVATE_KEY_PATH = "private_key.pem"
    PUBLIC_KEY_PATH = "public_key.pem"
    CLIENT_SHARED_KEY = os.environ.get('CLIENT_SHARED_KEY', 'clientpubkey1234') # Get shared key too
    
    try:
        # Initialize (generates keys if they don't exist)
        rsa_key_manager = RSAKeyManager(
            private_key_path=PRIVATE_KEY_PATH,
            public_key_path=PUBLIC_KEY_PATH,
            private_key_password=PRIVATE_KEY_PASSWORD
        )
        print("--- Server Public Key (PEM Format) ---")
        print(rsa_key_manager.get_public_key_pem())
        print("\n--- Client Shared Key (for AES Derivation) ---")
        # Replace 'clientpubkey1234' with the actual value from your server config
        # Ensure this matches the key used in your Dart client
        print(CLIENT_SHARED_KEY) # Print the actual shared key being used/generated
        print("\nEnsure the Client Shared Key above matches the key used in your Dart client.")
        print("Ensure the Server Public Key PEM above is used in your Dart client.")
    
    except Exception as e:
        print(f"Error initializing RSAKeyManager: {e}")
    
    

    Run this script (e.g., python get_keys.py). You might need to set environment variables first (e.g., export YOUR_PK_PASSWORD='your_actual_password').

  3. Use the Output:

    • Copy the entire -----BEGIN PUBLIC KEY----- block printed by the Python script.
    • Copy the Client Shared Key value printed by the script. This must match the value used when initializing MultiEncClient in Dart.

Usage (Encryption)

import 'package:multienc/multienc.dart';
import 'dart:io' show Platform, exit;

void main() async {
  // --- Load Configuration Securely ---
  // In a real app (Flutter), use appropriate config management.
  final String serverPublicKeyPem = 'SERVER_PUBLIC_KEY_PEM';
  final String clientSharedKey = 'CLIENT_SHARED_KEY';
  final String userId = 'default-user'; // unique per user/session

  if (serverPublicKeyPem.isEmpty || clientSharedKey.isEmpty) {
    print("ERROR: SERVER_PUBLIC_KEY_PEM and CLIENT_SHARED_KEY env vars must be set.");
    exit(1);
  }

  // --- Initialize Client ---
  MultiEncClient client;
  try {
    client = MultiEncClient(
      serverPublicKeyPem: serverPublicKeyPem,
      clientSharedKey: clientSharedKey,
    );
    print("MultiEncClient initialized.");
  } on MultiEncError catch (e) { // Catch specific initialization errors
    print("ERROR initializing MultiEncClient: ${e.runtimeType} - ${e.message}");
    exit(1);
  } catch (e) {
    print("Unexpected error during initialization: $e");
    exit(1);
  }


  // --- Data to encrypt ---
  final dataToSend = {
    'message': 'Hello from Dart client (class-based)!',
    'timestamp': DateTime.now().toIso8601String(),
    'value': 123.45,
  };

  // --- Encryption ---
  try {
    print("Encrypting data for UID: $userId");
    String encryptedPayload = client.encrypt(
      jsonData: dataToSend,
      uid: userId,
    );

    print('\nEncrypted Payload to send to server:');
    print(encryptedPayload);

    // Now send the 'encryptedPayload' string to your backend endpoint.
    // Example using http package (add `http: ^1.0.0` to pubspec.yaml):
    /*
    import 'package:http/http.dart' as http;
    import 'dart:convert';

    final url = Uri.parse('https://apiserver/api/data');
    final response = await http.post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: encryptedPayload, // Send the already JSON-encoded string
    );

    if (response.statusCode == 200) {
      print('Successfully sent encrypted data.');
      // Handle success response from server
    } else {
      print('Error sending data: ${response.statusCode}');
      print('Response body: ${response.body}');
    }
    */

  } on MultiEncError catch (e) {
    print('Encryption failed: ${e.runtimeType} - ${e.message}');
    // Handle encryption error
  } catch (e) {
    print('An unexpected error occurred during encryption: $e');
  }
}

Important:

  • Configuration Management: The example uses environment variables. In real applications (Flutter, server-side Dart), use secure methods like configuration files, platform-specific secure storage, or dedicated secrets management solutions. Never hardcode keys or sensitive data.
  • Key Matching: The clientSharedKey used to initialize MultiEncClient in Dart must exactly match the CLIENT_PUBLIC_KEY_FOR_AES used on the Python server. The serverPublicKeyPem in Dart must correspond to the private_key.pem used by the server.
  • UID: Use a meaningful and consistent unique identifier for each user or session.

Decryption

Decryption is handled by the multienc Python package on the server-side. This Dart package only performs encryption.

TODO

Decrypt on client side

Libraries