带有Entity Framework的加密列

时间:2010-07-21 12:16:36

标签: c# mysql entity-framework encryption entity

有人想出通过实体框架4从db中提取加密值的好方法吗?

我有一个MySql数据库,其中一些列使用des_encrypt加密,并且需要能够尽可能简单地获取这些值,当然还要更新并插入它们。

我觉得很奇怪,在EF中似乎没有建立对此的支持。即使是我们自己构建的ORM系统也支持这一点。我们只为加密的每个字段添加“加密”注释,ORM工具将在查询中添加des_decrypt(列)和des_encrypt(列)。

任何?

6 个答案:

答案 0 :(得分:14)

这是@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文章中有详细记录:如何:在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)每次检索用户对象时,都会对数据库进行2次旅行,而不是1次。检索对象的第一次旅行;解密SSN的第二次旅行。如果您不小心,这可能会导致性能问题。

推荐:不要自动解密加密字段!在上面显示的示例中,我在检索用户对象时解密了SSN。我这样做只是为了演示目的!问自己每次检索用户是否真的需要SSN。如果可能的话,选择懒惰的解密而不是急切的解密!

2)虽然我没有证明这一点,但每次创建/更新用户对象时,也会有2次访问数据库。加密SSN的第一次旅行;插入物体的第二次旅行。如果你不小心,这可能会导致性能问题。

推荐:注意此次性能提升,但不要将加密和保存SSN作为一种不同的方法。将其全部保存在一个操作中,否则您可能会忘记完全保存它。因此,创建/更新的建议与检索相反:选择对延迟加密的急切加密!

答案 1 :(得分:2)

IMO你应该在放入数据库之前加密并将其存储为二进制数据。然后,您可以轻松地使用EF获得byte[]

编辑:如果您使用存储过程为您执行所有des_encryptdes_decrypt以及selects/inserts/deletes,该怎么办?那么EF还会为你做映射吗?

答案 2 :(得分:2)

您可以使用AES加密(双向加密)。当您需要查询数据库时,您可以发送可以表示目标值的加密字符串。

您可以创建解密实体的扩展程序。

MyTableEntitiesSet.Where(c=>c.MyField == MySeekValue.Encrypt()).First().Decrypt();

这可以进行数据库查询。

注意数据大小,加密数据更大......

答案 3 :(得分:2)

您可以使用DIY /自己的加密安全性,但每位安全专家都会告诉您never, ever, do that。数据安全和加密中最难的部分实际上不是“AES”或某种算法。这是关键管理。迟早,你会面对这种野兽,而且方式更难。

幸运的是,有一个名为Crypteron CipherDb的工具可以解决这个问题。事实上,它超越了实体框架加密,还提供自动篡改保护,安全密钥存储,安全密钥分发,密钥翻转,密钥缓存,访问控制列表等。有一个免费社区版,只需几分钟即可添加到您的应用中。

与Entity Framework集成时,您只需使用[Secure]注释数据模型,或者将属性命名为Secure_SocialSecurityNumberSecure_是关键部分),CipherDb负责处理其余的。

例如,您的数据模型将是:

public class Patient
{
    public int Id {get; set;}

    [Secure]
    public string FullName {get; set;}

    [Secure]
    public string SocialSecurityNumber {get; set;}
}

你的web.config将是

<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>

建议保护您的web.config或以编程方式插入Crypteron API密钥(AppSecret)(documentation

您可以在https://github.com/crypteron/crypteron-sample-apps的GitHub上找到示例应用。

顺便说一句,免费版受益于商业产品,因此除了上述内容之外,您还可以在一个地方保护流,文件,对象,消息队列,NoSQL数据库等。

免责声明:我在那里工作,我们确实有一个免费的社区版本,任何人都可以使用(我们不会赚钱)。如果您觉得它很酷,请告诉我们,告诉您的朋友。如果您有预算,请获得商业许可。它帮助我们为每个人提供免费版本:))

答案 4 :(得分:2)

对于那些用Google搜索这个问题并寻找一种简单的方法来解密单个列/行(不是整个表/类),使用对称加密和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();
        }

第二种方法是通过数据库事务实现与存储过程基本相同的事情。 注意我完全清楚这个例子不符合sql注入保护,我遇到了参数化查询的一些问题,所以这就是我工作的例子。如果你走这条路线,你会想要使用参数化的查询;

 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
                }
            }
        }

答案 5 :(得分:-3)

在我的具体情况下,我需要加密信用卡号码,总是16个字符;所以我只是在get中添加了一个条件(如果lenght!= 16然后解密)并且在该属性的集合中(如果lenght == 16然后加密)。它起作用并且避免了我很多工作。