自w到高c的25种方法 密码学

大家好,关于自w到高c的25种方法很多朋友都还不太明白,今天小编就来为大家分享关于密码学的知识,希望对各位有所帮助!

在本章中,我们将讨论.NET中的主要加密API:

WindowsDataProtection(DPAPI)散列法对称加密公钥加密和签名

本章中介绍的类型在以下命名空间中定义:

System.Security;nSystem.Security.Cryptography;概述

汇总了.NET中的加密选项。在其余部分中,我们将逐一探讨其中的每一个。

.NET中的加密和哈希选项

选择

要管理的密钥

速度

强度

笔记

文件加密

0

取决于用户的密码

通过文件系统支持透明地保护文件。密钥隐式派生自登录用户的凭据。仅限窗口。

视窗数据保护

0

取决于用户的密码

使用隐式派生密钥加密和解密字节数组

散列法

0

单向(不可逆)转换。用于存储密码、比较文件和检查数据损坏

对称加密

1

用于通用加密/解密。相同的密钥加密和解密。可用于保护传输中的邮件

公钥加密

2

加密和解密使用不同的密钥。用于在消息传输中交换对称密钥和对文件进行数字签名

.NET还为在System.Security.Cryptography.Xml中创建和验证基于XML的签名以及用于处理System.Security.Cryptography.X509Certificates中的数字证书的类型提供了更专业的支持。

视窗数据保护注意

Windows数据保护仅在Windows上可用,并在其他操作系统上抛出PlatformNotSupportedException。

在第的一节中,我们描述了如何使用File.Encrypt请求操作系统透明地加密文件:

File.WriteAllText("myfile.txt","");nFile.Encrypt("myfile.txt");nFile.AppendAllText("myfile.txt","sensitivedata");

在这种情况下,加密使用从登录用户的密码派生的密钥。您可以使用相同的隐式派生密钥通过Windows数据保护API(DPAPI)加密字节数组。DPAPI通过ProtectedData类公开,类是一种具有两种静态方法的简单类型:

publicstaticbyte[]Protectn(byte[]userData,byte[]optionalEntropy,DataProtectionScopescope);nnpublicstaticbyte[]Unprotectn(byte[]encryptedData,byte[]optionalEntropy,DataProtectionScopescope);

无论您包含在optionalEntropy中的任何内容都会添加到密钥中,从而提高其安全性。DataProtectionScope枚举参数允许两个选项:CurrentUser或LocalMachine。使用当前用户,密钥派生自登录用户的凭据;使用本地计算机,使用计算机范围的密钥,这是所有用户共有的。这意味着,使用CurrentUser范围时,一个用户加密的数据不能被另一个用户解密。本地计算机密钥提供的保护较少,但在Windows服务或需要在各种帐户下运行的程序下工作。

下面是一个简单的加密和解密演示:

byte[]original={1,2,3,4,5};nDataProtectionScopescope=DataProtectionScope.CurrentUser;nnbyte[]encrypted=ProtectedData.Protect(original,null,scope);nbyte[]decrypted=ProtectedData.Unprotect(encrypted,null,scope);n//decryptedisnow{1,2,3,4,5}

Windows数据保护针对对计算机具有完全访问权限的攻击者提供中等安全性,具体取决于用户密码的强度。对于本地计算机范围,它仅对物理和电子访问受限的用户有效。

散列法

哈希将潜在的大量字节提取为一个小的固定长度。哈希算法的设计使得源数据中任意位置的单位更改会导致明显不同的哈希码。这使其适用于比较文件或检测文件或数据流的意外(或恶意)损坏。

哈希还充当单向加密,因为很难将哈希码转换回原始数据。这使其成为在数据库中存储密码的理想选择,因为如果您的数据库遭到入侵,您不希望攻击者获得对纯文本密码的访问权限。要进行身份验证,只需对用户键入的内容进行哈希处理,并将其与数据库中存储的哈希进行比较。

要进行哈希处理,您可以在其中一个HashAlgorithm子类(如SHA1或SHA256)上调用ComputeHash:

byte[]hash;nusing(Streamfs=File.OpenRead("checkme.doc"))nhash=SHA1.Create().ComputeHash(fs);//SHA1hashis20byteslong

ComputeHash方法还接受字节数组,这对于散列密码很方便(我们在中描述了一种更安全的技术):

byte[]data=System.Text.Encoding.UTF8.GetBytes("stRhong%pword");nbyte[]hash=SHA256.Create().ComputeHash(data);注意

编码对象上的GetBytes方法将字符串转换为字节数组;方法将其转换回来。但是,编码对象无法将加密或哈希字节数组转换为字符串,因为加扰的数据通常会违反文本编码规则。相反,请使用Convert.ToBase64String和Convert.FromBase64String:它们在任何字节数组和合法(XML或JSON友好)字符串之间进行转换。

.NET中的哈希算法

SHA1和SHA256是.NET提供的两种哈希算法子类型。以下是所有主要算法,按安全性升序排列(和哈希长度,以字节为单位):

MD5(16)→SHA1(20)→SHA256(32)→SHA384(48)→SHA512(64)

MD5和SHA1是目前最快的算法,尽管其他算法在当前的实现中不超过(大约)慢两倍。为了给出一个大概的数字,在当今的典型桌面或服务器上,使用这些算法中的任何一种都可以预期性能超过每秒100MB。较长的哈希值可降低的可能性(两个不同的文件产生相同的哈希值)。

注意

在散列密码或其他安全敏感数据时使用SHA256。MD5和SHA1被视为不安全的,并且仅适用于防止意外损坏,而不适用于故意篡改。

哈希密码

如果您强制实施强密码策略来缓解字典攻击(攻击者通过对中的每个单词进行哈希处理来构建密码查找表的策略),则较长的SHA算法适合作为密码哈希的基础。

散列密码时,一种标准技术是合并“盐”,即最初通过随机数生成器获得的一长串字节,然后在散列之前与每个密码组合。这在两个方面使黑客感到沮丧:

他们还必须知道盐字节。他们不能使用(公开可用的预先计算的密码及其哈希码数据库),尽管在有足够的能力的情况下,字典攻击仍然是可能的。

您可以通过“延伸”密码哈希来进一步增强安全性-重复重新哈希以获得计算量更大的字节序列。如果你重复100次,否则可能需要一个月的字典攻击将需要八年时间。KeyDerivation、Rfc2898DeriveBytes和PasswordDeriveBytes类正是执行这种拉伸,同时还允许方便的加盐。其中,KeyDerivation.Pbkdf2提供了最好的哈希:

byte[]encrypted=KeyDerivation.Pbkdf2(npassword:"stRhong%pword",nsalt:Encoding.UTF8.GetBytes("j78Y#p)/saREN!y3@"),nprf:KeyDerivationPrf.HMACSHA512,niterationCount:100,nnumBytesRequested:64);注意

KeyDerivation.Pbkdf2需要NuGet包Microsoft.AspNetCore.Cryptography.KeyDerivation。尽管它位于ASP.NETCore命名空间中,但任何.NET应用程序都可以使用它。

对称加密

对称加密使用与解密相同的密钥进行加密。.NETBCL提供了四种对称算法,其中Rijndael(发音为“RhineDahl”或“雨娃娃”)是高级算法;其他算法主要用于与旧应用程序的兼容性。Rijndael既快速又安全,并且有两种实现:

莱恩戴尔级艾斯级

两者几乎相同,只是Aes不允许通过更改块大小来削弱密码。Aes由CLR的安全团队推荐。

Rijndael和Aes允许长度为16、24或32字节的对称密钥:目前都被认为是安全的。下面介绍如何使用16字节密钥在写入文件时加密一系列字节:

byte[]key={145,12,32,245,98,132,98,214,6,77,131,44,221,3,9,50};nbyte[]iv={15,122,132,5,93,198,44,31,9,39,241,49,250,188,80,7};nnbyte[]data={1,2,3,4,5};//Thisiswhatwe'reencrypting.nnusing(SymmetricAlgorithmalgorithm=Aes.Create())nusing(ICryptoTransformencryptor=algorithm.CreateEncryptor(key,iv))nusing(Streamf=File.Create("encrypted.bin"))nusing(Streamc=newCryptoStream(f,encryptor,CryptoStreamMode.Write))nc.Write(data,0,data.Length);

以下代码解密该文件:

byte[]key={145,12,32,245,98,132,98,214,6,77,131,44,221,3,9,50};nbyte[]iv={15,122,132,5,93,198,44,31,9,39,241,49,250,188,80,7};nnbyte[]decrypted=newbyte[5];nnusing(SymmetricAlgorithmalgorithm=Aes.Create())nusing(ICryptoTransformdecryptor=algorithm.CreateDecryptor(key,iv))nusing(Streamf=File.OpenRead("encrypted.bin"))nusing(Streamc=newCryptoStream(f,decryptor,CryptoStreamMode.Read))nfor(intb;(b=c.ReadByte())>-1;)nConsole.Write(b+"");//12345

在这个例子中,我们组成了一个由16个随机选择的字节组成的键。如果在解密中使用了错误的密钥,CryptoStream将抛出CryptographicException。捕获此异常是测试密钥是否正确的唯一方法。

除了密钥之外,我们还组成了一个IV或。这个16字节序列构成了密码的一部分(非常类似于密钥),但不被视为。如果要传输加密邮件,则应以纯文本形式(可能在邮件头中)发送IV,然后。这将使每封加密邮件无法与以前的任何邮件识别,即使它们的未加密版本相似或相同。

注意

如果不需要或不需要IV的保护,则可以通过对密钥和IV使用相同的16字节值来击败它。但是,使用相同的IV发送多条消息会削弱密码,甚至可能使其有可能被破解。

密码学工作在各个类之间划分。艾斯是数学家;它应用密码算法及其加密器和解密器转换。加密流是水管工;它负责溪流管道。您可以使用不同的对称算法替换Aes,但仍使用CryptoStream。

CryptoStream是,这意味着您可以读取或写入流,具体取决于您是选择CryptoStreamMode.Read还是CryptoStreamMode.Write。加密器和解密器都,产生四种组合-选择可能会让您盯着空白屏幕一段时间!将阅读建模为“拉”和写作建模为“推动”会很有帮助。如有疑问,请从写入进行加密和读取以进行解密开始;这通常是最自然的。

要生成随机密钥或IV,请使用System.Cryptography中的RandomNumberGenerator。它产生的数字确实是不可预测的,或者加密(System.Random类不提供相同的保证)。下面是一个:

byte[]key=newbyte[16];nbyte[]iv=newbyte[16];nRandomNumberGeneratorrand=RandomNumberGenerator.Create();nrand.GetBytes(key);nrand.GetBytes(iv);

如果未指定密钥和IV,则会自动生成加密强度高的随机值。可以通过Aes对象的键和IV属性查询这些。

在内存中加密

从.NET6开始,可以利用EncryptCbc和DecryptCbc方法来缩短加密和解密字节数组的过程:

publicstaticbyte[]Encrypt(byte[]data,byte[]key,byte[]iv)n{nusingAesalgorithm=Aes.Create();nalgorithm.Key=key;nreturnalgorithm.EncryptCbc(data,iv);n}nnpublicstaticbyte[]Decrypt(byte[]data,byte[]key,byte[]iv)n{nusingAesalgorithm=Aes.Create();nalgorithm.Key=key;nreturnalgorithm.DecryptCbc(data,iv);n}

以下是适用于all.NET版本的等效项:

publicstaticbyte[]Encrypt(byte[]data,byte[]key,byte[]iv)n{nusing(Aesalgorithm=Aes.Create())nusing(ICryptoTransformencryptor=algorithm.CreateEncryptor(key,iv))nreturnCrypt(data,encryptor);n}nnpublicstaticbyte[]Decrypt(byte[]data,byte[]key,byte[]iv)n{nusing(Aesalgorithm=Aes.Create())nusing(ICryptoTransformdecryptor=algorithm.CreateDecryptor(key,iv))nreturnCrypt(data,decryptor);n}nnstaticbyte[]Crypt(byte[]data,ICryptoTransformcryptor)n{nMemoryStreamm=newMemoryStream();nusing(Streamc=newCryptoStream(m,cryptor,CryptoStreamMode.Write))nc.Write(data,0,data.Length);nreturnm.ToArray();n}

在这里,CryptoStreamMode.Write最适合加密和解密,因为在这两种情况下,我们都“推送”到一个新的内存流中。

以下是接受和返回字符串的重载:

publicstaticstringEncrypt(stringdata,byte[]key,byte[]iv)n{nreturnConvert.ToBase64String(nEncrypt(Encoding.UTF8.GetBytes(data),key,iv));n}nnpublicstaticstringDecrypt(stringdata,byte[]key,byte[]iv)n{nreturnEncoding.UTF8.GetString(nDecrypt(Convert.FromBase64String(data),key,iv));n}

下面演示了它们的用法:

byte[]key=newbyte[16];nbyte[]iv=newbyte[16];nnvarcryptoRng=RandomNumberGenerator.Create();ncryptoRng.GetBytes(key);ncryptoRng.GetBytes(iv);nnstringencrypted=Encrypt("Yeah!",key,iv);nConsole.WriteLine(encrypted);//R1/5gYvcxyR2vzPjnT7yaQ==nnstringdecrypted=Decrypt(encrypted,key,iv);nConsole.WriteLine(decrypted);//Yeah!链接加密流

CryptoStream是一个装饰器,这意味着您可以将其与其他流链接在一起。在以下示例中,我们将压缩的加密文本写入文件,然后将其读回:

byte[]key=newbyte[16];nbyte[]iv=newbyte[16];nnvarcryptoRng=RandomNumberGenerator.Create();ncryptoRng.GetBytes(key);ncryptoRng.GetBytes(iv);nnusing(Aesalgorithm=Aes.Create())n{nusing(ICryptoTransformencryptor=algorithm.CreateEncryptor(key,iv))nusing(Streamf=File.Create("serious.bin"))nusing(Streamc=newCryptoStream(f,encryptor,CryptoStreamMode.Write))nusing(Streamd=newDeflateStream(c,CompressionMode.Compress))nusing(StreamWriterw=newStreamWriter(d))nawaitw.WriteLineAsync("Smallandsecure!");nnusing(ICryptoTransformdecryptor=algorithm.CreateDecryptor(key,iv))nusing(Streamf=File.OpenRead("serious.bin"))nusing(Streamc=newCryptoStream(f,decryptor,CryptoStreamMode.Read))nusing(Streamd=newDeflateStream(c,CompressionMode.Decompress))nusing(StreamReaderr=newStreamReader(d))nConsole.WriteLine(awaitr.ReadLineAsync());//Smallandsecure!n}

(最后,我们通过调用WriteLineAsync和ReadLineAsync来使我们的程序异步,并等待结果。

在此示例中,所有单字母变量构成链的一部分。数学家——算法、加密器和解密器——在那里协助CryptoStream进行密码工作,如图所示。

链接加密和压缩流

无论最终流大小如何,以这种方式链接流都需要很少的内存。

释放加密对象

释放加密流可确保其内部数据缓存刷新到底层流。内部缓存对于加密算法是必需的,因为它们以块的形式处理数据,而不是一次处理一个字节。

CryptoStream的不寻常之处在于它的Flush方法什么都不做。要刷新流(不释放它),您必须调用FlushFinalBlock。与Flush相反,您只能调用FlushFinalBlock一次,然后就不能再写入数据了。

我们还处理了数学家——Aes算法和ICryptoTransform对象(加密器和解密器)。当Rijndael转换被释放时,它们会从内存中擦除对称密钥和相关数据,从而防止随后被计算机上运行的其他软件发现(我们说的是恶意软件)。您不能依赖垃圾回收器来完成此作业,因为它只是将内存部分标记为可用;它不会在每个字节上写入零。

在using语句之外释放Aes对象的最简单方法是调用Clear。它的Dispose方法通过显式实现隐藏(以指示其不寻常的处置语义,从而清除内存而不是释放非托管资源)。

注意

通过执行以下操作,可以进一步降低应用程序通过释放的内存泄露机密的漏洞:

避免使用安全信息的字符串(不可变,字符串的值一旦创建就永远无法清除)在不再需要缓冲区时立即覆盖缓冲区(例如,通过在字节数组上调用Array.Clear)密钥管理

密钥管理是安全的关键要素:如果您的密钥暴露,您的数据也会暴露。您需要考虑谁应该有权访问密钥,以及如何在发生硬件故障时备份它们,同时以防止未经授权的访问的方式存储它们。

不建议对加密密钥进行硬编码,因为存在用于反编译程序集的常用工具,几乎不需要专业知识。更好的选择(在Windows上)是为每个安装制造一个随机密钥,并使用Windows数据保护安全地存储它。

对于部署到云的应用程序,MicrosoftAzure和AmazonWebServices(AWS)提供密钥管理系统,这些系统具有在企业环境中非常有用的其他功能,例如审计跟踪。

如果您正在加密消息流,公钥加密仍然是最佳选择。

公钥加密和签名

公钥加密是,这意味着加密和解密使用不同的密钥。

与对称加密不同,对称加密(任何适当长度的任意字节序列都可以用作密钥),非对称加密需要特制的密钥对。密钥对组件,它们按如下方式协同工作:

公钥对消息进行加密。私钥解密消息。

“制作”密钥对的一方在自由分发公钥的同时保持私钥的秘密。这种类型的加密的一个特点是您无法从公钥计算私钥。因此,如果私钥丢失,则无法恢复加密数据;相反,如果私钥泄露,加密系统将变得无用。

公钥握手允许两台计算机通过公共网络安全地通信,无需事先联系,也没有现有的共享机密。若要了解其工作原理,假设计算机想要向计算机发送机密消息:

生成公钥/私钥对,然后将其公钥发送到。使用Target的公钥对机密消息进行加密,然后将其发送到。使用其私钥解密机密消息。

窃听者将看到以下内容:

的公钥使用的公钥加密的机密消息

但是如果没有的私钥,消息就无法解密。

注意

这并不能阻止中间人攻击:换句话说,无法知道不是恶意方。要对收件人进行身份验证,发起方需要已经知道收件人的公钥,或者能够通过验证其密钥。

由于公钥加密相对较慢且消息大小有限,因此从发送到的机密消息通常包含用于后续加密的新密钥。这允许在会话的剩余时间内放弃公钥加密,转而使用能够处理较大消息的对称算法。如果为每个会话生成新的公钥/私钥对,则此协议特别安全,因为不需要在任一存储密钥。

注意

公钥加密算法依赖于消息小于密钥。这使得它们仅适用于加密少量数据,例如用于后续对称加密的密钥。如果尝试加密远大于密钥大小一半的消息,提供程序将引发异常。

RSA类

.NET提供了许多非对称算法,其中RSA是最受欢迎的。以下是使用RSA加密和解密的方法:

byte[]data={1,2,3,4,5};//Thisiswhatwe'reencrypting.nnusing(varrsa=newRSACryptoServiceProvider())n{nbyte[]encrypted=rsa.Encrypt(data,true);nbyte[]decrypted=rsa.Decrypt(encrypted,true);n}

Becausewedidn’tspecifyapublicorprivatekey,thecryptographicproviderautomaticallygeneratedakeypair,usingthedefaultlengthof1,024bits;youcanrequestlongerkeysinincrementsofeightbytes,throughtheconstructor.Forsecurity-criticalapplications,it’sprudenttorequest2,048bits:

varrsa=newRSACryptoServiceProvider(2048);

生成密钥对是计算密集型的,可能需要100毫秒。因此,RSA实现会延迟此操作,直到实际需要密钥,例如调用Encrypt时。这使您有机会加载现有密钥或密钥对(如果存在)。

方法ImportCspBlob和ExportCspBlob以字节数组格式加载和保存密钥。FromXmlString和ToXmlString以字符串格式(包含XML片段的字符串)执行相同的工作。布尔标志允许您指示在保存时是否包含私钥。以下是制造密钥对并将其保存到磁盘的方法:

using(varrsa=newRSACryptoServiceProvider())n{nFile.WriteAllText("PublicKeyOnly.xml",rsa.ToXmlString(false));nFile.WriteAllText("PublicPrivate.xml",rsa.ToXmlString(true));n}

由于我们没有提供现有密钥,ToXmlString强制制造新的密钥对(在第一次调用时)。在下一个示例中,我们读回这些密钥并使用它们来加密和解密消息:

byte[]data=Encoding.UTF8.GetBytes("Messagetoencrypt");nnstringpublicKeyOnly=File.ReadAllText("PublicKeyOnly.xml");nstringpublicPrivate=File.ReadAllText("PublicPrivate.xml");nnbyte[]encrypted,decrypted;nnusing(varrsaPublicOnly=newRSACryptoServiceProvider())n{nrsaPublicOnly.FromXmlString(publicKeyOnly);nencrypted=rsaPublicOnly.Encrypt(data,true);nn//Thenextlinewouldthrowanexceptionbecauseyouneedtheprivaten//keyinordertodecrypt:n//decrypted=rsaPublicOnly.Decrypt(encrypted,true);n}nnusing(varrsaPublicPrivate=newRSACryptoServiceProvider())n{n//Withtheprivatekeywecansuccessfullydecrypt:nrsaPublicPrivate.FromXmlString(publicPrivate);ndecrypted=rsaPublicPrivate.Decrypt(encrypted,true);n}数字签名

您还可以使用公钥算法对邮件或文档进行数字签名。签名就像一个哈希,只是它的制作需要私钥,因此不能伪造。公钥用于验证签名。下面是一个示例:

byte[]data=Encoding.UTF8.GetBytes("Messagetosign");nbyte[]publicKey;nbyte[]signature;nobjecthasher=SHA1.Create();//Ourchosenhashingalgorithm.nn//Generateanewkeypair,thensignthedatawithit:nusing(varpublicPrivate=newRSACryptoServiceProvider())n{nsignature=publicPrivate.SignData(data,hasher);npublicKey=publicPrivate.ExportCspBlob(false);//getpublickeyn}nn//CreateafreshRSAusingjustthepublickey,thentestthesignature.nusing(varpublicOnly=newRSACryptoServiceProvider())n{npublicOnly.ImportCspBlob(publicKey);nConsole.Write(publicOnly.VerifyData(data,hasher,signature));//Truenn//Let'snowtamperwiththedata,andrecheckthesignature:ndata[0]=0;nConsole.Write(publicOnly.VerifyData(data,hasher,signature));//Falsenn//Thefollowingthrowsanexceptionaswe'relackingaprivatekey:nsignature=publicOnly.SignData(data,hasher);n}

签名的工作原理是首先对数据进行哈希处理,然后将非对称算法应用于生成的哈希。由于哈希的固定大小较小,因此可以相对快速地对大型文档进行签名(公钥加密比哈希占用CPU资源大得多)。如果需要,您可以自己进行哈希处理,然后调用SignHash而不是SignData:

using(varrsa=newRSACryptoServiceProvider())n{nbyte[]hash=SHA1.Create().ComputeHash(data);nsignature=rsa.SignHash(hash,CryptoConfig.MapNameToOID("SHA1"));n...n}

SignHash仍然需要知道你使用了什么哈希算法;CryptoConfig.MapNameToOID以正确的格式从友好名称(如“SHA1”)提供此信息。

RSACryptoServiceProvider生成大小与密钥大小匹配的签名。目前,没有主流算法生成明显小于128字节的安全签名(例如,适用于产品激活码)。

注意

要使签名生效,收件人必须知道并信任发件人的公钥。这可以通过事先的通信、预配置或站点证书来实现。站点证书是发起方公钥和名称的电子记录,其本身由独立的受信任机构签名。命名空间System.Security.Cryptography.X509Certificates定义了用于处理证书的类型。

自w到高c的25种方法和密码学的问题分享结束啦,以上的文章解决了您的问题吗?欢迎您下次再来哦!