如何在具有50,000个加密记录的表上进行搜索

时间:2017-03-21 16:21:39

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

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

我正在使用加密,以这种方式使用Entity Framework Code First。

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. DO Searches。
  3. 保持表现。

2 个答案:

答案 0 :(得分:1)

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

一些建议:

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

Shift搜索&amp;过滤到中间层

再次,权衡和&amp;可能会影响性能,但是一旦数据被解密,您就可以使用普通的旧LINQ

执行更复杂的过滤

您真的需要加密该列吗?

您的数据是否未归类为PHI,PII或类似内容?考虑不对其进行加密,您可以执行普通的SQL WHERE&amp; LIKE过滤

答案 1 :(得分:1)

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

<强> 1。加密数据库中的所有字段。

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

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

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

  • B部分:加密新数据

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

<强> 2。 DO Searches。

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

第3。保持业绩。

他们有很多方法,这取决于你是来自网络项目还是软件项目。我将谈谈可能涉及两者的解决方案。

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

  • 缓存

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

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

  • 暗示

示例:您的columnA,B和C为每个可能的值均匀分布数据。使用带有索引的列和每个Where的列应该可以快速响应。

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

  • 使用临时数据

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

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