createSelectiveEncryptionDecryptHook function
Creates a hook that decrypts selectively encrypted fields.
Implementation
PVCacheHook createSelectiveEncryptionDecryptHook({
String? encryptionKey,
String keyName = DEFAULT_ENCRYPTION_KEY_NAME,
int priority = 0,
}) {
return PVCacheHook(
eventString: 'selective_encryption_decrypt',
eventFlow: EventFlow.postProcess,
priority: priority,
actionTypes: [ActionType.get, ActionType.exists],
hookFunction: (ctx) async {
// Skip if no value
if (ctx.entryValue == null) return;
// Check if this entry has selective encryption
final nonces = ctx.runtimeMeta['_encryption_nonces'] as Map?;
if (nonces == null || nonces.isEmpty) return;
// Get encryption key
final key = encryptionKey ?? await getOrCreateEncryptionKey(keyName);
final cipher = AESCipher(key);
// Make a deep copy of the value to modify
final valueJson = jsonEncode(ctx.entryValue);
final modifiedValue = jsonDecode(valueJson);
// Decrypt each field
for (final entry in nonces.entries) {
final path = entry.key as String;
// Note: nonce is stored in metadata but not needed for decryption
// The IV generated from the nonce is embedded in the encrypted data
// Get the encrypted value at this path
final encryptedValue = getNestedValue(modifiedValue, path);
if (encryptedValue == null || encryptedValue is! String) continue;
try {
// Decrypt (IV is extracted from encrypted data)
final decrypted = cipher.decryptString(encryptedValue);
// Parse JSON back to original value
final fieldValue = jsonDecode(decrypted);
// Restore decrypted value
setNestedValue(modifiedValue, path, fieldValue);
} catch (e) {
// If decryption fails for this field, leave it as is
print('Warning: Failed to decrypt field "$path": $e');
}
}
// Update entry value with decrypted data
ctx.entryValue = modifiedValue;
},
);
}