multienc (Dart Client)
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
MultiEncClientclass to manage configuration and encryption. - Implements the client-side encryption steps of the
multiencprotocol. - 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.
-
Install the Python package:
pip install multienc -
Generate/Load Keys in Python: Create a simple Python script to initialize the
RSAKeyManager. This will generateprivate_key.pemandpublic_key.pemif 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'). -
Use the Output:
- Copy the entire
-----BEGIN PUBLIC KEY-----block printed by the Python script. - Copy the
Client Shared Keyvalue printed by the script. This must match the value used when initializingMultiEncClientin Dart.
- Copy the entire
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
clientSharedKeyused to initializeMultiEncClientin Dart must exactly match theCLIENT_PUBLIC_KEY_FOR_AESused on the Python server. TheserverPublicKeyPemin Dart must correspond to theprivate_key.pemused 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