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