ASP.NET Core 数据加解密的一些坑

点击蓝字

关注我

ASP.NET Core 给我们提供了自带的Data Protection机制,用于敏感数据加解密,带来方便的同时也有一些限制可能引发问题,这几天我就被狠狠爆了一把2_05.png

640?wx_fmt=gif

我的场景

我的博客系统有个发送邮件通知的功能,因此需要配置一个邮箱账号,让程序去用该账号像管理员或用户发送邮件。这就牵涉到如何安全存储账户密码的问题了。作为有节操的程序员,我们当然不能像国内众多平台一样存储明文密码到数据库。在这个场景里,我们也没法用HASH存储密码,因为发邮件是系统后台自己完成的,不会要求用户输入密码进行HASH运算之后与数据库存储的HASH对比。因此,我首先想到的就是用AES这样的对称加密算法,在数据库里存储加密后的密文,由程序根据Key去解密,然后使用该账号发送邮件。

不想重复造轮子

在设计一个功能之前,我通常会先查阅资料,看看是否有框架自带的功能可以完成需求。于是,ASP.NET Core自带的Data Protection引起了我的注意。

冗长的官方文档大家可以自己去看,这里我做一下总结:

使用Data Protection API的好处在于:

  1. 淘汰传统的MachineKey。

  2. 无需自己去设计加密算法,直接使用框架提供的,由专业的微软保证安全的算法即可。

  3. 无需自己管理密钥,默认情况下框架会自动生成以及选择对应的存储方式。

  4. 密钥默认情况每90天自动更替一次。

  5. 编程方式简单,通常情况下无需深入了解原理即可完成需求。

  6. 保留灵活性和拓展性,允许自定义算法、密钥存储等步骤。

有关Data Protection的详细介绍,可以看官方文档:

https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/introduction?view=aspnetcore-2.2

Data Protection 默认用的算法就是AES,可以满足我的需要。

加解密过程

框架帮我们隐藏复杂的算法过程之后,我们只要简单3部,就能完成加解密。

640?wx_fmt=png

通常的实践是:在Startup里添加DataProtection服务

public void ConfigureServices(IServiceCollection services)

{

    services.AddDataProtection();

    // ...

}

然后创建一个类似这样的Service供系统其他地方加解密数据。

public class EncryptionService

{

    private readonly IDataProtectionProvider _dataProtectionProvider;

    private const string Key = "cxz92k13md8f981hu6y7alkc";


    public EncryptionService(IDataProtectionProvider dataProtectionProvider)

    {

        _dataProtectionProvider = dataProtectionProvider;

    }


    public string Encrypt(string input)

    {

        var protector = _dataProtectionProvider.CreateProtector(Key);

        return protector.Protect(input);

    }


    public string Decrypt(string cipherText)

    {

        var protector = _dataProtectionProvider.CreateProtector(Key);

        return protector.Unprotect(cipherText);

    }

}

我用该方法,加密了邮箱密码,并存储到数据库。然后更改了对应的代码从数据中成功解密,并在自己机器上调试完成发送邮件的功能,没有问题。于是我部署到了生产环境……

坑来了

生产环境解密数据库中的密文时发生了异常2_05.png

640?wx_fmt=png

System.Security.Cryptography.CryptographicException: The key {bd424a84-5faa-4b97-8cd9-6bea01f052cd} was not found in the key ring.

经过研究,这是因为,ASP.NET Core在不同机器上运行的时候,会生成不同的Key用来加密数据,而我数据库里的密文是用开发机的Key加密的,和服务器的Key不一样因此尝试解密的时候,找不到加密用的Key,就产生了这个异常。

ASP.NET Core 可以将Key保存在注册表、用户profile、Azure KeyVault、Azure 存储账户、文件系统等多种位置。

在Azure App Service下,Key被保存在了%HOME%\ASP.NET\DataProtection-Keys文件夹里。这个文件夹会非常神奇的自动同步到App Service的其他Instance下。

有兴趣的猿可以在Kudu工具里看到这个文件夹:

640?wx_fmt=png

因此要解决不同环境Key不一致的问题,只需要找一个一致的存储位置即可。但这并不能解决问题!因为默认情况下,每90天会重新生成一个新的Key,这样数据库里的密文如果不更新的话,又会失效。2_05.png

另外,ASP.NET Core表单使用的AntiForgeryToken也使用这套机制加密。因此如果你自己部署了多个instance的服务器(而不是用App Service去弹性扩充),就会导致每台服务器的key不同,用户提交表单会验证失败。2_05.png

640?wx_fmt=gif

解决方法

虽然我们可以做到用统一的位置保存Key,也能指定自动刷新周期,但我并不建议这样做。因为这套机制只适用于加密短时效的数据,并不是针对被持久化到数据库里的数据而设计的。所以在这种场景下,我们还是得自己写一个加解密的服务。

先(很不要脸的)从微软官方文档里拷一对AES加解密函数:

加密

private static byte[] EncryptStringToBytes_Aes(string plainText, byte[] key, byte[] iv)

{

    if (plainText == null || plainText.Length <= 0)

        throw new ArgumentNullException(nameof(plainText));

    if (key == null || key.Length <= 0)

        throw new ArgumentNullException(nameof(key));

    if (iv == null || iv.Length <= 0)

        throw new ArgumentNullException(nameof(iv));

    byte[] encrypted;


    using (var aesAlg = Aes.Create())

    {

        aesAlg.Key = key;

        aesAlg.IV = iv;


        var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

        using (var msEncrypt = new MemoryStream())

        {

            using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))

            {

                using (var swEncrypt = new StreamWriter(csEncrypt))

                {

                    swEncrypt.Write(plainText);

                }

                encrypted = msEncrypt.ToArray();

            }

        }

    }


    return encrypted;

}

解密

private static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] key, byte[] iv)

{

    if (cipherText == null || cipherText.Length <= 0)

        throw new ArgumentNullException(nameof(cipherText));

    if (key == null || key.Length <= 0)

        throw new ArgumentNullException(nameof(key));

    if (iv == null || iv.Length <= 0)

        throw new ArgumentNullException(nameof(iv));


    string plaintext;

    using (var aesAlg = Aes.Create())

    {

        aesAlg.Key = key;

        aesAlg.IV = iv;


        var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

        using (var msDecrypt = new MemoryStream(cipherText))

        {

            using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))

            {

                using (var srDecrypt = new StreamReader(csDecrypt))

                {

                    plaintext = srDecrypt.ReadToEnd();

                }

            }

        }


    }


    return plaintext;

}

定义一个EncryptionService

为了方便使用,加密结果我喜欢输出为string类型

public class EncryptionService

{

    private readonly KeyInfo _keyInfo;


    public EncryptionService(KeyInfo keyInfo = null)

    {

        _keyInfo = keyInfo;

    }


    public string Encrypt(string input)

    {

        var enc = EncryptStringToBytes_Aes(input, _keyInfo.Key, _keyInfo.Iv);

        return Convert.ToBase64String(enc);

    }


    public string Decrypt(string cipherText)

    {

        var cipherBytes = Convert.FromBase64String(cipherText);

        return DecryptStringFromBytes_Aes(cipherBytes, _keyInfo.Key, _keyInfo.Iv);

    }


    // 微软那两个加解密函数...

}

其中KeyInfo设计成一个单独的类,用来灵活的让用户选择赋值byte[]数组还是string类型的Key以及初始向量(IV)

public class KeyInfo

{

    public byte[] Key { get; }

    public byte[] Iv { get; }


    public string KeyString => Convert.ToBase64String(Key);

    public string IVString => Convert.ToBase64String(Iv);


    public KeyInfo()

    {

        using (var myAes = Aes.Create())

        {

            Key = myAes.Key;

            Iv = myAes.IV;

        }

    }


    public KeyInfo(string key, string iv)

    {

        Key = Convert.FromBase64String(key);

        Iv = Convert.FromBase64String(iv);

    }


    public KeyInfo(byte[] key, byte[] iv)

    {

        Key = key;

        Iv = iv;

    }

}

注册到DI容器

services.AddTransient(ec => new EncryptionService(new KeyInfo("45BLO2yoJkvBwz99kBEMlNkxvL40vUSGaqr/WBu3+Vg=", "Ou3fn+I9SVicGWMLkFEgZQ==")));

其中的Key和IV可以通过KeyInfo的无参构造函数获得。自己保存下来以后,就可以一直用这一对Key了,保证之后的加解密数据都是一致的。

使用方式

private readonly EncryptionService _encryptionService;


public HomeController(EncryptionService encryptionService)

{

    _encryptionService = encryptionService;

}


public IActionResult Index()

{

    var str = "Hello";

    var enc = _encryptionService.Encrypt(str);

    var dec = _encryptionService.Decrypt(enc);


    return Content($"str: {str}, enc: {enc}, dec: {dec}");

}

640?wx_fmt=png640?wx_fmt=png

总结

ASP.NET Core 自带的Data Protection API非常安全,使用方便,也比较灵活。但要注意Key存储以及定时刷新,只适用短时效的加密。对于长时间保存的固定密文,可以自己实现一个加解密服务。

完整的案例代码参见我的GitHub:

https://github.com/EdiWang/DotNet-Samples/tree/master/AspNet-AES-Non-DPAPI


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/317788.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[2020-11-28 contest]素数(数学),精灵(区间dp),农夫约的假期(结论),观察(树链剖分lca+set)

文章目录素数solutioncode精灵solutioncode农夫约的假期solutioncode观察solutionsolutioncode素数 solution 通过观察可得一个结论 对于两个相邻的质数p1,p2(p1<p2)p_1,p_2\ (p_1<p_2)p1​,p2​ (p1​<p2​) 对于x∈[p1,p2)x∈[p_1,p_2)x∈[p1​,p2​)&#xff0c;都…

B. Box Fitting

B. Box Fitting 题意&#xff1a; 现在有n个长方形&#xff0c;宽均为1&#xff0c;现在有一个底为m的容器&#xff0c;问将长方形放入其中&#xff0c;所用容器的最小宽度是多少 &#xff08;长方形必须长朝下放置详细如图&#xff09; 题解&#xff1a; 比赛时脑子抽了。…

AT2371-[AGC013E]Placing Squares【矩阵乘法】

正题 题目链接:https://www.luogu.com.cn/problem/AT2371 题目大意 给出nnn和mmm个数bbb。 求所有满足以下要求的序列aaa 和为nnn对于所有bib_ibi​不存在任何一个前缀和为bib_ibi​。 一个序列的贡献为所有数的二次方和&#xff0c;求所有合法序列的贡献。 1≤n≤109,1≤m…

博客搬迁通知

地址 CSDN 上所有博客由于更新困难的问题&#xff0c;已经全部搬迁到 我的博客园&#xff0c;如果您发现博客有许多缺失部分&#xff0c;请到 cnblogs 上查看&#xff0c;谢谢&#xff01;

洛谷P1074:靶形数独(搜索、剪枝)

解析 搜索题都是玄学 本题暴搜人人都会写&#xff0c;关键是如何剪枝 我一直在最优性剪枝上纠结qwq 但仔细想想&#xff0c;不同方案的权值差别没有那么大 再加上剪枝时不可避免的要放弃一些准确度 所以最优性剪枝在本题可能确实没有太大的作用 考虑我们平时如何玩数独 肯定是…

人工智能第六课:如何做研究

这是我学习 Data Science Research Methods 这门课程的笔记。这门课程的讲师是一名教授和数据科学家&#xff0c;可能因为他既有理论背景&#xff0c;又有实践经验&#xff0c;所以整个课程听下来还比较舒服&#xff0c;学到了一些不错的理论知识。这门课比较系统地介绍了什么…

CF889E-Mod Mod Mod【dp】

正题 题目链接:https://www.luogu.com.cn/problem/CF889E 题目大意 给出一个长度为nnn的序列aaa&#xff0c;定义函数f(i,x)f(i,x)f(i,x)有 f(n,x)xmodanf(n,x)x\bmod a_nf(n,x)xmodan​ f(i,x)(xmodai)f(xmodai)(i<n)f(i,x)(x\bmod a_i)f(x\bmod a_i)(i<n)f(i,x)(xmod…

[2020-11-30 contest]数列(矩阵加速),秘密通道(dijkstra最短路)小X游世界树(换根dp),划分(数学)

文章目录数列solutioncode秘密通道solutioncode小X游世界树solutioncode划分solutioncode数列 a[1]a[2]a[3]1 a[x]a[x-3]a[x-1] (x>3) 求 a 数列的第 n 项对 1000000007&#xff08;10^97&#xff09;取余的值。 输入格式 第一行一个整数 T&#xff0c;表示询问个数。 以下…

动态区间第k小:树状数组套权值线段树

所谓树状数组套权值线段树&#xff0c;就是在树状树组上套权值线段树 &#xff08;逃&#xff09; 解析 如何解决静态区间第k小&#xff1f; 使用主席树就ok啦 辣么如何解决动态区间第k小嘞… 我们想想主席树为啥不能解决动态区间第k小 因为如果改了一个点的值&#xff0c;…

Docker最全教程——数据库容器化之持久保存数据(十二)

上一节我们讲述了SQL Server容器化实践&#xff08;注意&#xff0c;SQL Server现在也支持跨平台&#xff09;&#xff0c;本节将讲述如何持久保存数据&#xff0c;并且接下来将逐步讲解其他数据库&#xff08;MySql、Redis、Mongodb等等&#xff09;的容器化实践&#xff0c;中…

AT2064-[AGC005F]Many Easy Problems【NTT】

正题 题目链接:https://www.luogu.com.cn/problem/AT2064 题目大意 给出nnn个点的一棵树&#xff0c;对于k∈[1,n]k\in[1,n]k∈[1,n]求出所有kkk个点的点集的构出的虚树大小和。 1≤n≤21051\leq n\leq 2\times 10^51≤n≤2105 解题思路 考虑每个点的贡献&#xff0c;一个点…

【李超树】李超线段树维护凸包(凸壳) (例题:blue mary开公司+线段游戏+ZZH的旅行)

文章目录前言李超树引入(斜率优化)什么是李超树&#xff1f;李超树活着能干点什么&#xff1f;算法思想(使用手册&#xff1f;)插入查询模板判断是否覆盖(优不优)插入查询例题板题&#xff1a;BlueMary开公司分析code线段游戏分析code拓展——(动态开点李超树维护凸包)ZZH的旅行…

L3-2 至多删三个字符

L3-2 至多删三个字符 题意&#xff1a; 给定一个全部由小写英文字母组成的字符串&#xff0c;允许你至多删掉其中 3 个字符&#xff0c;结果可能有多少种不同的字符串&#xff1f; 题解&#xff1a; 最不擅长dp 我们设dp[i][j]表示前i里面删除j个有多少种方法 第i个删除或者…

2021CSP:游记

前言 没做出别人做不出的题 却犯了别人不会犯的错 考场 先遍历 T1感觉似乎很水的样子 T2这甚么东西… T3似乎有的一搞 T4网络流模板可以拿40 &#xff08;然而由于某次模拟悲伤的前车之鉴并不想为这点分写又臭又长的dinic…&#xff09; 先开T1 一开始直觉想三分水过去 但分析…

老牌开源Office操作组件NPOI现已支持.NET Core

昨天在微信群里听到老牌Excel开发利器NPOI的作者瞿总说4.6.1版本的NPOI已经支持.NET Standard 2.0了&#xff0c;这也就意味着你可以在.NET Core中使用NPOI了。作者&#xff1a;依乐祝原文地址 &#xff1a;https://www.cnblogs.com/yilezhu/p/10269281.html写在前面曾经的.NET…

P5327-[ZJOI2019]语言【线段树合并,LCA】

正题 题目链接:https://www.luogu.com.cn/problem/P5327 题目大意 给出nnn个点的一棵树&#xff0c;和mmm条路径&#xff0c;求有多少个点对至少存在一条路径经过它们。 1≤n,m≤1051\leq n,m\leq 10^51≤n,m≤105 解题思路 有一个很显然的性质&#xff0c;如果点zzz在x→yx…

D. Bananas in a Microwave

D. Bananas in a Microwave 题意&#xff1a; u1s1&#xff0c;题意真难懂 有n个时间&#xff0c;每个时间给你两个操作&#xff0c;第一个是kkx&#xff0c;第二个是kk∗x&#xff0c;且可以执行[0,y]次&#xff0c;&#xff08;在第i个时间点&#xff0c;必须应用第i个操作…

.NET西安社区 [拥抱开源,又见 .NET] 第二次活动简报

「拥抱开源, 又见 .NET」随着 .NET Core的发布和开源&#xff0c;.NET又重新回到人们的视野。 .NET Core的下个3.0即将release&#xff0c;加入非常多的新功能&#xff0c;越来越拥抱变化&#xff0c;DevOps和Microservice的最佳实践已经在 .NET Core落地&#xff0c;比如 Ocel…

YBTOJ洛谷P2839:最大中位数(主席树、二分答案)

遇事不决&#xff0c;二分试试 解析 很好的一道题 真是把主席树玩明白了 一个关于中位数的常用trick&#xff1a; 二分答案mid&#xff0c;把>mid的看成1&#xff0c;<mid的看成-1&#xff0c;然后看最大子段和是否>0 然而如果对离散化后的每一个值建一棵小白逛公园那…

计数学习小记

前言 闲的无聊懒得做题不如来水点博客。 虽然一直作为一个感性做题的选手&#xff0c;但是理性层面上确实是分析题目初步做法的一个十分重要的方法。 额不会涉及具体的知识点&#xff0c;只是总结点自己做题的时候遇到的比较巧妙的方法。 混沌排版请见谅 还有我也很菜有错…