【问题标题】:How to search on a table with 50,000 records that are encrypted如何在包含 50,000 条加密记录的表上进行搜索
【发布时间】:2017-03-21 16:21:39
【问题描述】:

我有 SQL Server 2012,但我无法迁移到 SQL Server 2016。

我正在使用加密,以这种方式与实体框架代码优先。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace x.y.Api.Models
{
    [Table("Tbl_Naturalezas")] 
    public class Naturalezas: EncryptDecrypt
    {   
        public Naturalezas()
        {
            _locked = true;
        }

        [Key]
        public int idNaturaleza { get; set; }

        string _naturaleza;

        [StringLength(350)]
        public string naturaleza
        {
            get { return locked ? Decrypt(_naturaleza, ConfigurationManager.AppSettings["appKeyPassword"]) : naturaleza; }
            set { _naturaleza = IsEncrypted(value) ? value : Encrypt(value, ConfigurationManager.AppSettings["appKeyPassword"]) ; }
        }

        public virtual ICollection<Contactos> Contactos { get; set; }     

    }
}

继承自这个类:

public class EncryptDecrypt
    {
        public bool _locked;
        public const string EncryptedStringPrefix = "X";

        private const int Keysize = 256;
        private const int DerivationIterations = 1000;

        public string Encrypt(string atributoClase, string passPhrase)
        {
            string plainText = atributoClase.ToUpper();
            if (plainText != null)
            {
                var saltStringBytes = Generate256BitsOfRandomEntropy();
                var ivStringBytes = Generate256BitsOfRandomEntropy();
                var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
                using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
                {
                    var keyBytes = password.GetBytes(Keysize / 8);
                    using (var symmetricKey = new RijndaelManaged())
                    {
                        symmetricKey.BlockSize = 256;
                        symmetricKey.Mode = CipherMode.CBC;
                        symmetricKey.Padding = PaddingMode.PKCS7;
                        using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                        {
                            using (var memoryStream = new MemoryStream())
                            {
                                using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                                {
                                    cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                                    cryptoStream.FlushFinalBlock();
                                    // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
                                    var cipherTextBytes = saltStringBytes;
                                    cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                                    cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                                    memoryStream.Close();
                                    cryptoStream.Close();
                                    return Convert.ToBase64String(cipherTextBytes);
                                }
                            }
                        }
                    }
                }

            }
            else
            {
                return plainText; 
            }
        }

        public string Decrypt(string atributoClase, string passPhrase)
        {
            string cipherText = atributoClase.ToUpper();
            if (cipherText != null)
            {
                // Get the complete stream of bytes that represent:
                // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
                var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
                // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
                var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
                // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
                var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
                // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
                var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8)  2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8)  2)).ToArray();

                using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
                {
                    var keyBytes = password.GetBytes(Keysize / 8);
                    using (var symmetricKey = new RijndaelManaged())
                    {
                        symmetricKey.BlockSize = 256;
                        symmetricKey.Mode = CipherMode.CBC;
                        symmetricKey.Padding = PaddingMode.PKCS7;
                        using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                        {
                            using (var memoryStream = new MemoryStream(cipherTextBytes))
                            {
                                using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                                {
                                    var plainTextBytes = new byte[cipherTextBytes.Length];
                                    var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                    memoryStream.Close();
                                    cryptoStream.Close();
                                    return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                return cipherText;
            }        
        }

        private static byte[] Generate256BitsOfRandomEntropy()
        {
            var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
            using (var rngCsp = new RNGCryptoServiceProvider())
            {
                // Fill the array with cryptographically secure random bytes.
                rngCsp.GetBytes(randomBytes);
            }
            return randomBytes;
        }

        public void Lock()
        {
            _locked = true;
        }

        public void Unlock()
        {
            _locked = false;
        }

        public bool IsEncrypted(string atributosClases)
        {

            if (atributosClases != null)
            {
                if(atributosClases.Length > 50)
                {
                    return true;
                }
                else
                {
                    return false;
                }

            }
            else
            {
                return true;
            }

        }


    }

在 POST api 控制器中,我这样做:

// POST: api/Naturalezas
        [ResponseType(typeof(Naturalezas))]
        public IHttpActionResult PostNaturaleza(Naturalezas naturaleza)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            naturaleza.Unlock();
            db.Naturalezas.Add(naturaleza);
            db.SaveChanges();
            naturaleza.Lock();
            return CreatedAtRoute("DefaultApi", new { id = naturaleza.idNaturaleza }, naturaleza);
        }

我基于这篇博文进行了加密:

https://www.tabsoverspaces.com/233147-custom-encryption-of-field-with-entity-framework/?utm_source=blog.cincura.net

现在,这仅适用于一个表,但在另一个表中,我们有 20 个字段,所有字段都必须加密,但是我们需要能够使用 LIKE、= 等搜索这 20 个字段。

什么是最好的解决方案(引导我找到代码解决方案),以便能够:

  1. 在不使用 SQL 2016 Always Encrypted 的情况下加密数据库中的所有字段。
  2. 做搜索。
  3. 保持性能。

【问题讨论】:

  • 如果数据是加密的,而且密码不在数据库中,你就不能真正做like这样的操作。即使始终加密,您也无法做到like
  • 我找到了这个,但它没有说它是否适用于 LIKE。 blogs.msdn.microsoft.com/sqlsecurity/2015/08/27/…

标签: c# .net sql-server entity-framework linq


【解决方案1】:

这些问题可能会导致各种正确答案。我只会谈谈我会为每个问题做些什么

1.加密数据库中的所有字段。

我认为这个问题可以分为两部分:

  • A 部分:将当前数据更新为加密格式。

这部分是最简单的(但不是最短的),可以通过一个简单的 oneshot 项目读取每一行并将其更新为加密格式来解决。这部分可以是可选的,因为您的项目将加密数据与明文数据区分开来。

  • B 部分:加密您的新数据

这里有两个选择,具体取决于您解决第一个问题所需的时间。最好的办法是更改从 EF 项目中保存数据的方式。最糟糕的情况是在特定时间重新运行项目 A 部分。

2。 DO 搜索。

这里最简单和最安全的是加载所有数据并使用 Linq 请求它。

3.保持性能。

他们有很多方法可以解决这个问题,这取决于您是来自网络项目还是软件项目。我将只讨论可能涉及两者的解决方案。

但要小心!如果您不特别注意这一点,下面的每个解决方案都会增加很多安全问题。并且实施许多会要求更多的修补。

  • 缓存

最好的通用解决方案之一是拥有一些 $cache$。它可以使用SqlChangeMonitor (example) 直接缓存您的数据库,也可以使用Entity Framework Extended 从您的EF 项目中延迟一些时间。

也许您可以同时使用 SqlChangeMonitor 来更新您的 EF 缓存。

  • 提示

示例:A、B 和 C 列的每个可能值的数据均匀分布。在每个Where 中使用这些带有索引的列应该会给你一个快速的响应。

如何实现:使用内部选择或返回表的函数创建存储过程,并仅从结果中查询。您的 EF 项目需要重新设计,以便从存储过程的函数/结果集中进行查询。

  • 使用临时数据

在登录/进入时,您的用户可以将所有数据保存在带有未加密数据的临时索引表中,并在此临时表上进行查询。如果您开发了解决方案 1.A,这将很容易。您可以使用Keep Fixed PlanKeep Plan 选项以获得更好的性能。它会再次要求您重新设计 EF 项目(但这个项目应该更简单)。

警告:不要使用全局临时表。这将打破加密数据的重点。

【讨论】:

    【解决方案2】:

    在使用中间层解决方案加密静态数据时,您必须做出很多权衡,因为您目前正在这样做。虽然 Always Encrypted 确实让事情变得更容易一些,并且消除了应用程序中的自定义加密代码,但自定义加密仍然存在类似的限制,例如无法进行通配符 LIKE 过滤,因为它们的功能相似,即加密/解密没有发生在数据库级别。

    几个建议:

    基本过滤仍然有效 使用相同的加密密钥和盐时,您仍然可以执行正常的WHERE x = 'y' 类型的过滤。

    将搜索和过滤转移到中间层

    同样,权衡和可能会影响性能,但是一旦数据被解密,您就可以使用普通的旧 LINQ 执行更复杂的过滤

    您真的需要加密该列吗?

    您的数据是否未归类为 PHI、PII 或类似内容?考虑不加密就可以正常SQLWHERE&LIKE过滤

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-20
      • 1970-01-01
      • 2020-05-08
      • 2014-04-10
      • 1970-01-01
      • 2018-07-26
      • 1970-01-01
      相关资源
      最近更新 更多