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

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

我们有一个使用 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,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,CryptoStreamMode.Read );
        byte[] plainBytes = new byte[cipherBytes.Length];
        cs.Read(plainBytes,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}'.",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_IV)
.

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

解决方法

在两个地方都重现你的结果并改变了几件事但没有改变输出之后,我检查了两组代码以确保它们相同并发现问题:

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

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

  1. 您应该使用 Value 类型(即输入参数)的 Sql* 属性,而不是调用 ToString() 或强制转换。例如:
    string cipherText = CipherText.Value;
    byte[] IV = DES_IV.Value;
    byte[] Key = DES_Key.Value;
    
  2. 您应该将 MemoryStreamDESCryptoServiceProviderCryptoStream 的实例包装在 using() 块中,以便正确清理外部资源。所有这三个都实现了 IDisposable 接口。
  3. 由于为 3 个输入参数中的任何一个传入的 NULL 将返回 NULL,因此您可以通过在创建 T-SQL 包装函数时设置选项来绕过需要在代码中处理它.当然,这不能通过 SSDT/Visual Studio 发布操作自动化,但您可以手动处理部署,在这种情况下,您自己发出 CREATE FUNCTION,或者您可以添加发布后部署脚本来执行ALTER FUNCTION。因此,从代码中删除它:
    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];
    
    RETURNS NULL ON NULL INPUT 子句中的 WITH 选项起到了神奇的作用;-)。您获得的 NULL 越多,效率就越高,因为 SQL Server 不需要调用代码,因为它已经知道答案。请记住,如果 any 输入为 NULL,则此选项返回 NULL,因此如果希望任何输入参数传入 NULL,则此选项不起作用。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)> insert overwrite table dwd_trade_cart_add_inc > select data.id, > data.user_id, > data.course_id, > date_format(
错误1 hive (edu)> insert into huanhuan values(1,'haoge'); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive> show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 <configuration> <property> <name>yarn.nodemanager.res