时间:2021年04月26日 | 作者 : aaronyang | 分类 : .NET CORE | 浏览: 782次 | 评论 0 人
在NC2.1中,微软引入基于Span<T>的加密API,用于计算哈希值,生成随机数,生成和处理非对称签名以及进行RSA加密。
很多技术保护数据,介绍6种
加密和解密:双向
哈希:这是一个生成哈希值以安全存储密码的单向过程,检测数据是否恶意修改或者损坏
签名:用于根据某人的公钥来验证应用 于某些数据的签名,从而确保是否来自信任的人。
身份验证:用于通过检查某人的凭据来识别此人。
授权:用于通过检查某人所属的角色或组来确保此人 具有执行操作或处理某些数据的权限。
密钥可以是对称的(也称 共享密钥 或 秘密密钥,因为使用相同的密钥进行加密和解密),也可以是非对称的(公钥-私钥对,公钥用于加密,私钥用于解密)
对称加密速度快,可以使用流加密大量数据,非对称速度慢,只能加密小字节数组
可以使用对称密钥加密数据,使用非对称密钥共享对称的 密钥
在对大量数据进行加密时,可能出现重复的序列。例如,在英文文档中,经常出现 the,the,每次都可能被加密为hQ2。
把数据分解成块,就可以避免出现重复的序列。对一个块进行加密后,就会从这个块生成一个字节数组值,可以将这个字节数组输入下一个块以调整算法,进而以不同的方式加密数据。为了加密第一个块,需要一个字节数组,这称为初始化向量(IV)
选择较小的块能提高加密程度。
salt是随机的字节数组,可用作单向哈希函数的额外输入。如果再生成哈希时没有使用salt,那么当许多用户使用123456作为密码时,他们将具有相同的哈希值,他们的账户很容易收到字典式攻击。
用户注册时,应该随机生成salt,并在哈希之前将salt于密码连接起来,一起存数据库。
然后,当用户下一次登陆并且输入密码时,查找他们的salt+密码连接起来,重新生成哈希值,然后与数据库中存储的哈希值进行比较,如果项目,密码正确。
就是给 字符串+遗传随机的字符串 ,然后生成不同的哈希值了
键和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程序
然后新建个类库 CtyptographyLib
控制台对这个项目添加引用
、
在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(); } } }
NC中,多种哈希算法可供选择。不使用密钥的,使用对称的,也有使用 不对称密钥的
选择哈希算法时候,两个因素
抗碰撞性:两个输入拥有相同哈希的情况有多罕见?
逆原像阻力:对于某个哈希,另一个输入想要共享 相同的哈希有多难?
MD5 哈希大小16字节 算法快,无碰撞性
SHA1 20字节 2011年后SHA1在互联网已被禁用
SHA256 32字节 安全的SHA二代版本
SHA384 48字节 安全的SHA二代版本
SHA512 64字节 安全的SHA二代版本
推荐使用SHA 二代,因为SHA大,重复可能性小。
这里用 一个用户注册 用户名和密码的示例解释用法,在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
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("无效签名"); }
上述代码注意:
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(); }
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类
ClaimsIdentity类有Claims属性,他和Claims类之间双箭头
Claims对象拥有Type属性, 声明是否 针对名称、角色或 组的成员关系、出生日期等。
IPrincipal 接口用于将身份与它们所属的角色和组关联起来,因此可以用于授权目的。执行代码的当前线程拥有CurrentPrincipal属性,该属性可以设置为实现了IPrincipal接口的任何对象,并且因为执行安全操作而需要权限时,应检查CurrentPrincipal属性。
实现了IPrincipal接口的类 GenericPrincipal,该类继承ClaimsPrincipal类。
实现身份验证和授权
在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}"); } }
保护应用程序功能
在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}"); }
运行代码
Protector.LogIn("ay", "ay2021");
换成不在这个组的ay2
更多主题:https://docs.microsoft.com/zh-cn/dotnet/standard/security/principal-and-identity-objects
====================www.ayjs.net 杨洋 wpfui.com ayui ay aaronyang=======请不要转载谢谢了。=========
抖音:wpfui 工作wpf
目前在合肥企迈科技公司上班,加我QQ私聊
2023年11月网站停运,将搬到CSDN上
AYUI8全源码 Github地址:前往获取
杨洋(AaronYang简称AY,安徽六安人)和AY交流
高中学历,2010年开始web开发,2015年1月17日开始学习WPF
声明:AYUI7个人与商用免费,源码可购买。部分DEMO不免费
查看捐赠AYUI7.X MVC教程 更新如下:
第一课 第二课 程序加密教程
额 本文暂时没人评论 来添加一个吧
发表评论