We typically think of encryption as a cross-cutting concern or as a separate utility service that our applications use, not as something that’s an integral part of our application domain. That way of thinking doesn’t always hold though. In this short post, I’ll show you how I recently implemented support for encryption as a first-class citizen of my domain model.
The Domain
Almost every application contains some element that needs to be encrypted or hashed (a user’s credentials, perhaps?). In the past, I’ve never bothered encapsulating the encrypted value as its own separate data type. I’ll still encapsulate the logic for encrypting or hashing in the domain, but I typically place that logic in the owning entity. Here’s a User object from RageFeed as an example:
public class User { public virtual Guid Id { get; set; } public virtual string Username { get; set; } public virtual string Email { get; set; } public virtual string PasswordSalt { get; set; } public virtual string PasswordHash { get; set; } ...snip... public virtual void SetPassword(string password) { GenerateNewSalt(); PasswordHash = HashPassword(password); } public virtual bool IsThisTheUsersPassword(string password) { var hash = HashPassword(password); return hash == PasswordHash; } private string HashPassword(string password) { ...snip... } private void GenerateNewSalt() { ...snip... } ...snip... }
There’s quite a bit of logic related to securely storing the user’s password in this redacted snippet. While it’s fairly easy to test, it does make for a slightly less-cohesive domain model since there are now true business concerns mixed in with encryption concerns. Since it was really only this one entity and applied only to the credentials, I decided at the time to leave the code in the User object instead of refactoring it to somewhere else.
Encrypted Members as a First Class Citizen
On a recent application though, I found that I had several members that all needed to be encrypted. I decided I would encapsulate the encrypted members as first-class data types instead of rolling the encryption responsibility into the owning entity. The difference is subtle, but it greatly changes where behavior lives within the domain. The entities with encrypted members become simpler and more cohesive since they no longer contain encryption-related logic. That logic is now encapsulated separately from the owning entity and is far easier to reuse throughout the domain.
public class AccountDetails { ...snip... public virtual EncryptedString ReallySecretNumber { get; private set; } public virtual EncryptedString AnotherReallySecretNumber { get; private set; } ...snip... }
The actual data type doesn’t do much directly:
public class EncryptedString { public virtual string EncryptedValue { get; set; } public virtual string CertificateName { get; set; } public virtual string Key { get; set; } protected EncryptedString() { } public static EncryptedString Create(string value, IEncryptData encrypter) { var encryptionResult = encrypter.Encrypt(value); return new EncryptedString { EncryptedValue = encryptionResult.EncryptedText, CertificateName = encryptionResult.CertificateName, Key = encryptionResult.Key }; } public virtual string GetDecryptedValue(IEncryptData encrypter) { return encrypter.Decrypt(EncryptedValue, CertificateName, Key); } }
Instead, it depends on a separate service, IEncryptData, to provide the low-level encryption API it requires. This keeps the domain decoupled from the low-level methods, making it easier to test, extend, and maintain in the future.
Which is Better?
As always, the answer is “it depends.” The approach I took with RageFeed and Fail Tracker, where I embedded the logic within the owning entity, has never caused me issues. However, I chose to go a different route as soon as I had multiple members that required encryption. That feels like the right approach in this case.