CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-tencent--mmkv

High-performance mobile key-value storage framework with memory mapping, encryption, and multi-process support for Android applications.

Pending
Overview
Eval results
Files

encryption.mddocs/

Encryption and Security

Built-in encryption capabilities with key management, supporting AES encryption for secure data storage and key transformation operations.

Capabilities

Encryption Key Management

Manage encryption keys for secure data storage with up to 16-byte keys.

/**
 * Get the current encryption key.
 * @return The encryption key string, or null if not encrypted
 */
public String cryptKey();

/**
 * Transform plain text into encrypted text, or vice versa.
 * Can also change existing encryption key with a different key.
 * @param cryptKey The new encryption key (no more than 16 bytes), null to remove encryption
 * @return True if successful, false otherwise
 */
public boolean reKey(String cryptKey);

/**
 * Reset the encryption key without encrypting or decrypting anything.
 * Usually called after another process has reKey() the multi-process MMKV instance.
 * @param cryptKey The new encryption key (no more than 16 bytes)
 */
public void checkReSetCryptKey(String cryptKey);

Usage Example:

import com.tencent.mmkv.MMKV;

// Create encrypted MMKV instance
MMKV secureKv = MMKV.mmkvWithID("secure_data", MMKV.SINGLE_PROCESS_MODE, "my-secret-key");

// Check current encryption key
String currentKey = secureKv.cryptKey();  // "my-secret-key"

// Store sensitive data
secureKv.encode("password", "user-password");
secureKv.encode("api_key", "secret-api-key");
secureKv.encode("token", "access-token-12345");

// Change encryption key (re-encrypts all data)
boolean success = secureKv.reKey("new-secret-key");
if (success) {
    Log.d("MMKV", "Successfully changed encryption key");
} else {
    Log.e("MMKV", "Failed to change encryption key");
}

// Remove encryption (converts to plain text)
secureKv.reKey(null);

// Add encryption to existing plain text data
secureKv.reKey("another-secret-key");

Multi-Process Encryption Synchronization

Handle encryption key changes across multiple processes safely.

/**
 * Reset the encryption key without encrypting/decrypting.
 * Use this when another process has changed the encryption key.
 * @param cryptKey The new encryption key that was set by another process
 */
public void checkReSetCryptKey(String cryptKey);

Usage Example:

// In Process A - change encryption key
MMKV multiProcessKv = MMKV.mmkvWithID("shared_data", MMKV.MULTI_PROCESS_MODE, "old-key");
boolean changed = multiProcessKv.reKey("new-key");

// In Process B - synchronize the key change
MMKV sharedKv = MMKV.mmkvWithID("shared_data", MMKV.MULTI_PROCESS_MODE, "old-key");
if (changed) {
    // Reset to the new key without re-encrypting
    sharedKv.checkReSetCryptKey("new-key");
    // Now Process B can access the data with the new key
}

Encryption Status Check

Check if an MMKV instance has encryption enabled.

/**
 * Check if encryption is enabled for this instance.
 * @return True if encryption is enabled, false otherwise
 */
private boolean isEncryptionEnabled();

Usage Example:

MMKV plainKv = MMKV.mmkvWithID("plain_data");
MMKV encryptedKv = MMKV.mmkvWithID("encrypted_data", MMKV.SINGLE_PROCESS_MODE, "secret");

// Note: isEncryptionEnabled() is private, but you can check via cryptKey()
boolean plainHasEncryption = (plainKv.cryptKey() != null);       // false
boolean encryptedHasEncryption = (encryptedKv.cryptKey() != null); // true

Log.d("MMKV", "Plain KV encrypted: " + plainHasEncryption);
Log.d("MMKV", "Encrypted KV encrypted: " + encryptedHasEncryption);

Security Best Practices

Best practices for using MMKV encryption securely.

Key Generation Example:

import java.security.SecureRandom;
import java.util.Base64;

public class MMKVSecurity {
    
    /**
     * Generate a secure random encryption key.
     * @return A base64-encoded encryption key suitable for MMKV
     */
    public static String generateSecureKey() {
        SecureRandom random = new SecureRandom();
        byte[] keyBytes = new byte[16]; // 16 bytes = 128 bits
        random.nextBytes(keyBytes);
        return Base64.getEncoder().encodeToString(keyBytes);
    }
    
    /**
     * Create an encrypted MMKV instance with a secure key.
     * @param instanceId The unique ID for the MMKV instance
     * @param encryptionKey The encryption key (store securely!)
     * @return The encrypted MMKV instance
     */
    public static MMKV createSecureMMKV(String instanceId, String encryptionKey) {
        return MMKV.mmkvWithID(instanceId, MMKV.SINGLE_PROCESS_MODE, encryptionKey);
    }
}

// Usage
String secureKey = MMKVSecurity.generateSecureKey();
// Store secureKey in Android Keystore or other secure location
MMKV secureStorage = MMKVSecurity.createSecureMMKV("user_credentials", secureKey);

Android Keystore Integration

Example of integrating MMKV encryption with Android Keystore for enhanced security.

Usage Example:

import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import java.security.KeyStore;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import android.util.Base64;

public class MMKVKeystoreHelper {
    
    private static final String KEYSTORE_ALIAS = "MMKVMasterKey";
    private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
    
    /**
     * Generate or retrieve a master key from Android Keystore.
     * @return The master key for encrypting MMKV keys
     */
    public static SecretKey getMasterKey() throws Exception {
        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
        keyStore.load(null);
        
        if (!keyStore.containsAlias(KEYSTORE_ALIAS)) {
            // Generate new key
            KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);
            KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
                    KEYSTORE_ALIAS,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                    .build();
            keyGenerator.init(keyGenParameterSpec);
            return keyGenerator.generateKey();
        } else {
            // Retrieve existing key
            return (SecretKey) keyStore.getKey(KEYSTORE_ALIAS, null);
        }
    }
    
    /**
     * Create an MMKV instance with Keystore-protected encryption.
     * @param instanceId The unique ID for the MMKV instance
     * @return The encrypted MMKV instance
     */
    public static MMKV createKeystoreProtectedMMKV(String instanceId) throws Exception {
        // Generate MMKV encryption key
        SecureRandom random = new SecureRandom();
        byte[] mmkvKey = new byte[16];
        random.nextBytes(mmkvKey);
        
        // Encrypt the MMKV key with Keystore key
        SecretKey masterKey = getMasterKey();
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, masterKey);
        byte[] encryptedKey = cipher.doFinal(mmkvKey);
        
        // Store encrypted key in SharedPreferences or other location
        // (In real app, you'd save this encrypted key for later retrieval)
        
        // Create MMKV with the raw key
        String mmkvKeyString = Base64.encodeToString(mmkvKey, Base64.NO_WRAP);
        return MMKV.mmkvWithID(instanceId, MMKV.SINGLE_PROCESS_MODE, mmkvKeyString);
    }
}

// Usage
try {
    MMKV keystoreProtectedKv = MMKVKeystoreHelper.createKeystoreProtectedMMKV("secure_user_data");
    keystoreProtectedKv.encode("sensitive_info", "highly confidential data");
} catch (Exception e) {
    Log.e("MMKV", "Failed to create keystore-protected MMKV", e);
}

Performance Considerations

Encryption impact on performance and optimization strategies.

Usage Example:

public class MMKVPerformanceExample {
    
    public void demonstrateEncryptionPerformance() {
        // Plain MMKV - fastest
        MMKV plainKv = MMKV.mmkvWithID("plain_data");
        
        // Encrypted MMKV - slower due to encryption overhead
        MMKV encryptedKv = MMKV.mmkvWithID("encrypted_data", MMKV.SINGLE_PROCESS_MODE, "key");
        
        // Disable compare-before-set for encrypted instances (it's inefficient)
        // encryptedKv.enableCompareBeforeSet(); // Don't do this with encryption
        
        long startTime, endTime;
        String testData = "This is test data for performance measurement";
        
        // Measure plain storage performance
        startTime = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            plainKv.encode("key_" + i, testData);
        }
        endTime = System.nanoTime();
        long plainTime = endTime - startTime;
        
        // Measure encrypted storage performance
        startTime = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            encryptedKv.encode("key_" + i, testData);
        }
        endTime = System.nanoTime();
        long encryptedTime = endTime - startTime;
        
        Log.d("MMKV", String.format("Plain: %d ns, Encrypted: %d ns, Overhead: %.2fx", 
                                   plainTime, encryptedTime, (double)encryptedTime / plainTime));
    }
}

Encryption Migration

Migrating existing plain text data to encrypted storage.

Usage Example:

public class MMKVEncryptionMigration {
    
    /**
     * Migrate existing plain MMKV data to encrypted storage.
     * @param instanceId The MMKV instance ID to migrate
     * @param encryptionKey The encryption key to use
     * @return True if migration successful
     */
    public static boolean migrateToEncrypted(String instanceId, String encryptionKey) {
        try {
            // Get existing plain instance
            MMKV plainKv = MMKV.mmkvWithID(instanceId);
            
            // Get all existing keys and values
            String[] allKeys = plainKv.allKeys();
            if (allKeys == null || allKeys.length == 0) {
                // No data to migrate, just enable encryption
                return plainKv.reKey(encryptionKey);
            }
            
            // Create temporary encrypted instance
            String tempId = instanceId + "_temp_encrypted";
            MMKV tempEncryptedKv = MMKV.mmkvWithID(tempId, MMKV.SINGLE_PROCESS_MODE, encryptionKey);
            
            // Copy all data to encrypted instance
            for (String key : allKeys) {
                // Try different data types (you might want to track types separately)
                try {
                    String stringValue = plainKv.decodeString(key);
                    if (stringValue != null) {
                        tempEncryptedKv.encode(key, stringValue);
                        continue;
                    }
                } catch (Exception ignored) {}
                
                try {
                    int intValue = plainKv.decodeInt(key, Integer.MIN_VALUE);
                    if (intValue != Integer.MIN_VALUE) {
                        tempEncryptedKv.encode(key, intValue);
                        continue;
                    }
                } catch (Exception ignored) {}
                
                // Add other type checks as needed...
                
                // Fallback to bytes
                byte[] bytesValue = plainKv.decodeBytes(key);
                if (bytesValue != null) {
                    tempEncryptedKv.encode(key, bytesValue);
                }
            }
            
            // Replace original with encrypted version
            plainKv.clearAll();
            long importCount = plainKv.importFrom(tempEncryptedKv);
            boolean reKeySuccess = plainKv.reKey(encryptionKey);
            
            // Clean up temporary instance
            MMKV.removeStorage(tempId);
            
            return reKeySuccess && importCount > 0;
            
        } catch (Exception e) {
            Log.e("MMKV", "Failed to migrate to encrypted storage", e);
            return false;
        }
    }
}

// Usage
boolean migrated = MMKVEncryptionMigration.migrateToEncrypted("user_data", "secret-key");
if (migrated) {
    Log.d("MMKV", "Successfully migrated to encrypted storage");
} else {
    Log.e("MMKV", "Failed to migrate to encrypted storage");
}

Security Notes

  1. Key Length: Encryption keys must be no more than 16 bytes (128-bit AES)
  2. Key Storage: Store encryption keys securely using Android Keystore or other secure mechanisms
  3. Performance: Encryption adds computational overhead; use only for sensitive data
  4. Multi-Process: Use checkReSetCryptKey() to synchronize key changes across processes
  5. Compare-Before-Set: Disable enableCompareBeforeSet() for encrypted instances as it's inefficient
  6. Key Rotation: Use reKey() to change encryption keys and re-encrypt existing data
  7. Migration: Plan migration strategy when adding encryption to existing plain text data

Install with Tessl CLI

npx tessl i tessl/maven-com-tencent--mmkv

docs

data-management.md

data-storage.md

encryption.md

index.md

initialization.md

instance-management.md

multi-process.md

namespace.md

tile.json