SQL CLR标量UDF中的DES解密不起作用

Posted

技术标签:

【中文标题】SQL CLR标量UDF中的DES解密不起作用【英文标题】:DES Decryption in SQL CLR Scalar UDF not working 【发布时间】:2021-07-29 23:48:22 【问题描述】:

我们有一个使用 SQL Server 作为其后端的旧版应用程序。作为一些安全问题的一部分,它使用(单个)DES 使用硬编码到应用程序代码中的密钥和 IV 加密从用户收集的一些字段,然后 Base64 对加密字节进行编码,最后将该字符串存储在 varchar数据库中的列。在这一点上(可能是第一次编码时)非常不安全,以及有问题的设计/实现,但它就是这样。我的任务是在 SQL Server 中实现一个 CLR 用户定义的标量函数,它可以解密这种类型的数据。

作为概念证明,我创建了以下简短的控制台应用程序,以确保我了解 C# 中的 DES 解密过程:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

class My_Decrypt

    static void Main(string[] args)
    
        DES des = new DESCryptoServiceProvider();
        byte[] IV = BitConverter.GetBytes(0xFECAEFBEEDFECEFA);
        byte[] Key = Encoding.ASCII.GetBytes("password");

        foreach (string cipherText in args)
        
            byte[] cipherBytes = Convert.FromBase64String(cipherText);
            MemoryStream es = new MemoryStream(cipherBytes);
            CryptoStream cs = new CryptoStream(
                es,
                des.CreateDecryptor(Key, IV),
                CryptoStreamMode.Read
            );
            byte[] plainBytes = new byte[cipherBytes.Length];
            cs.Read(plainBytes, 0, plainBytes.Length);
            string plainText = Encoding.ASCII.GetString(plainBytes);
            Console.WriteLine(
                "'0' == '1'\nusing key = '2', IV = '3'\ndecrypts to '4' == '5'.\n",
                cipherText,
                BitConverter.ToString(cipherBytes),
                BitConverter.ToString(Key),
                BitConverter.ToString(IV),
                BitConverter.ToString(plainBytes),
                plainText
            );
        
    

编译后,我可以运行以下命令:

C:\>My_Decrypt.exe KDdSnfYYnMQawhwuaWo2WA==
'KDdSnfYYnMQawhwuaWo2WA==' == '28-37-52-9D-F6-18-9C-C4-1A-C2-1C-2E-69-6A-36-58'
using key = '70-61-73-73-77-6F-72-64', IV = 'FA-CE-FE-ED-BE-EF-CA-FE'
decrypts to '73-65-63-72-65-74-20-64-61-74-61-00-00-00-00-00' == 'secret data     '.

这看起来是正确的,我使用 openssl 进行了验证。

因此,确定了这一点后,我接下来尝试在 CLR 标量 UDF 中使用相同的代码,如下所示:

    [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true)]
    public static SqlString DES_Decrypt( SqlString CipherText, SqlBinary DES_Key, SqlBinary DES_IV )
    
        if (CipherText.IsNull || DES_Key.IsNull || DES_IV.IsNull)
            return SqlString.Null;
        string cipherText = CipherText.ToString();
        byte[] cipherBytes = Convert.FromBase64String(cipherText);
        MemoryStream es = new MemoryStream(cipherBytes);
        DES des = new DESCryptoServiceProvider();
        byte[] IV = (byte[]) DES_IV;
        byte[] Key = (byte[]) DES_Key;
        CryptoStream cs = new CryptoStream(
            es, des.CreateEncryptor(Key, IV), CryptoStreamMode.Read );
        byte[] plainBytes = new byte[cipherBytes.Length];
        cs.Read(plainBytes, 0, plainBytes.Length);
        cs.Close();
        string plainText = new ASCIIEncoding().GetString(plainBytes);
        return new SqlString(plainText);
    

但是,在编译、将程序集加载到 MSSQL、创建函数并尝试执行它之后,我得到了输出垃圾。因此,经过多次尝试完成这项工作(包括创建上面的 POC 应用程序),我将最后一行中的 return 替换为以下内容:

        throw new ArgumentException(String.Format(
            "\n'0' == '1'\nusing key = '2', IV = '3'\ndecrypts to '4' == '5'.",
            cipherText,
            BitConverter.ToString(cipherBytes),
            BitConverter.ToString(Key),
            BitConverter.ToString(IV),
            BitConverter.ToString(plainBytes),
            plainText
        ));

现在,当我在 MSSQL 中运行查询 SELECT dbo.DES_Decrypt(N'KDdSnfYYnMQawhwuaWo2WA==', CAST('password' AS binary(8)), 0xFACEFEEDBEEFCAFE); 时,我收到异常错误消息:

A .NET Framework error occurred during execution of user-defined routine or aggregate "DES_Decrypt": 
System.ArgumentException: 
'KDdSnfYYnMQawhwuaWo2WA==' == '28-37-52-9D-F6-18-9C-C4-1A-C2-1C-2E-69-6A-36-58'
using key = '70-61-73-73-77-6F-72-64', IV = 'FA-CE-FE-ED-BE-EF-CA-FE'
decrypts to '47-F7-06-E4-88-C4-50-5B-E5-4D-CC-C9-32-C7-8F-BB' == 'G????P[?M??2???'.
System.ArgumentException: 
   at DES_Decryptor.Decrypt(SqlString CipherText, SqlBinary DES_Key, SqlBinary DES_IV)
.

输入处理看起来不错:base64 解码字节匹配,传入的密钥和 IV 的二进制版本也是如此。所以,在我看来,调用 C# DES 解密例程时出现了问题来自 CLR 标量 UDF,但我很沮丧并且完全没有想法。关于这里可能出现什么问题的任何线索?

【问题讨论】:

您可能已经知道这一点,但该安全方案对有动机的攻击者(在使用 DES 和硬编码密钥之间)没有用处。 @EJoshuaS-ReinstateMonica - 是的,我想我在介绍中已经说得很清楚了。 【参考方案1】:

在两个地方都重现了您的结果并更改了一些内容但未更改输出后,我检查了两组代码以确保它们相同并发现了问题:

在对new CryptoStream() 的调用中,您在控制台应用程序中使用des.CreateDecryptor(Key, IV)(正确),但在SQLCLR 函数中使用des.CreateEncryptor(Key, IV)(不同且不正确)。将 SQLCLR 函数更改为使用 des.CreateDecryptor(Key, IV) 会产生预期的输出。

关于代码的一些一般说明:

    您应该使用Sql* 类型(即输入参数)的Value 属性,而不是调用ToString() 或强制转换。例如:
    string cipherText = CipherText.Value;
    byte[] IV = DES_IV.Value;
    byte[] Key = DES_Key.Value;
    
    您应该将MemoryStreamDESCryptoServiceProviderCryptoStream 的实例包装在using() 块中,以便正确清理外部资源。所有这 3 个都实现了 IDisposable 接口。 由于为 3 个输入参数中的任何一个传入的 NULL 将返回 NULL,因此您可以通过在创建 T-SQL 包装函数时设置一个选项来绕过需要在代码中处理它。当然,这不能通过 SSDT / Visual Studio 发布操作自动执行,但您可以手动处理部署,在这种情况下您自己发出 CREATE FUNCTION,或者您可以添加发布后部署脚本来执行 @987654337 @。因此,从代码中删除它:
    if (CipherText.IsNull || DES_Key.IsNull || DES_IV.IsNull)
        return SqlString.Null;
    
    并将以下内容添加到发布发布后的 SQL 脚本(SSDT / Visual Studio 至少会处理):
    ALTER FUNCTION [dbo].[DES_Decrypt](
        @CipherText [nvarchar](max),
        @DES_Key    [varbinary](8000),
        @DES_IV [varbinary](8000)
    )
    RETURNS [nvarchar](max)
    WITH EXECUTE AS CALLER, RETURNS NULL ON NULL INPUT
    AS EXTERNAL NAME [YourAssembly].[YourClass].[DES_Decrypt];
    
    WITH 子句中的 RETURNS NULL ON NULL INPUT 选项具有魔力;-)。你得到的NULLs 越多,效率就越高,因为 SQL Server 不需要调用代码,因为它已经知道答案。请记住,如果 any 输入为 NULL,则此选项返回 NULL,因此,如果任何输入参数预计将传入 NULL,则此选项将不起作用。

有关使用 SQLCLR 的更多信息,请访问我的网站:SQLCLR Info

【讨论】:

以上是关于SQL CLR标量UDF中的DES解密不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Pyspark 子字符串在 UDF 内部不起作用

spark read 在 Scala UDF 函数中不起作用

Excel VBA UDF 操作字符串不起作用

Golang:如何使用 DES、CBC 和 PKCS7 解密?

excel UDF在工作表中使用时不起作用

MS SQL Server 中的标量 UDF 在查询中仅返回一个值