当前位置:网站首页 / .NET CORE / 正文

ay的C#8.0和net5高级编程笔记10-保护数据和应用程序

时间:2021年04月26日 | 作者 : aaronyang | 分类 : .NET CORE | 浏览: 782次 | 评论 0

保护数据和应用程序

在NC2.1中,微软引入基于Span<T>的加密API,用于计算哈希值,生成随机数,生成和处理非对称签名以及进行RSA加密。

理解数据保护术语

很多技术保护数据,介绍6种

加密和解密:双向

哈希:这是一个生成哈希值以安全存储密码的单向过程,检测数据是否恶意修改或者损坏

签名:用于根据某人的公钥来验证应用 于某些数据的签名,从而确保是否来自信任的人。

身份验证:用于通过检查某人的凭据来识别此人。

授权:用于通过检查某人所属的角色或组来确保此人 具有执行操作或处理某些数据的权限。


密钥可以是对称的(也称 共享密钥 或 秘密密钥,因为使用相同的密钥进行加密和解密),也可以是非对称的(公钥-私钥对,公钥用于加密,私钥用于解密)

对称加密速度快,可以使用流加密大量数据,非对称速度慢,只能加密小字节数组


可以使用对称密钥加密数据,使用非对称密钥共享对称的 密钥


IV和块大小

在对大量数据进行加密时,可能出现重复的序列。例如,在英文文档中,经常出现 the,the,每次都可能被加密为hQ2。

把数据分解成块,就可以避免出现重复的序列。对一个块进行加密后,就会从这个块生成一个字节数组值,可以将这个字节数组输入下一个块以调整算法,进而以不同的方式加密数据。为了加密第一个块,需要一个字节数组,这称为初始化向量(IV)

选择较小的块能提高加密程度。


salt

salt是随机的字节数组,可用作单向哈希函数的额外输入。如果再生成哈希时没有使用salt,那么当许多用户使用123456作为密码时,他们将具有相同的哈希值,他们的账户很容易收到字典式攻击。

用户注册时,应该随机生成salt,并在哈希之前将salt于密码连接起来,一起存数据库。

然后,当用户下一次登陆并且输入密码时,查找他们的salt+密码连接起来,重新生成哈希值,然后与数据库中存储的哈希值进行比较,如果项目,密码正确。


就是给  字符串+遗传随机的字符串 ,然后生成不同的哈希值了


生成密钥和IV

键和IV是字节数组。想要交换加密数据的双方都需要密钥和IV,但是字节数组很难可靠地交换。可使用基于密码的密钥派生函数(PBKDF2)可靠地生成密钥或IV

salt的大小应该是8字节或更大,迭代计数应该大于0,建议的最小迭代次数是1000



加密和解密数据

NC中,有些加密由操作系统实现,他们名以 CryptoServiceProvider 结尾,还有些加密算法在NC中实现,名以 Managed 结尾

主要非对称加密算法是RSA

对称加密算法CryptoStream对大量字节进行有效的加密或解密。非对称处理少量字节,并且存储在字节组中而不是存储在流中

对称加密算法源自 SymmetricAlgorithm的抽象类,比如

    AES

    DESCryptoServiceProvider

    TripleDESCryptoServiceProvider

    RC2CryptoServiceProvider

    RijndaelManaged

如果需要编写代码来解密外部系统发送的某些数据,那就必须使用外部同用于加密数据的任何算法。或者 加密数据发送到特定算法解密的系统,那就无法选择加密算法


AES对称加密,RSA非对称,数字签名算法(DSA)不能加密数据,只能生成哈希值和签名。




DEMO1 使用AES

新建chapter10文件夹,新建EncryptionApp 控制台core程序

image.png

然后新建个类库 CtyptographyLib

image.png

image.png

控制台对这个项目添加引用

image.pngimage.png

在CryptographyLib新建个类

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

namespace CryptographyLib
{
    public static class Protector
    {
        //至少8个字节,我们将使用16字节
        private static readonly byte[] salt = System.Text.Encoding.Unicode.GetBytes("2Arronyang");

        /// <summary>
        /// 至少1000,我们使用2000
        /// </summary>
        private static readonly int iterations = 2000;

        public static string Encrpt(string text, string pwd)
        {
            byte[] encryptedBytes;
            byte[] plainBytes = System.Text.Encoding.Unicode.GetBytes(text);
            var aes = Aes.Create();
            var pbkdf2 = new Rfc2898DeriveBytes(pwd, salt, iterations);
            aes.Key = pbkdf2.GetBytes(32);//设置256位key
            aes.IV = pbkdf2.GetBytes(16); //设置128位IV
            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(plainBytes, 0, plainBytes.Length);
                }
                encryptedBytes = ms.ToArray();
            }
            return Convert.ToBase64String(encryptedBytes);
        }


        public static string Decrypt(string cryptoText, string pwd)
        {
            byte[] plainBytes;
            byte[] cryptoBytes = Convert.FromBase64String(cryptoText);
            var aes = Aes.Create();
            var pbkdf2 = new Rfc2898DeriveBytes(pwd, salt, iterations);
            aes.Key = pbkdf2.GetBytes(32);//设置256位key
            aes.IV = pbkdf2.GetBytes(16); //设置128位IV

            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(cryptoBytes, 0, cryptoBytes.Length);
                }
                plainBytes = ms.ToArray();
            }
            return System.Text.Encoding.Unicode.GetString(plainBytes);

        }
    }
}

控制台调用

using System;
using System.Security.Cryptography;
using CryptographyLib;

namespace EncryptionApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //使用了双倍推荐的salt大小和迭代计数
            //虽然salt和迭代计数硬编码,但是密码是动态的

            string str = "我叫杨洋,AY2021";
            string pwd = "ay@2021";
            //加密
            string cryptoText = Protector.Encrpt(str, pwd);
            Console.WriteLine(cryptoText);
            try
            {
                //解密
                Console.WriteLine("\r\n解密后:");
                string clearText = Protector.Decrypt(cryptoText, pwd);
                Console.WriteLine(clearText);
            }
            catch (CryptographicException ex)
            {
                Console.WriteLine("密码输入错了");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }


            Console.ReadLine();
        }
    }
}

image.png


哈希数据

NC中,多种哈希算法可供选择。不使用密钥的,使用对称的,也有使用 不对称密钥的

选择哈希算法时候,两个因素

抗碰撞性:两个输入拥有相同哈希的情况有多罕见?

逆原像阻力:对于某个哈希,另一个输入想要共享 相同的哈希有多难?


MD5 哈希大小16字节 算法快,无碰撞性

SHA1 20字节  2011年后SHA1在互联网已被禁用

SHA256     32字节  安全的SHA二代版本

SHA384     48字节 安全的SHA二代版本

SHA512     64字节 安全的SHA二代版本


推荐使用SHA 二代,因为SHA大,重复可能性小。


哈希与常用的SHA256算法

这里用 一个用户注册 用户名和密码的示例解释用法,在CryptographyLib新增

public class User
    {
        public string Name { get; set; }
        public string Salt { get; set; }
        public string SaltedHashedPassword { get; set; }
    }

在Protector类 添加代码

 /// <summary>
        /// 存用户
        /// </summary>
        private static Dictionary<string, User> Users = new Dictionary<string, User>();
        public static User Register(string username, string password)
        {
            var rng = RandomNumberGenerator.Create();
            var saltBytes = new byte[16];
            rng.GetBytes(saltBytes);

            var saltText = Convert.ToBase64String(saltBytes);
            //用户的密码+随机数=新字符串,sha哈希下
            var saltedhashedPassword = SaltAndHashPassword(password, saltText);
            var user = new User()
            {
                Name = username,
                Salt=saltText,
                SaltedHashedPassword = saltedhashedPassword
            };
            Users.Add(user.Name,user);
            return user;
        }

        public static bool CheckPwd(string username, string password)
        {
            if (!Users.ContainsKey(username))
            {
                return false;
            }
            var user = Users[username];
            var shp = SaltAndHashPassword(password, user.Salt);
         
            return shp ==user.SaltedHashedPassword;
        }

        private static string SaltAndHashPassword(string password, string saltText)
        {
            var sha = SHA256.Create();
            var saltedp = password + saltText;
            return Convert.ToBase64String(sha.ComputeHash(System.Text.Encoding.Unicode.GetBytes(saltedp)));
        }

打开控制台,添加测试代码

      string myusername = "aaronyang";
            string mypwd = "ay@2021";
            //注册
            var user = Protector.Register(myusername, mypwd);
            //注册好后,验证
            var checkwrong = Protector.CheckPwd(myusername,mypwd+"wrong");
            if (!checkwrong)
            {
                Console.WriteLine("密码错误");
            }
            var checkright = Protector.CheckPwd(myusername, mypwd);
            if (checkright)
            {
                Console.WriteLine("密码正确");
            }

代码还是容易懂的,自己看吧


====================www.ayjs.net       杨洋    wpfui.com        ayui      ay  aaronyang=======请不要转载谢谢了。=========



签名数据

下面使用SHA256 生成哈希,并结合RSA算法对哈希进行签名

可以同时使用DSA算法进行哈希和签名。DSA生成比RSA算法快,但是在验证签名比RSA慢。由于签名只生成一次,但要验证多次,因此最好能比生成更快地进行验证。

RSA算法基于大整数的因式分解,而DSA算法基于离散对数计算。

https://mathworld.wolfram.com/RSAEncryption.html


使用SHA256和RSA算法进行签名

CryptographyLib的Protector新增代码

      //demo3 sha256和RSA算法签名
        public static string PublicKey;
        public static string ToXmlStringExt(this RSA rsa, bool includePrivateParameters)
        {
            var p = rsa.ExportParameters(includePrivateParameters);
            XElement xml;
            if (includePrivateParameters)
            {
                xml = new XElement("RSAKeyValue",
                                     new XElement("Modulus", Convert.ToBase64String(p.Modulus)),
                                     new XElement("Exponent", Convert.ToBase64String(p.Exponent)),
                                     new XElement("P", Convert.ToBase64String(p.P)),
                                      new XElement("Q", Convert.ToBase64String(p.Q)),
                                      new XElement("DP", Convert.ToBase64String(p.DP)),
                                      new XElement("DQ", Convert.ToBase64String(p.DQ)),
                                      new XElement("InverseQ", Convert.ToBase64String(p.InverseQ))
                    );
            }
            else
            {
                xml = new XElement("RSAKeyValue",
                                 new XElement("Modulus", Convert.ToBase64String(p.Modulus)),
                                 new XElement("Exponent", Convert.ToBase64String(p.Exponent))
                );
            }
            return xml?.ToString();
        }

        public static void FromXmlStringExt(this RSA rsa, string parameterAsXml)
        {
            var xml = XDocument.Parse(parameterAsXml);
            var root = xml.Element("RSAKeyValue");
            var p = new RSAParameters
            {
                Modulus = Convert.FromBase64String(root.Element("Modulus").Value),
                Exponent = Convert.FromBase64String(root.Element("Exponent").Value)
            };
            if (root.Element("p") != null)
            {
                p.P = Convert.FromBase64String(root.Element("P").Value);
                p.Q = Convert.FromBase64String(root.Element("Q").Value);
                p.DP = Convert.FromBase64String(root.Element("DP").Value);
                p.DQ = Convert.FromBase64String(root.Element("DQ").Value);
                p.InverseQ = Convert.FromBase64String(root.Element("InverseQ").Value);
            }
            rsa.ImportParameters(p);
        }

        //生成签名
        public static string GenerateSignature(string data)
        {
            byte[] databytes = System.Text.Encoding.Unicode.GetBytes(data);
            var sha = SHA256.Create();
            var hashedData = sha.ComputeHash(databytes);
            var rsa =RSA.Create();
            PublicKey = rsa.ToXmlStringExt(false);
            return Convert.ToBase64String(rsa.SignHash(hashedData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
        }
        //验证签名
        public static bool ValidateSignature(string data,string signature)
        {
            byte[] databytes = System.Text.Encoding.Unicode.GetBytes(data);
            var sha = SHA256.Create();
            var hashedData = sha.ComputeHash(databytes);

            byte[] signatureBytes = Convert.FromBase64String(signature);

            var rsa = RSA.Create();
            rsa.FromXmlStringExt(PublicKey);

            return rsa.VerifyHash(hashedData, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }


客户端使用

 string data = "我是中国人,aaronyang";
            var signature = Protector.GenerateSignature(data);
            Console.WriteLine("签名:"+signature);
            Console.WriteLine("PublicKey如下");
            Console.WriteLine(Protector.PublicKey);

            Console.WriteLine();
            if (Protector.ValidateSignature(data, signature))
            {
                Console.WriteLine("正确,签名有效");
            }
            else
            {
                Console.WriteLine("无效签名");
            }

            var fakeSignature = signature.Replace(signature[0], 'X');
            if (Protector.ValidateSignature(data, fakeSignature))
            {
                Console.WriteLine("正确,签名有效");
            }
            else
            {
                Console.WriteLine("无效签名");
            }


image.png

 上述代码注意:

    RSA类型有两个名为ToXmlString和FromXmlString的方法,他们用于对包含公钥和私钥的RSAParameters结构进行序列化和反序列化。

这两个方法在macOS中实现会抛出PlatformNotSupportedException异常。为了将他们重新实现为扩展方法ToXmlStringExt和FromXmlStringExt。


只需要将公钥-私钥对的公共部分提供给检查签名的代码,以便在调用ToXmlStringExt方法时传入false值。私有部分需要对数据进行签名,并且必须保密,因为任何还拥有私有部分的人都可以对数据进行签名。


更多:https://docs.microsoft.com/zh-cn/dotnet/standard/security/cryptographic-signatures

关于RSA这块,我以前写过深入的一篇

http://www.ayjs.net/post/722.html


生成随机数

两个类

Random生成伪随机数

随机数可以设置种子

var r=new Random(Seed:12345);

共享的种子可以充当密钥,两个应用程序使用相同种子值的相同随机数生成算法,那么他们可以生成相同的随机序列。例如当同步GPS接收器与卫星时,通常,种子值保密。


int a=r.Next(1,7) 生成1 到 6

double b=r.NextDouble() //0.0到1.0

var c=new byte[256];

r.NextBytes(c);


随机数使用 RandomNumberGenerator派生类型,例如RNGCrpytoServiceProvider

        //Demo4
        public static byte[] GetRandomKeyOrIV(int size)
        {
            var r = RandomNumberGenerator.Create();
            var data = new byte[size];
            r.GetNonZeroBytes(data);
            return data;
        }

客户端使用

     string size = "16";
            byte[] b = Protector.GetRandomKeyOrIV(int.Parse(size));
            for (int i = 0; i < b.Length; i++)
            {
                Console.Write($"{b[i]:x2}");
                if (((i + 1)%16)== 0)Console.WriteLine();
            }

image.png



密码学新内容

NC3 自然优势就是哈希算法,HMAC,随机数生成,非对称签名的生成与处理,RSA加密已重写为使用Span<T>,更好的性能,例如Rfc2898DerlVeBytes快了15%

CMS/PKSC#7 消息的签署和验证

启用X509Certificate.GetCertHash和X509Certificate.GetCertHashString,以使用非SHA-1算法获取证书缩略图

CryptographicOperations类包含一些例如ZeroMemory以安全清除内存

RandomNumberGenerator提供了Fill,用于使用随机值填充跨度,并且不需要管理IDsposable资源。

用于读取验证和创建RFC3161 TimestampToken值的API

使用ECDiffieHellman类支持椭圆曲线的Diffie-Hellman算法

在Linux平台支持RSA-OAEP-SHA2和RSA-PSS算法



用户的身份验证和授权

凭据包括 用户名和密码,还有指纹或者面部扫描

授权是在允许访问应用程序的功能和数据等资源之前,验证组或角色的成员资格的过程。虽然授权可以基于个人身份,但是基于组或角色的成员身份进行授权是一种良好的安全实践,即使角色或组中只有一个用户也是如此。因为这种方式允许用户的成员身份在未来发生更改,而无须重新分配用户的个人访问权限。

有多种身份验证和授权机制可供选择。

在System.Security.Principal名称空间实现了一对接口 IIdentity和IPrincipal

IIdentity表示用户,因此拥有Name和IsAuthenticated属性,指示用户是匿名还是通过凭据进行身份验证。

试下来了IIdentity接口的最常见的类是GenericiIdentity,该类继承ClaimsIdentity类

image.png


ClaimsIdentity类有Claims属性,他和Claims类之间双箭头

Claims对象拥有Type属性, 声明是否 针对名称、角色或 组的成员关系、出生日期等。

IPrincipal 接口用于将身份与它们所属的角色和组关联起来,因此可以用于授权目的。执行代码的当前线程拥有CurrentPrincipal属性,该属性可以设置为实现了IPrincipal接口的任何对象,并且因为执行安全操作而需要权限时,应检查CurrentPrincipal属性。

实现了IPrincipal接口的类 GenericPrincipal,该类继承ClaimsPrincipal类。

image.png


实现身份验证和授权

在CryptographyLib,User类增加个Roles

    public class User
    {
        public string Name { get; set; }
        public string Salt { get; set; }
        public string SaltedHashedPassword { get; set; }
        public string[] Roles { get; set; }
    }

Protector类中的Register方法,添加角色

修改Roles

  public static User Register(string username, string password,string[] roles=null)
        {
            var rng = RandomNumberGenerator.Create();
            var saltBytes = new byte[16];
            rng.GetBytes(saltBytes);

            var saltText = Convert.ToBase64String(saltBytes);
            //用户的密码+随机数=新字符串,sha哈希下
            var saltedhashedPassword = SaltAndHashPassword(password, saltText);
            var user = new User()
            {
                Name = username,
                Salt = saltText,
                SaltedHashedPassword = saltedhashedPassword,
                Roles=roles
            };
            Users.Add(user.Name, user);
            return user;
        }

Protector类增加个 LogIn,从而登录用户并使用通用标识和主题将他们分配给当前线程。

        public static void LogIn(string username,string password)
        {
            if (CheckPwd(username, password))
            {
                var identity = new GenericIdentity(username,"AYAuth");
                var principal = new GenericPrincipal(identity, Users[username].Roles);
                System.Threading.Thread.CurrentPrincipal = principal;

            }
        }

顶部的命名空间

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Xml.Linq;


打开控制台,添加测试代码

    //注册
            Protector.Register("ay", "ay2021", new[] { "Admins"});
            Protector.Register("ay1", "ay2021", new[] { "Admins","Team" });
            Protector.Register("ay2", "ay2021");

            //测试1 登录
            Protector.LogIn("ay", "ay2021");
            if (Thread.CurrentPrincipal == null)
            {
                Console.WriteLine("登录失败");
                return;
            }
            var p = Thread.CurrentPrincipal;
            Console.WriteLine("是否验证:"+p.Identity.IsAuthenticated);
            Console.WriteLine("验证类型:" + p.Identity.AuthenticationType);
            Console.WriteLine("名称:" + p.Identity.Name);
            Console.WriteLine("是否在Admins组里:" + p.IsInRole("Admins"));
            Console.WriteLine("是否在Devs组里:" + p.IsInRole("Devs"));
            if(p is ClaimsPrincipal cp)
            {
                Console.WriteLine("有以下声明");
                foreach (Claim claim in cp.Claims)
                {
                    Console.WriteLine($"{claim.Type}:{claim.Value}");
                }
            }


image.png


保护应用程序功能

在Program类中添加一个方法,如果用户是匿名或者不是Admins角色的成员,就抛出异常

  static void SecureFeature()
        {
            if (Thread.CurrentPrincipal == null)
            {
                throw new System.Security.SecurityException("用户必须在Admins组才可以使用");
            }
            if (!Thread.CurrentPrincipal.IsInRole("Admins"))
            {
                throw new System.Security.SecurityException("用户必须在Admins组才可以使用");
            }
            Console.WriteLine("可以访问");
        }


然后在Main方法末尾增加

            try
            {
                SecureFeature();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.Message}");
            }

image.png

运行代码

 Protector.LogIn("ay", "ay2021");

image.png

换成不在这个组的ay2

image.png

image.png


更多主题:https://docs.microsoft.com/zh-cn/dotnet/standard/security/principal-and-identity-objects


====================www.ayjs.net       杨洋    wpfui.com        ayui      ay  aaronyang=======请不要转载谢谢了。=========



推荐您阅读更多有关于“C#8.0core3,”的文章

猜你喜欢

额 本文暂时没人评论 来添加一个吧

发表评论

必填

选填

选填

必填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

  查看权限

抖音:wpfui 工作wpf

目前在合肥企迈科技公司上班,加我QQ私聊

2023年11月网站停运,将搬到CSDN上

AYUI8全源码 Github地址:前往获取

杨洋(AaronYang简称AY,安徽六安人)AY唯一QQ:875556003和AY交流

高中学历,2010年开始web开发,2015年1月17日开始学习WPF

声明:AYUI7个人与商用免费,源码可购买。部分DEMO不免费

查看捐赠

AYUI7.X MVC教程 更新如下:

第一课 第二课 程序加密教程

标签列表