簡體   English   中英

使用 EF Core 2.2 使用 SQL Server DECRYPTBYKEY 解密字符串

[英]Using EF Core 2.2 to decrypt a string using SQL Server DECRYPTBYKEY

基本上我有一個帶有加密字符串的 POCO 模型。 使用 EF 核心 2.2。

我們使用 DECRYPTBYKEY 使用 SYMMETRIC KEY 解密字符串。

我正在使用 DBSet.FromSQL 傳入調用開放對稱密鑰的 SQL 查詢,獲取包括解密值在內的數據,關閉對稱密鑰。

FromSQL 只允許你帶回一個實體而不是一個字符串本身。

我嘗試在模型上添加一個解密的字符串值,然后嘗試在 FromSQL 查詢中設置它。

當存儲庫 DBSet 中沒有任何 .Include 時,這實際上可以填充。

當 DBSet 確實有 .Include(在外鍵表上過濾 DBSet)時,會出現運行時錯誤,它抱怨解密的字符串不是數據庫表上的列 - 當然不是。 因此,擁有 .Include 是首先在基表上調用 SQL。

如果我將 [NotMapped] 屬性放在解密的字符串列上,那么當 FromSQL 查詢運行時,它不會填充它。

那么如何在不使用 [NotMapped] 但在 DBSet 上使用 .Include 的情況下使用這個解密的字符串列呢?

我已經添加了代碼,以便您可以更多地看到問題。 無法按照一個答案中的建議在模型上添加 Decrypt 的實現。 Decrypt 方法需要 DbSet 調用 FromSQL。 DbSet 源自 ConcreteRepository。 我也無法看到調用即席 SQL 查詢來返回 1 個字符串。

從原始 SQL (SQL Server) 中截取

    OPEN SYMMETRIC KEY {1} DECRYPTION BY PASSWORD = '{2}';

    SELECT  * , --other fields
            CONVERT(VARCHAR(60), DECRYPTBYKEY(A.Encrypted)) AS Decrypted
    FROM    dbo.Model A
    JOIN table2 t2 ON ...
    JOIN table3 t3 ON ...

   WHERE A.Id= 123

   CLOSE SYMMETRIC KEY {1};",

具體存儲庫

public async Task<IEnumerable<Model>> GetAllById(int id)
{

            var filteredSet = Set.Where(x => x.Id == id)
               .Include(x => x.Table2)
               .Where(x => x.Table2.IsSomething).ToList();

            var models = filteredSet.Select(f =>
                GetDecryptValue($"Id = {f.Id}");

            return models;

}


基礎資料庫

protected DbSet<TEntity> Set => _dbContext.Set<TEntity>();

public virtual TEntity GetDecryptValue(string filterCriteria)
        {
            string buildSelectStmt = $"SELECT TOP 1 Encrypted FROM Model";
            string buildSelectStmt2 = $"SELECT *, CONVERT(VARCHAR(MAX), DECRYPTBYKEY(@Value)) AS Decrypted FROM Model";

            buildSelectStmt = $"{buildSelectStmt} WHERE {filterCriteria}";
            buildSelectStmt2 = $"{buildSelectStmt2} WHERE {filterCriteria}";

            string sql = string.Format(@"
                DECLARE @Value NVARCHAR(MAX)
                SET @Value = ({0});
                OPEN SYMMETRIC KEY {1} DECRYPTION BY PASSWORD = '{2}';
                {3};
                CLOSE SYMMETRIC KEY {1};",
                buildSelectStmt, SymmetricKeyName, SymmetricKeyPassword, buildSelectStmt2);

            var result = Set.FromSql(sql);

            return result.FirstOrDefault();
        }

模型

    public partial class Model
    {
        public int Id { get; set; }
        public string Encrypted { get; set; }
        [NotMapped]
        public string Decrypted { get; set; }
    }

因此,正如我在評論中暗示的那樣,確實可以侵入 EFCore 的管道並使其執行自定義 SQL 函數。 這是一個功能控制台應用程序

我會預先聲明,因為我沒有密鑰,所以我使用DECRYPTBYPASSPHRASE函數對數據庫(請參閱我的 repo 鏈接中的 SQL 腳本)進行了DECRYPTBYPASSPHRASE 我也只安裝了.net core 2.1 盡管如此,我還是希望你能明白要點。 話雖如此,我將強調幾點,讓您進一步探索解決方案:

我最終像這樣定義了我的模型:

public partial class Model
{
    public int Id { get; set; }
    public byte[] Encrypted { get; set; } // apparently encrypted data is stored in `VARBINARY`, which translates to `byte[]`, so I had to tweak it here
    [NotMapped] // this is still required as EF will not know where to get the data unless we tell it (see down below)
    public string Decrypted { get; set; } // the whole goal of this exercise here
    public Table2 Table2 { get; set; }
}

鑒於我應該能夠只選擇值而不必進行第二次往返,我稍微修改了您的Concrete Repository代碼:

public IEnumerable<Model> GetAllById(int id)
{
    // you will need to uncomment the following line to work with your key
    //_dbContext.Database.ExecuteSqlCommand("OPEN SYMMETRIC KEY {1} DECRYPTION BY PASSWORD = '{2}';", SymmetricKeyName, SymmetricKeyPassword);
    var filteredSet = Set.Include(x => x.Table2)
        .Where(x => x.Id == id)
        .Where(x => x.Table2.IsSomething)
        .Select(m => new Model
    {
        Id = m.Id,
        //Decrypted = EF.Functions.DecryptByKey(m.Encrypted), // since the key's opened for session scope - just relying on it should do the trick
        Decrypted = EF.Functions.Decrypt("test", m.Encrypted),
        Table2 = m.Table2,
        Encrypted = m.Encrypted
    }).ToList();
    // you will need to uncomment the following line to work with your key
    //_dbContext.Database.ExecuteSqlCommand("CLOSE SYMMETRIC KEY {1};", SymmetricKeyName);
    return filteredSet;
}

現在,定義EF.Functions.Decrypt是這里的關鍵。 我們基本上必須做兩次:1) 作為擴展方法,所以我們可以在 LINQ 中使用 then 和 2) 作為 EF 表達式樹節點。 然后 EF 會做什么,對於它發現的每個方法調用,它檢查IMethodCallTranslator內部列表,如果它發現匹配 - 它將函數推遲到 SQL。 否則,它將必須在 C# 中運行。 因此,您將看到的所有管道基本上都需要將TranslateImpl注入該列表。

IMethodCallTranslator 本身

public class TranslateImpl : IMethodCallTranslator
{

    private static readonly MethodInfo _encryptMethod
        = typeof(DbFunctionsExtensions).GetMethod(
            nameof(DbFunctionsExtensions.Encrypt),
            new[] { typeof(DbFunctions), typeof(string), typeof(string) });
    private static readonly MethodInfo _decryptMethod
        = typeof(DbFunctionsExtensions).GetMethod(
            nameof(DbFunctionsExtensions.Decrypt),
            new[] { typeof(DbFunctions), typeof(string), typeof(byte[]) });

    private static readonly MethodInfo _decryptByKeyMethod
        = typeof(DbFunctionsExtensions).GetMethod(
            nameof(DbFunctionsExtensions.DecryptByKey),
            new[] { typeof(DbFunctions), typeof(byte[]) });

    public Expression Translate(MethodCallExpression methodCallExpression)
    {
        if (methodCallExpression.Method == _encryptMethod)
        {
            var password = methodCallExpression.Arguments[1];
            var value = methodCallExpression.Arguments[2];
            return new EncryptExpression(password, value);
        }
        if (methodCallExpression.Method == _decryptMethod)
        {
            var password = methodCallExpression.Arguments[1];
            var value = methodCallExpression.Arguments[2];
            return new DecryptExpression(password, value);
        }

        if (methodCallExpression.Method == _decryptByKeyMethod)
        {
            var value = methodCallExpression.Arguments[1];
            return new DecryptByKeyExpression(value);
        }

        return null;
    }
}

我最終實現了三個表達式存根: DecryptByKeyDecryptByPassphraseEncryptByPassphrase ,例如:

public class DecryptByKeyExpression : Expression
{
    private readonly Expression _value;

    public override ExpressionType NodeType => ExpressionType.Extension;
    public override Type Type => typeof(string);
    public override bool CanReduce => false;

    protected override Expression VisitChildren(ExpressionVisitor visitor)
    {
        var visitedValue = visitor.Visit(_value);

        if (ReferenceEquals(_value, visitedValue))
        {
            return this;
        }

        return new DecryptByKeyExpression(visitedValue);
    }

    protected override Expression Accept(ExpressionVisitor visitor)
    {
        if (!(visitor is IQuerySqlGenerator))
        {
            return base.Accept(visitor);
        }
        visitor.Visit(new SqlFragmentExpression("CONVERT(VARCHAR(MAX), DECRYPTBYKEY("));
        visitor.Visit(_value);
        visitor.Visit(new SqlFragmentExpression("))"));
        return this;
    }

    public DecryptByKeyExpression(Expression value)
    {
        _value = value;
    }
}

畢竟是非常微不足道的弦樂練習。 希望這能為您提供足夠的燃料來啟動和運行您的解決方案。

UPD EF Core 3 似乎仍然支持IMethodCallTranslator ,因此上述解決方案仍然適用。 UPD2 :確實可以。 在 github 上查看我更新的 repo

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM