有没有人找到了一种能够通过Entity Framework 4从数据库中提取加密值的好方法?
我有一个使用des_encrypt加密的MySql数据库,需要尽可能轻松地获取这些值,并且当然还要能够更新和插入它们。
我认为很奇怪EF中似乎没有内置支持此功能。即使是我们自己构建的ORM系统也支持此功能。我们只需为每个加密字段添加注释“encrypted”,ORM工具将在查询中添加des_decrypt(column)和des_encrypt(column)。
有人知道吗?
有没有人找到了一种能够通过Entity Framework 4从数据库中提取加密值的好方法?
我有一个使用des_encrypt加密的MySql数据库,需要尽可能轻松地获取这些值,并且当然还要能够更新和插入它们。
我认为很奇怪EF中似乎没有内置支持此功能。即使是我们自己构建的ORM系统也支持此功能。我们只需为每个加密字段添加注释“encrypted”,ORM工具将在查询中添加des_decrypt(column)和des_encrypt(column)。
有人知道吗?
这是@TheCloudlessSky提出的答案的一个实现示例。我认为它会帮助任何想知道如何实现它的人。
我正在使用一个现有的数据库,所以基本的模型类已经为我自动生成了。
自动生成的User.cs:
namespace MyApp.Model
{
public partial class User
{
public int UserId { get; set; }
public byte[] SSN { get; set; }
...
}
}
我创建了自己的User.cs。(注意它与自动生成的User.cs在相同的命名空间中,并且没有编译器错误,因为自动生成的User.cs被声明为partial class!此外,我的User.cs不能与自动生成的User.cs在同一文件夹中,因为文件名冲突!)
namespace MyApp.Model
{
public partial class User
{
public string DecryptedSSN { get; set; }
...
}
}
现在,每当我从我的DbContext中检索User时,我将看到所有在自动生成的类中定义的属性,以及在我的增强类中定义的属性。
这里是我的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;
}
}
}
}
现在你可能会想知道我从哪里得到了MyDbContext.Decrypt()方法?
这不是为您自动生成的。但是,您可以将此存储过程导入到自动生成的Model.Context.cs文件中。(这个过程在官方EntityFramework文章中有很好的文档说明:如何导入存储过程(Entity Data Model Tools),网址为:http://msdn.microsoft.com/en-us/library/vstudio/bb896231(v=vs.100).aspx)
如果您不知道最终结果应该是什么样子的,这是我的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
}
}
这是我的Decrypt存储过程的样子:
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
性能考虑
现在我已经展示了@TheCloudlessSky提供的答案实现,我想快速强调一些与性能相关的要点。
1) 每次检索用户对象时,将进行两次数据库访问而不是一次。第一次访问是为了检索对象; 第二次访问是为了解密SSN。如果您不小心,这可能会导致性能问题。
建议: 不要自动解密加密字段! 在上面展示的示例中,我在检索用户对象时解密了SSN。我那样做只是为了演示目的!问问自己是否真的需要每次检索用户时都获取SSN。如果可能,选择懒惰解密而不是急切解密!
2) 尽管我没有展示这个,但每次创建/更新用户对象时,也将进行两次数据库访问。第一次访问是为了加密SSN; 第二次访问是为了插入对象。同样,如果您不小心,这也可能会导致性能问题。
建议: 确保意识到此性能问题,但不要将加密和保存SSN委托为不同的方法。将其全部包含在一个操作中,否则您可能会忘记完全保存它。因此,创建/更新的建议与检索相反:选择急切加密而不是懒惰加密!
如果您在谷歌上搜索这个问题,并且想要一种简单的方法来解密单个列/行(而不是整个表/类),使用对称加密和EF,您可以通过以下两种(简单)方式之一实现。
第一种方法:创建一个存储过程来执行解密操作:
CREATE PROCEDURE [dbo].[Decrypt_Credential]
@User_Name varchar(50) = NULL
AS
BEGIN
OPEN SYMMETRIC KEY My_Key_01 DECRYPTION BY CERTIFICATE MyCertName;
SELECT CONVERT(varchar, DecryptByKey(Encrypted_Password)) FROM Application_Credentials WHERE User_Name = @User_Name;
CLOSE SYMMETRIC KEY My_Key_01;
END;
...然后直接在代码中调用存储过程,将结果作为字符串检索:
using (var context = new YourDatabaseContext())
{
var result = context.Database.SqlQuery<string>("Decrypt_Credential @user", new SqlParameter("user", TheUserName)).FirstOrDefault();
}
using (var context = new YourDatabaseContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
var sql = String.Format("OPEN SYMMETRIC KEY {0} DECRYPTION BY CERTIFICATE {1}", KeyName, CertName);
context.Database.ExecuteSqlCommand(sql);
sql = String.Format("SELECT CONVERT(varchar, DecryptByKey(Encrypted_Password)) FROM Application_Credentials WHERE User_Name = '{0}'", UserNameToDecryptCredsFor);
var result = context.Database.SqlQuery<string>(sql).FirstOrDefault();
sql = String.Format("CLOSE SYMMETRIC KEY {0}", KeyName);
context.Database.ExecuteSqlCommand(sql);
}
catch (Exception exp)
{
var x = exp.ToString(); //do something with exception
}
}
}
在将数据存入数据库之前,建议先进行加密并将其存储为二进制数据。这样,您就可以轻松地使用 EF 获取 byte[]
。
编辑:如果您使用存储过程来执行所有的 des_encrypt
、des_decrypt
以及 selects/inserts/deletes
操作,那么 EF 仍然可以为您完成映射工作吗?
MemberId
是加密的?你只需要解密那些被加密的列(比如密码...尽管你应该使用单向哈希)就可以了,如果 MemberId
没有被加密,你不需要解密它来进行“SELECT”。还有其他人在使用这个表吗?为什么不将其全部解密并创建一个新的非加密数据表呢? - TheCloudlessSky您可以使用AES加密(双向加密)。当您需要查询数据库时,您可以发送代表目标值的加密字符串。
您可以创建一个扩展程序来解密实体。
MyTableEntitiesSet.Where(c=>c.MyField == MySeekValue.Encrypt()).First().Decrypt();
这可以执行数据库查询。
请注意数据大小,加密数据会更大...
[Secure]
注释数据模型或将属性命名为Secure_SocialSecurityNumber
(Secure_
是关键部分),CipherDb会处理剩下的部分。public class Patient
{
public int Id {get; set;}
[Secure]
public string FullName {get; set;}
[Secure]
public string SocialSecurityNumber {get; set;}
}
<configuration>
<configSections>
<section
name="crypteronConfig"
type="Crypteron.CrypteronConfig, CipherCore, Version=2017, Culture=neutral, PublicKeyToken=e0287981ec67bb47"
requirePermission="false" />
</configSections>
<crypteronConfig>
<myCrypteronAccount appSecret="Get_this_from_http://my.crypteron.com" />
</crypteronConfig>
</configuration>