Advanced licensing solution for Flutter/Dart applications with cryptographic protection.
Licensify transforms complex license management into a simple process, providing maximum security and flexibility.
🚀 Contents
🔥 Features
- Powerful Cryptography: RSA and ECDSA with SHA-512 for robust protection
- Flexible Licenses: Built-in and custom types, metadata, and features
- Expiration: Automatic expiration verification
- Schema Validation: Validate license structures with custom schemas
- Storage Independence: Bring your own storage implementation
- Cross-Platform: Works on all platforms including web (WASM)
- High Performance: ECDSA up to 10x faster with 72% smaller key sizes
📦 Installation
dependencies:
licensify: ^1.7.0
🏁 Quick Start
ECDSA (recommended)
// 1. Generate key pair (server-side/developer only)
final keyPair = EcdsaKeyGenerator.generateKeyPairAsPem(curve: EcCurve.p256);
// 2. Create license (for your user)
final license = keyPair.privateKey.licenseGenerator(
appId: 'com.example.app',
expirationDate: DateTime.now().add(Duration(days: 365)),
type: LicenseType.pro,
);
// 3. Validate license (client-side)
final validator = keyPair.publicKey.licenseValidator;
final result = validator.validateLicense(license);
if (result.isValid) {
print('✅ License is valid');
} else {
print('❌ License is invalid: ${result.message}');
}
RSA (traditional approach)
// 1. Generate key pair
final keyPair = RsaKeyGenerator.generateKeyPairAsPem(bitLength: 2048);
// 2 and 3 - same as with ECDSA
📚 Usage Examples
Complete License Workflow
// SERVER: generating a license
// Import private key with automatic type detection
final privateKey = LicensifyKeyImporter.importPrivateKeyFromString(privateKeyPem);
final generator = privateKey.licenseGenerator;
final license = generator(
appId: 'com.example.app',
expirationDate: DateTime.now().add(Duration(days: 365)),
type: LicenseType.pro,
features: {
'maxUsers': 50,
'modules': ['reporting', 'analytics', 'export'],
'premium': true,
},
metadata: {
'customerName': 'My Company',
'contactEmail': 'support@mycompany.com',
},
);
// Convert to bytes for transmission/storage
final bytes = LicenseEncoder.encodeToBytes(license);
// CLIENT: validating the received license
// Import public key with automatic type detection
final publicKey = LicensifyKeyImporter.importPublicKeyFromString(publicKeyPem);
final validator = publicKey.licenseValidator;
// Read from bytes
final receivedLicense = LicenseEncoder.decodeFromBytes(bytes);
// Validate
final result = validator.validateLicense(receivedLicense);
if (result.isValid && !receivedLicense.isExpired) {
print('✅ License is valid - available level: ${receivedLicense.type.name}');
} else {
print('❌ License is invalid or expired');
}
// Check license features
if (receivedLicense.features?['premium'] == true) {
print('Premium features activated');
}
License Storage
// Built-in In-Memory storage
final storage = InMemoryLicenseStorage();
final repository = LicenseRepository(storage: storage);
// Save license
await repository.saveLicense(license);
// Retrieve current license
final savedLicense = await repository.getCurrentLicense();
// Custom storage implementation
class FileSystemLicenseStorage implements ILicenseStorage {
final String filePath;
FileSystemLicenseStorage(this.filePath);
@override
Future<bool> deleteLicenseData() async {
// Implementation to delete file
return true;
}
@override
Future<bool> hasLicense() async {
// Implementation to check if file exists
return true;
}
@override
Future<Uint8List?> loadLicenseData() async {
// Implementation to read file
return null;
}
@override
Future<bool> saveLicenseData(Uint8List data) async {
// Implementation to write to file
return true;
}
}
Schema Validation
// Define license schema
final schema = LicenseSchema(
featureSchema: {
'maxUsers': SchemaField(
type: FieldType.integer,
required: true,
validators: [NumberValidator(minimum: 1, maximum: 1000)],
),
'modules': SchemaField(
type: FieldType.array,
required: true,
validators: [
ArrayValidator(minItems: 1, itemValidator: StringValidator()),
],
),
},
metadataSchema: {
'customerName': SchemaField(
type: FieldType.string,
required: true,
),
},
allowUnknownFeatures: false,
allowUnknownMetadata: true,
);
// Validate license against schema
final schemaResult = validator.validateSchema(license, schema);
if (schemaResult.isValid) {
print('✅ License schema is valid');
} else {
print('❌ License schema is invalid:');
for (final entry in schemaResult.errors.entries) {
print(' - ${entry.key}: ${entry.value}');
}
}
// Comprehensive validation of signature, expiration, and schema
final isValid = validator.validateLicenseWithSchema(license, schema);
📖 Documentation
Key Formats and Importing
// Generate keys
final ecdsaKeyPair = EcdsaKeyGenerator.generateKeyPairAsPem();
// Create keys with explicit type specification
final privateKey = LicensifyPrivateKey.rsa(privateKeyPemString);
final publicKey = LicensifyPublicKey.ecdsa(publicKeyPemString);
// Import keys with automatic type detection
final privateKey = LicensifyKeyImporter.importPrivateKeyFromString(pemPrivateKey);
final publicKey = LicensifyKeyImporter.importPublicKeyFromString(pemPublicKey);
// Import keys from bytes
final privateKeyBytes = Uint8List.fromList(utf8.encode(privateKeyPem));
final privateKey = LicensifyKeyImporter.importPrivateKeyFromBytes(privateKeyBytes);
// Import key pair with auto type detection and compatibility check
final keyPair = LicensifyKeyImporter.importKeyPairFromStrings(
privateKeyPem: privatePemString,
publicKeyPem: publicPemString,
);
// The importer automatically:
// 1. Detects key type (RSA or ECDSA)
// 2. Verifies key format correctness
// 3. Ensures key pair consistency (matching types)
License Types
// Built-in types
final trial = LicenseType.trial;
final standard = LicenseType.standard;
final pro = LicenseType.pro;
// Custom types
final enterprise = LicenseType('enterprise');
final premium = LicenseType('premium');
License Format
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"appId": "com.example.app",
"createdAt": "2024-07-25T14:30:00Z",
"expirationDate": "2025-07-25T14:30:00Z",
"type": "pro",
"features": {
"maxUsers": 50,
"modules": ["analytics", "reporting"]
},
"metadata": {
"customerName": "My Company"
},
"signature": "Base64EncodedSignature..."
}
🔒 Security
- Private key should be stored only on the server or licensing authority side
- Public key can be safely embedded in your application
- Code obfuscation is recommended in release builds
- ECDSA with P-256 curve provides high security level with smaller key sizes
📝 License
SPDX-License-Identifier: LGPL-3.0-or-later
Created by Karim "nogipx" Mamatkazin