In case the cryptoType
value you get from the database is constant during the lifetime of the application (which means, if you want to change it, you're fine with restarting the application), this means that the cryptoType is a configuration value and you can simply wire your application as you described:
var cryptoType = //get param
if (cryptoType = "SHA1")
{
services.AddTransient<ICrypto, CryptoSHA1>();
}
else if (cryptoType = "MD5")
{
services.AddTransient<ICrypto, CryptoMD5>();
}
If however you need to swap implementations dynamically (which I find very unlikely in your specific case, but let's assume for the sake of argument), the solution is to implement a proxy and wrap the real implementations. Example:
public interface DatabaseCryptoSelectorProxy : ICrypto
{
private readonly CryptoSHA1 sha;
private readonly CryptoMD5 md5;
public DatabaseCryptoSelectorProxy(CryptoSHA1 sha, CryptoMD5 md5) {
this.sha = sha;
this.md5 = md5;
}
public string HashPassword(string plainPassword) =>
GetCrypto().HashPasswords(plainPassword);
public bool VerifyHashedPassword(string hashedPassword, string providedPassword) =>
GetCrypto().VerifyHashedPassword(hashedPassword, providedPassword);
private ICrypto GetCrypto() {
var cryptoType = // get param
if (cryptoType = "SHA1") return this.sha;
if (cryptoType = "MD5") return this.md5;
throw new InvalidOperationException("Unknown cryptotype: " + cryptotype);
}
}
This proxy has a few clear advantages:
- It makes the consumers of
ICrypto
oblivious to the fact that some complex dispatching is happening based on some data from a database.
- It prevents having to execute this query during object graph construction, since this would make this process unreliable and hard to verify.
Some notes though about your design around password hashing from a security perspective. I don't see any strong reason to switch from crypto methods the way you are doing, and especially not the switch to algorithms like MD5. Instead, I advise using PBKDF2 in the form of Rfc2898DeriveBytes. An example of how to do this can be shown here. By concatenating the number of hash iterations to the hashed password (for instance by simply doing + "|" + iterations
), you can later on increase the number of used iterations by keeping up with the industry standard and it allows you to automatically rehash the user's password on login if his number if you detect the number of used iterations an old value.
Additionally, if you think that you ever need to move away from PBKDF2, you can prefix the hash with the used algorithm, this way you can again use a proxy that passes a hashed password on to the right implementation based on the algorithm-prefix. By storing the algorithm in the password hash in the database, you can migrate transparently without having to convert all existing passwords at once (which is impossible, because you can't decrypt a hashed password).