Este es un ejemplo de implementación de la respuesta propuesta por @TheCloudlessSky. Pensé que ayudaría a cualquiera que se preguntara cómo implementarlo.
Estaba trabajando con una base de datos existente, por lo que la clase de modelo básico se generó automáticamente para mí.
User.cs generada automáticamente:
namespace MyApp.Model
{
public partial class User
{
public int UserId { get; set; }
public byte[] SSN { get; set; }
...
}
}
he creado mi propia User.cs. (Tenga en cuenta que está en el mismo espacio de nombres que los User.cs generados automáticamente y no hubo errores en el compilador porque el User.cG generado automáticamente se declaró como clase parcial! Además, mis propios User.cs no pueden estar en la misma carpeta que User.cs generados automáticamente a causa de conflicto de nombre de archivo!)
namespace MyApp.Model
{
public partial class User
{
public string DecryptedSSN { get; set; }
...
}
}
Ahora cada vez que tuviera que recuperar usuario de mi DbContext, voy a ver todas las propiedades definidas en la clase de auto-generado, así como los definidos en mi clase mejorada
Aquí hay una implementación de mi UserRepository.CS:
namespace MyApp.Model
{
public interface IUserRepository
{
User Get(int userId);
...
}
public class UserRepository : IUserRepository
{
public User GetById(int userId)
{
using (var dataContext = MyDbContext())
{
var user = dataContext.Users.Find(u => u.UserId == userId);
var decryptedSSNResult = dataContext.Decrypt(u.SSN);
user.DecryptedSSN = decryptedSSNResult.FirstOrDefault();
return user;
}
}
}
}
Ahora usted puede preguntarse cómo/dónde he llegado MyDbContext.Decrypt() desde?
Esto NO se genera automáticamente para usted. Sin embargo, puede importar este procedimiento almacenado en su archivo Model.Context.cs generado automáticamente. (Este proceso está muy bien documentado en el artículo oficial de EntityFramework: Cómo: Importar un procedimiento almacenado (Entity Data Model Tools) http://msdn.microsoft.com/en-us/library/vstudio/bb896231(v=vs.100).aspx)
Por si acaso no sabe cómo debería ser el resultado final, aquí está lo que se genera automáticamente en mis Model.Context.cs:
namespace MyApp.Model
{
// using statements found here
public partial class MyDbContext : DbContext
{
public MyDbContext()
: base("name = MyDbContext")
{ }
public virtual ObjectResult<string> Decrypt(byte[] encryptedData)
{
var encryptedDataParameter = encryptedData != null ?
new ObjectParameter("encryptedData", encryptedData) :
new ObjectParameter("encryptedData", typeof(byte[]));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<string>("Decrypt", encryptedDataParameter);
}
// similar function for Encrypt
}
}
Así es como mi Descifrar procedimiento almacenado se parece a:
CREATE PROCEDURE decrypt
@encryptedData VARBINARY(8000)
AS
BEGIN
OPEN SYMMETRIC KEY xxx_Key DECRYPTION BY CERTIFICATE xxx_Cert;
SELECT CAST(DECRYPTIONBYKEY(@encryptedData) AS NVARCHAR(MAX)) AS data;
CLOSE ALL SYMMETRIC KEYS;
END;
GO
Consideraciones de rendimiento
Ahora que le he mostrado una implementación de respuesta dada por @TheCloudlessSky, me gustaría resaltar rápidamente algunos puntos relacionados con el rendimiento.
1) Cada vez que recupera un objeto de usuario, se realizan 2 viajes a la base de datos en lugar de 1. Primer viaje para recuperar el objeto; segundo viaje para descifrar el SSN. Esto puede causar problemas de rendimiento si no tiene cuidado.
Recomendación: ¡NO descifre automáticamente los campos encriptados! En mi ejemplo que se muestra arriba, descifré el SSN cuando estaba recuperando un objeto de usuario. ¡Lo hice solo con fines de demostración! Pregúntese si realmente necesita SSN cada vez que se recupera el usuario. Si es posible, ¡elija descifrado perezoso sobre desencriptación ansiosa!
2) Si bien no lo he demostrado, cada vez que crea/actualiza un objeto de usuario, también se realizarán 2 viajes a la base de datos. Primer viaje para cifrar SSN; segundo viaje para insertar el objeto. De nuevo, esto puede causar problemas de rendimiento si no tiene cuidado.
Recomendación: Sea consciente de este rendimiento pero no delegue el cifrado y el guardado de SSN como un método diferente. Manténgalo todo en una sola operación, de lo contrario, puede olvidarse de guardarlo por completo. Por lo tanto, la recomendación para crear/actualizar es lo opuesto a la recuperación: ¡elija un encriptado ansioso sobre un cifrado lento!
Parece una buena solución y probablemente lo haría si crease una nueva base de datos. La cuestión es que la base de datos es bastante grande y es utilizada por toneladas de proyectos, por lo que sería un gran trabajo revisar el código y cambiar esto. – Andreas
@Andreas - Ver mi edición anterior. – TheCloudlessSky
Gracias por la sugerencia. Esto parecía una muy buena idea y lo probé. lamentablemente, si quiero poder hacer consultas LINQ en todos mis datos descifrados de la tabla, el procedimiento almacenado debe ejecutarse primero. Esto lleva mucho tiempo, ya que son más de 250 000 filas con 5 columnas para descifrar. Haciendo esto: context.AllMembers(). Where (x => x.MemberId == 1) llevará demasiado tiempo. Claro que podría hacer un SP que tome un argumento de memberid, pero ¿qué sucede si quiero buscar en, por ejemplo primer nombre con LINQ? Quizás estoy perdiendo algo importante aquí ... – Andreas