繁体   English   中英

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

[英]How to search on a table with 50,000 records that are encrypted

我有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. 做搜索。
  3. 保持性能。

当前正在使用中间层解决方案加密静态数据时,您需要做出很多权衡。 尽管Always Encrypted确实使事情变得更加轻松,并且消除了应用程序中的自定义加密代码,但是自定义加密仍然存在类似的局限性,例如无法进行通配符LIKE过滤,因为它们的功能相似,而加密/解密不是发生在数据库级别。

一些建议:

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

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

同样,需要权衡取舍并可能影响性能,但是一旦解密了数据,就可以使用普通的旧LINQ执行更复杂的过滤

您真的需要加密该列吗?

您的数据没有归类为PHI,PII或类似的东西吗? 考虑不对其进行加密,您可以执行常规的SQL WHERELIKE过滤

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

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

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

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

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

  • B部分:加密新数据

这里有两个选择,具体取决于您要花多少时间来解决第一个问题。 最好的办法是更改从EF项目保存数据的方式。 最坏的情况是在给定的时间重新运行项目A部分。

2.进行搜索。

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

3.保持性能。

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

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

  • 高速缓存

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

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

  • 提示

示例:您的columnA,B和C的数据按其每个可能的值均匀分布。 将这些列与索引一起使用,并在每个Where都可以快速响应。

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

  • 使用临时数据

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

警告:请勿使用全局临时表。 这将破坏具有加密数据的意义。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM