SQL Server数据的Aes加密存入与解密取出

这篇具有很好参考价值的文章主要介绍了SQL Server数据的Aes加密存入与解密取出。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近在做winfrom的毕设,边做边学,由于这个东西折磨了我一天,所以写一篇学习心得记录一下这天的收获,顺便吐槽一下这个气人代码;

由于本人是个菜鸡所以如果有缺陷或不足的地方欢迎大佬指出。

另:项目环境为:VS2022 、SQL Server 2019;编程语言为:C#

一、目前已知的SQL Server的加密方法

通过学习,我了解到的加解密方法无非两种:

  1. 通过SQL Server自带的Aes加密对数据库的内容进行加解密;

  1. 通过VS自带的System.Security.Cryptography这个命名空间中的Aes类对数据进行加解密

吐槽:

  1. 我本人查到的目前就这俩,而且这俩性质不一样,但保护数据内容不被泄露的角度来说是一致的,我就放一块说了;

  1. 说实话SQL自带加密的资料实在是不多,资料书上的也没有提过SQL Server加密方面的内容,论坛博客上虽然有,但总感觉有些凌乱,作为一个数据库入门的小白而言实在不太友好,故这方面没有太大进展;

二、通过VS编程加密

  1. 密钥与向量:
  1. Aes作为传统的对称密码就不需要多介绍了(我也忘得快差不多了),密钥是通过自己随便编的一串字符经过扩展、字循环、字节代换等一系列变换最终成为一个真正意义上的32位(byte)密钥

  1. 实际代码层面上只需要用一串字符转换成byte数组后填充到32位就行,当然在VS提供的代码库中密钥是可以自己生成的

  1. 向量,这个问题在最基础的AES中是没有的,换句话说AES本身是没有向量这个概念的(我就说看微软文档的时候半天找不到向量的概念,最后翻书也不在加密算法的介绍里)

  1. 那向量是怎么来的呢?答案就是分组密码;当数据太多单次加密不过来时,就会把数据分成好几个块,分别进行加密;可密钥就一个,频繁使用破解几率就会增加,所以添加了向量这个概念,配合密钥加密;简单来说就是给分组密码的工作模式多加了一层防护(给加密上强度)

  1. 实际代码操作层面上,向量和密钥的制作是一样的(微软Aes库中密码本好像默认是CBC,但也有人编程采用的是EBC,算法向量长度都是16位),加解密时只需将密钥和向量一同放进函数中即可(简单到有种哆啦A梦道具的既视感)

  1. 库与环境与说明:
  1. 上文提到引用Aes库种的类和函数,必然要在使用前将命名空间引用过来,这个库包含很多加密算法的基类

using System.Security.Cryptography;//引用加密库的基类
  1. 代码内容改编自微软示例文档Aes 构造函数,会融合一部分我在学习过程中查阅的资料

  1. 代码出于我当时的需求编写,并不是简单的加解密存入取出SQL Server数据库中内容,但我会尽力挑出符合的部分说明,如有无法剔除的会特意标注;

  1. 最后再次强调本项目环境:编译器是VS2022 、数据库为SQL Server 2019、编程语言为:C#、创建的项目是WinFramework(具体项目创建和使用请查资料,B站,CSDN上都有详细的介绍)

  1. 密钥的自动生成:
    //using作用:
    //(一).作为指令,用于为命名空间创建别名或导入其他命名空间中定义的类型。
    //(二).作为语句,用于定义一个范围,在此范围的末尾将释放对象。
    //这里using作用是第二条
    using (Aes myAes = Aes.Create())//使用Aes类 创建对象myAes,对象内容为Aes.Create()即创建一个对称加密的对象
    {
        byte[] key = myAes.Key;//可以直接在函数中引用(如下文),此处作为说明单独提出来了;
        byte[] kIV = myAes.IV;//同上
        //myAes.Key和myAes.IV分别作为密钥和向量存在,此时密钥和向量已经生成,可以看到,密钥和向量都是以byte数组形式存在的
        //在引用时可直接使用例如:
        byte[] encrypted = EncryptStringToBytes_Aes(original, Key, kIV);
        //其中EncryptStringToBytes_Aes是自己编写的函数,形参分别是:原文,密钥,向量
    }
  1. 密钥文件的读取与文件格式的转换

出于任务需要,文件需要将密钥写入txt文件,使用时再取出,这里有需要特别注意的点:

  1. 格式问题:如下文代码所示,创建了一个文件读取对象,并将读取的文件存入字符串中

        public string ReadTXT(string FilePath)
        {
            try
            {
                //创建文件读取对象实例,用于读取文件,形参分别为文件路径和文件编码格式,其中Default是ANSI编码格式
                StreamReader readstring = new StreamReader(FilePath, System.Text.Encoding.Default);
                string myfile;
                myfile = readstring.ReadToEnd();
                //此句读取到尾时,已把光标指针移动到文件结尾,读取文件的编码格式只要不是UTF-8其他都行,否则在显示时会出现乱码,UTF8编码格式不影响代码运行
                //SQL server 默认格式gbk 20936格式
                readstring.Close();
                return myfile;
            }
            catch
            {
                return null;
            }
        }
  1. 字符串从txt文件读取时问题:如下文代码所示,这是一个button控件的内容,其中无关内容已被删除,在从txt文件中读取存入的密钥如上文的myAes.Key和myAes.IV时,其长度从原本的32、16变成了31、15(编码格式都是ANSI即System.Text.Encoding.Default.GetBytes()形式获取byte数组)

        private void button4_Click(object sender, EventArgs e)//加密
        {
            //C#只能采用固定路径读取文件
            //tip: @可消除转义,如@"\"可以使"\"成为普通的字符
            byte[] rkey = new byte[32];
            byte[] rIV = new byte[16];
            byte[] key = System.Text.Encoding.Default.GetBytes("这里是读取的密钥字符串");//转换回byte是31位
            key.CopyTo(rkey, 0);//复制不足添0,成32位
            byte[] kIV = System.Text.Encoding.Default.GetBytes("这里是读取的向量字符串");//转换回byte是15位
            kIV.CopyTo(rIV, 0);//复制不足添0,成16位
            //经过填充新的变量key、kIV可以作为密钥和向量使用
        }
  1. SQL Server数据库取出时的编码问题:这是这篇文章的核心也是最关键的地方(我为了这瓶醋,包的这顿饺),数据库在加密的时候并不困难,只要按照微软示例文档Aes 构造函数的方法将密钥,向量和提取的字符串放进去就行,如下图所示:(抹去了与加密、编码无关内容)

        private void button1_Click(object sender, EventArgs e)//点击后进行加密的模块
        {
            using (Aes EAes = Aes.Create())
                {
                    //EncryptStringToBytes_Aes是自定义的加密函数,其内容就是微软文档的内容,形参分别是明文,密钥,向量
                    byte[] encrypted = EncryptStringToBytes_Aes(original, rkey, rIV);
                    MessageBox.Show("此时存入的数据流的长度为:" + encrypted.Length.ToString());
                    //错误示范
                    string temp = System.Text.Encoding.Default.GetString(encrypted);
                    //打开数据库将数据存入表中...    
                }

        }

可以看到这段代码采用的字符串转换是通过System.Text.Encoding.Default.GetString()方式进行的,此时可以看到存入的字符串长度为:

SQL Server数据的Aes加密存入与解密取出

解密本应同理,下面附上解密代码:(抹去了与解密、编码无关内容)

        private void button2_Click(object sender, EventArgs e)//解密
        {
            string dc = string.Empty;//此处dc是从数据库取出的字符串
            //...略去将数据库内容取出并赋值给dc的过程
            //如下行所示此时dc已经是数据库中取出的字符串了,其中de是通过System.Text.Encoding.Default.GetBytes()得到dc的byte流
            //错误示范
            byte[] de = System.Text.Encoding.Default.GetBytes(dc);
            MessageBox.Show("此时取出的数据流的长度为:" + de.Length.ToString());
            //下面DecryptStringFromBytes_Aes是自定义的解密函数,其内容就是微软文档里的内容
            //de是密文的比特流,rkey是密钥,rIV是向量,因为是Aes对称加密,所以密钥相同;
             string roundtrip = DecryptStringFromBytes_Aes(de, rkey, rIV);
            
        }

但解密的时候就会出现错误!如图:

SQL Server数据的Aes加密存入与解密取出

会出现这个无法解决的问题;

查看转换成byte流的长度是就会发现:

SQL Server数据的Aes加密存入与解密取出

存入前它的长度为16,取出后实际上它的byte长度是14,这是完全不符的

存取前后的长度都不一致,也就会造成读取的字符串不是完整的块问题了,从这个思路出发其实有两个疑点(其实马后炮的角度看问题是第六个,这里先不提)

1数据库存入/取出时会将某些数据抹去,取出时这些数据就消失了,造成了数据的不完整

2数据编码时会出现问题导致前后内容不一致

这里第二条最好验证,先验证第二条:

        private void button1_Click(object sender, EventArgs e)
        {
            string str = "123456";
            textBox1.Text = str;
            byte[] decrypt = System.Text.Encoding.Default.GetBytes(str);
            MessageBox.Show(decrypt.Length.ToString());
            string str2 = System.Text.Encoding.Default.GetString(decrypt);
            textBox2.Text = str2;
            byte[] bytes = System.Text.Encoding.Default.GetBytes(str2);
            MessageBox.Show(bytes.Length.ToString());
        }

可以看到字符串同样编码转到byte数组,再从byte数组转回字符串,再转回来,byte长度都是不变的,且字符串的值也不会更改;

SQL Server数据的Aes加密存入与解密取出
SQL Server数据的Aes加密存入与解密取出

现在疑点2排除了,要证明疑点1就好办了,既然编码转换不会改变byte流,那我直接存入比特流就可以了(由于技术问题我实际是byte数组转成字符串存入),于是将数据库对应列改成varbinary二进制类型(字符串通过SQL数据库自带的类型转换CAST转换成二进制);

SQL Server数据的Aes加密存入与解密取出

这里我的逻辑是:byte数组转换字符串没问题,通过字符串经过SQL自带的类型转换成二进制数据也就是流的形式,都是二进制数,就没有存入\取出丢失数据的问题了吧?(如果真的有,SQL Server就别干了)

事与愿违;结果都是“不是完整的数据块”(不管是问题还是截图结果都一样,再复现过程太麻烦就不了)

此时两个疑点都证明了凶手不是他们(最起码在这个bug里是这样的)

注:按照时间线来说不论是疑点1还是疑点2都是后面的事;当遇到问题时,最简单的办法就是百度一下,有帖子说是:

(这是第一时间尝试的方案)在加密和解密时,都将输入文件的大小写入(在CBC模式下)到创建文件的前4个字节中。因此,解密时,必须忽略前4个字节

这个我试了:除了第三行是自己加的,剩下都是官网文档的内容,将流从第4个位置开始读(从0数,也就是第五个byte开始)

                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    msDecrypt.Seek(4, SeekOrigin.Begin);
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }

结果是一致的,依旧是数据不是完整的块,由于测试的数据流有16位,我从0开始试到了16,包括用空内容去加密,也是从0试到了14(空的内容加密后就14位),除去12位bug有变化,剩下没有区别,超出16位的后面就没有内容了;

另:(叠甲)我不是说帖子的内容有误,只是当前的现实排除了“在加密和解密时,都将输入文件的大小写入(在CBC模式下)到创建文件的前4个字节中”的状况

(第二时间方案)按照上一个思路我怀疑是编码字符串的时候有像半个字符,或者有

“ ”、"\n"之类的内容被丢弃的内容,于是通过代码填充了内容二进制的0

            byte[] dz = new byte[16]; 
            byte[] de = System.Text.Encoding.Default.GetBytes(dc);//错误示范
            de.CopyTo(dz, 0);
            MessageBox.Show("此时取出的数据流的长度为:" + de.Length.ToString());
            using (Aes EAes = Aes.Create())
                {
                    //DecryptStringFromBytes_Aes自己定义的解密函数,内容和官方文档一致
                    string roundtrip = DecryptStringFromBytes_Aes(dz, rkey, rIV);
                }
SQL Server数据的Aes加密存入与解密取出

可以看到确实是16位,但结果就是如下图所示,填充无效;

SQL Server数据的Aes加密存入与解密取出

由于学识有限,填充方面的思路止步于此,由此拓展引发了之后的疑点1和疑点2去实践;

吐糟:说实话,这折腾了一个通宵的时间,当时以及气迷心了,开始走一些邪门歪道了,包括数据库自带的加密就是上头后收集到的方案

相继失败后,无意中发现了CSDN这个帖子:SQLServer CLR 函数AES加密解密代码

这篇文章确实做到了数据库内容的加解密存取,其实经过仔细研究就可以发现,这篇文章的代码和官方文档在关键步骤上是一致的,唯一不同的是在字符串编码时,采取的是

Convert.ToBase64String()//编码成字符串
Convert.FromBase64String()//编码成byte[]
System.Text.Encoding.Default.GetString()//编码成字符串
System.Text.Encoding.Default.GetBytes()//编码成byte[]

这两个编码方式是和官方文档实例唯一有出入的关键点,所以将官方文档的编码方式替换后,代码完美完成了自己的任务,顺利完成了加解密;(我当时想到了用Convert转换,怎么就没想到用ToBase64String()和FromBase64String()呢?)

现在复现这个问题的过程中,可以看到下图所示这是用System.Text.Encoding.Default.GetString(),编码格式的存入,Convert.FromBase64String(),编码格式取出的异常报告;

SQL Server数据的Aes加密存入与解密取出

说明数据库只接受Base-64字符的表示;还记得在小标题“密钥文件的读取与文件格式的转换”的“格式问题”中提到过编码显示的问题吗?类推下来很有可能就是不同文件存入时的编码格式不同导致的问题;

在使用效果相同的情况下,其内在核心不同造成了内部数据的不同;

其实进入数据库可以发现两个编码本质上的不同:

SQL Server数据的Aes加密存入与解密取出

第一个是System.Text.Encoding.Default.GetString()模式下的存入,后面是Convert.ToBase64String()模式下的存入;在数据库自己加密数据时也是后面2-4数据的大致加密样子(最起码不是第一行中的乱码),第一个是看似加密成功的失败品(因为解密不了);这样子更能确定bug是编码问题导致的;

注:官方文档也是可以正常运行的,即System.Text.Encoding.Default.GetString()\System.Text.Encoding.Default.GetBytes()编码方式只要不存入数据库,其加解密数据通过变量传递是可以完成解密的

三、总结:

  1. 不同文件有不同格式,要注意相同效果下其本质数据格式的不同;

  1. 数据库采用的Base-64编码,所以最好采用

Convert.ToBase64String()

Convert.FromBase64String()

来解决问题

  1. 相同模式下编码是不改变内容的

  1. 数据库存储转变变量时用CAST和CONVERT(此CONVERT是SQL的函数)

  1. AES只有一个密钥,密码本模式需要向量

  1. 多搜,多试,方法总比想法多;

  1. 补充一条System.Text.Encoding.Default.GetString()\System.Text.Encoding.Default.GetBytes()

在存入、读取txt时也会造成数据丢失;

四、附官方文档(+个人内容翻译+笔记)

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

namespace Aes_Example
{
    class AesExample
    {
        public static void Main()
        {
            string original = "Here is some data to encrypt!";//明文

            // Create a new instance of the Aes class.  //创建Aes的新实例类
            //This generates a new key and initialization vector (IV). // 这将生成一个新的密钥和初始化向量IV(微软Aes库采用密码本CBC模式)
            using (Aes myAes = Aes.Create())
            {
                //myAes.Key为bite类型
                // Encrypt the string to an array of bytes. //将字符串加密为字节数组
                byte[] encrypted = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV);

                // Decrypt the bytes to a string.//将字节解密为字符串
                string roundtrip = DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV);

                //Display the original data and the decrypted data. //显示原始数据和解密数据 这两行在编程过程中没有,文档演示采用的
                Console.WriteLine("Original:   {0}", original);
                Console.WriteLine("Round Trip: {0}", roundtrip);
            }
        }
        static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
        {
            // Check arguments.//检查冲突
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");//plainText是带入的string变量,原形是原文;这里是抛出问题,出bug的时候用的
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encrypted;

            // Create an Aes object //创建Aes对象
            // with the specified key and IV.//使用指定的key和IV
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;//这里的Key就是设定的密钥
                aesAlg.IV = IV;//同理向量

                // Create an encryptor to perform the stream transform.//创建一个加密器来执行流转换
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);//ICryptoTransform接口,用来定义加密转换的基本操作

                // Create the streams used for encryption.//创建用于加密的流
                using (MemoryStream msEncrypt = new MemoryStream())//MemoryStream创建一个流,其后备存储为内存
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))// CryptoStream定义将数据流链接到加密转换的流
                    //第一个参数是上一行创建的流;第二个参数是创建的接口包含密钥和向量;第三个参数指定了加密流的模式 (读/写)
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))//将字符写入到特定的编码中
                        {
                            //Write all data to the stream.//将所有数据写入流
                            swEncrypt.Write(plainText);//参数为明文
                        }
                        encrypted = msEncrypt.ToArray();//bite类型承接加密后的明文;msEncrypt.ToArray(),将流内容写入字节数组
                    }
                }
            }

            // Return the encrypted bytes from the memory stream.//从内存流中返回加密的字节
            return encrypted;
        }
        //加密和解密的方法一样,解密就不翻译了;
        static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decryptor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }
            }

            return plaintext;//还原的内容
        }
    }
}

五、引用

引用的帖子链接文章来源地址https://www.toymoban.com/news/detail-508601.html

https://blog.csdn.net/kk185800961/article/details/53083051
https://www.f2er.com/1651370.html

到了这里,关于SQL Server数据的Aes加密存入与解密取出的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用java语言写一个AES算法,使用AES(CBC模式)对数据进行加密或解密。加解密用到的密钥(Key)和密钥偏移量(IV),代码实例类编写。

    以下是一个使用Java编写的AES算法实例,使用AES(CBC模式)对数据进行加密和解密。代码中包括了生成随机密钥和密钥偏移量的方法。 java Copy code import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.InvalidAlgorithmParameterException; import

    2024年02月07日
    浏览(61)
  • php对称加密AES加密解密

    AES-128-ECB和AES-256-CBC是两种常见的AES加密模式,它们在加密方式和安全性上有以下区别: 加密方式: AES-128-ECB:ECB(Electronic Codebook)模式是最简单的AES加密模式,它将数据分成固定大小的块,每个块独立加密。这意味着相同的明文块将始终加密为相同的密文块,因此ECB模式不

    2024年02月09日
    浏览(62)
  • 前端AES加密解密

    前端经常会遇到这种接口和参数需要加密的情况,目前用的最多的就是aes加密了。以下就是操作步骤。以下方式适用各个前端框架,请自行根据框架修改对应语法 1.安装 CryptoJS 这个库就是用来加密的核心,直接打开项目终端,输入 npm install crypto-js --save 2. 新建 crypto.js 在 @/

    2024年02月13日
    浏览(59)
  • Android AES加密解密

     AES算法全称Advanced Encryption Standard。它是典型的“对称加密算法”,主要作用是保证私密信息不被泄露。 一、密钥 密钥是AES算法实现加密和解密的根本,因为 它对明文的加密和解密需要使用同一个密钥 。 AES支持三种长度的密钥:128位,192位,256位。 二、填充 AES算法在对明

    2024年02月04日
    浏览(59)
  • kotlin aes 加密解密

    aes算法对key的字节数有要求 所以对输入的key要做填充处理

    2024年02月07日
    浏览(51)
  • uniapp AES加密解密

    uniapp里我知道的有两种aes加密解密方式。 一、引入crypto-js 1.需要在uniapp项目根目录里,打开命令行,执行如下命令: 2.在项目根目录,创建一个utils文件夹,并创建一个aes_endecrypt.js文件  3.在main.js文件中,引入方法,并注册为全局方法 4.在页面中使用aes加密,解密 二、第二

    2024年02月13日
    浏览(48)
  • java:AES加密和解密

    1 前言 对称加密,即单秘钥加密,指加密和解密的过程中,使用相同的秘钥,相比于非对称加密,因仅有一把钥匙,故而速度更快,更适合解密大文件(常见于如视频文件的加密解密中)。AES算法就属于对称加密中的一种。 2 使用 依赖引入: AES加密与解密的工具类封装: 执

    2024年02月11日
    浏览(56)
  • 在线AES加密/解密工具

    在线AES加密/解密工具 http://lzltool.com/AES http://lzltool.com/AES

    2024年02月12日
    浏览(46)
  • Golang里的AES加密、解密

    CBC/ECB/CFB 解密方法 输出结果: 输出结果: 输出结果: 首先使用openssl生成公私钥

    2024年02月11日
    浏览(52)
  • Python学习笔记——AES 加密/解密

    AES,高级加密标准。目前比较流行的对称加密算法。是一种对称加密算法,即加密和解密都用相同的密钥。 AES只是个基本算法,实现AES有几种模式,主要有ECB、CBC、CFB等几种模式。CBC模式中还有一个偏移量参数IV。 AES加密有AES-128、AES-192和AES-256三种,分别对应三种密钥长

    2023年04月15日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包