罗伟雄++时东晓++曾纪霞++刘岚++郭柏乔
摘要:随着数据加密技术的不断发展,计算机处理能力的不断增强,以往许多被认为比较安全的数据加密算法,都相继被破解。针对这些情况,此文首先详细分析了私钥加密算法、公钥加密算法、数字签名技术和哈希函数的特点以及在使用过程中需要注意的问题,然后提出了一种综合使用私钥、公钥、数字签名等技术的增强型加密服务架构。此架构利用公钥算法的特点为私钥算法生成密钥,有效降低了因私钥泄露而导致的威胁,利用数字签名技术可有效降低数据被非法篡改的可能性。
关键词:数据加密;私钥加密;公钥加密;数字签名;哈希算法
中图分类号:TP309
文献标识码:A
DOI: 10.3969/j.issn.1003-6970.2015.08.012
0 引言
当在软件开发过程中往往会用到许多数据加密技术,例如使用哈希函数加密密码,使用数字签名技术验证文本是否被篡改等等。在.NET Framework下,系统提供了一套相对完整可靠的加密服务。但是随着数据加密技术的不断发展,计算机处理能力的不断增强,以往被认为比较安全的数据加密算法,都纷纷暴露其缺陷,有些甚至已经宣布被破解。例如2004年我国密码学家王小云教授利用碰撞方法实现了对MD5、SHA-1和RIPEMD的破解。那么在.NET Framework下,该如何更为安全地使用这些加密服务,如何构建相对安全的加密服务架构,是软件开发过程中必须考虑和深究的问题。
1 加密服务类型
在使用.NET Framework平台的开发过程中最常用到的是以下四种加密服务:
(1)私钥加密。又称为对称加密,它使用单个共享的密钥来加解密数据。
(2)公钥加密。又称为非对称加密,它使用两个密钥来分别对数据进行加解密。
(3)数字签名。它的作用是验证收到的数据是否被非法篡改。
(4)哈希加密。其作用是生成数据的摘要信息。
2 私钥加密算法
在.NET Framework 3.5及以上版本中支持DES,RC2,Rij ndael,TripleDES和AES等私钥加密算法,这些都属于分组加密算法。表1列出了这些算法的基本信息。
其中DES算法是使用较为广泛的私钥加密算法,但是由于DES算法的密钥长度只有64位,以现代计算机的计算能力来说,一天左右的时间就可以破解,因此在实际的应用中应该使用安全性相对较高的算法,如AES算法。由于AES算法支持128位以上长度的密钥,其安全性比DES要高,目前该算法已经成为替代DES算法的新标准。
2.1 块密码模式
以上的私钥加密算法在使用时可以选择不同的块密码模式并设置初始向量IV,其目的是提高系统的安全性。块密码在一定程度上决定了加密的强度。当前.NET Framework只支持CBC、CFB和ECB三种模式。
CBC模式:密码块链模式。该模式引入了反馈。每个纯文本块在加密前,通过按位“异或”操作与前一个块的密码文本结合。这样确保了即使纯文本包含许多相同的块,这些块中的每一个也会加密为不同的密码文本块。在加密块之前,初始化向量通过按位“异或”操作与第一个纯文本块结合。
CFB模式:密码反馈模式。该模式将少量递增的纯文本处理成密码文本,而不是一次处理整个块。
ECB模式:电子密码本模式。该模式是分别加密每个块。这意味着任何纯文本块只要相同,并且在同一消息中,或者在用相同的密钥加密的不同消息中,都将被转换成同样的密码文本块。
通过上面的比较可以看到,ECB模式安全性相对较差,所以在实际应用中尽量不要选择该模式,另外在.NET Framework中AES算法不支持CFB模式。
2.2 填充模式
对于分组加密算法,由于是将明文分成固定长度的多个块再进行处理,如果最后一个块的字节数小于块的长度,则需要进行填充。在.NET Framework中可选的填充模式有五种,而默认使用的是PKCS7模式。这五种模式分别是:
ANSIX923:该模式下填充字符串由一个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节均填充数字零。
IS010126:该模式下填充字符串由一个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节填充随机数据。
None:不填充。
PKCS7:该模式下填充字符串由一个字节序列组成,每个字节填充该字节序列的长度。
Zeros:该模式下填充字符串由设置为零的字节组成。
2.3 加解密流程
在.NET Framework中使用私钥加密算法就是把数据写入使用了某种私钥算法的加密流中;而解密则是从加密流中读出数据,算法类型的选择通过接口ICryptoTransform指定。私钥加密算法的加密流程如图1所示。
(1)定义密钥Key和初始向量IV;
(2)构建内存流;
(3)创建加密接口;
(4)使用加密接口创建加密流并套接在内存流上;
(5)创建流编写器并套接在加密流上;
(6)通过流编写器把明文写入加密流中;
(7)从内存流中读取密文;
(8)、清空敏感数据
以AES算法为例,其加密的核心代码如下:
Aes aes= new AesManaged();//创建AES加密服务对象
aes.Mode=cipMode;//设置块密码模式
aes.Key= key;//设置密钥
aes.IV= iv;//设置初始向量
aes.Padding= PaddingMode.PKCS7; //设置填充模式
MemoryStream ms= new MemoryStream();//创建:内存流
ICryptoTransform tran= aes.CreateEncryptor();//创建加密接口
CryptoStream cs=new CryptoStream(ms, tran, CryptoStreamMode.Write);//创建加密转换流
StreamWriter sw=new StreamWriter(cs);//创建编写器
sw.Write(m);//把明文写入流进行加密
sw.Flush();
cs.FlushFinalBlock();
string enString=Convert.ToBase64String(ms.ToArray());//转换为Base64字符串
aes.Clear();//清空敏感数据
解密操作与加密类似,也是先设置块密码模式、初始向量、填充模式、密钥等基本信息,然后再进行解密,其流程如图2所示。
(1)定义密钥Key和初始向量IV;
(2)构建内存流;
(3)把密文写入内存流中;
(4)创建解密接口;
(5)使用解密接口创建解密流并套接在内存流上;
(6)创建流读取器并套接在解密流上;
(7)通过流读取器从解密流中读取明文;
(8)清空敏感数据。
以AES算法为例,其解密的核心代码如下:
MemoryStream ms= new MemoryStream();//创建;内存流
ms.Write(enByte,0,enByte.Length);//把密文写入内存流中
ms.Position=0://重新定位内存流
ICryptoTransform tran= aes.CreateDecryptor();//创建解密接口
CryptoStream cs=new CryptoStream(ms,tran,Crypto StreamMode.Read);//创建解密转换流
StreamReader sr= new StreamReader(cs);//创建读取器
string deString=sr.ReadToEnd();//从流中读取明文
aes.Clear()//清空敏感数据
这里要注意的是,加解密完成后必须使用对应的Clear方法清空内存中的敏感信息,否则攻击者有可能通过读取内存数据的方法实施破解。另外对象清零后,要调用Dispost方法以释放与对象关联的所有资源。
3 公钥加密算法
在.NET Framework中有三种公钥加密算法,但只有RSA算法能用于数据加密,而DSA算法只能用于数字签名,DiffieHellman算法只能用于生成密钥。
公钥加密算法的使用最困难的是配置密钥对,而密钥从一定层面上决定了算法的安全性。在.NETFramework下,公钥加密算法的密钥一般由系统生成,然后把密钥保存到密钥文件中,需要时再从密钥文件中导人。例如下面的代码演示了生成RSA密钥的过程。
RSACryptoServiceProvider rsa= new RSACryptoServiceProvider();//构建RSA加密服务对象
Console.WriteLine(rsa.ToXmlString(true》;//输出系统生成的公钥和私钥
在公钥加密算法中,如果明文信息比较短则容易受到攻击,为了增强其安全性,算法通过为短信息填充伪数据来加大破解的难度。在.NET Framework中RSA算法支持OAEP和直接加密两种填充模式。
使用RSA算法进行数据加密的流程一般是:
(1)创建公钥加密算法服务对象;
(2)导人或生成公开密钥;
(3)把明文转换为字节数组;
(4)调用加密函数加密数据;
(5)清空敏感数据。
其核心代码如下:
RSACryptoServiceProvider rsa= new RSACryptoServiceProvider();//构建:RSA加密服务对象
rsa.FromXmlString(pubKey);//导人公开密钥
byte[] dataToEncrypt= Encoding.UTF8.GetBytes(m);//把明文转换为字节数组
byte[] enByte=rsa.Encrypt(dataToEncrypt, DoOAEPPadding);//设置填充模式并加密
string enString=Convert.ToBase64String(enByte);//把字节数组转换为Base64编码的字符串
rsa.Clear()//清空敏感数据
其中加密函数Encrypt的第二个参数DoOAEPPadding,是用于设置填充模式的,此值为true,则表示使用OAEP填充,否则直接加密。
解密的流程与加密类似,只是调用的方法不同,一般为:
(1)创建公钥加密算法服务对象;
(2)导人私密密钥;
(3)把密文转换为字节数组;
(4)调用解密函数解密数据;
(5)清空敏感数据。
其核心代码如下:
RSACryptoServiceProvider rsa= new RSACryptoServiceProvider();//构建RSA加密服务对象
rsa.FromXmlString(priKey);//导人私密密钥
byte[] dataToDecrypt= Convert.FromBase64String(enString);//把Base64编码的密文字符串转换为密文字节数组
byte[] deByte=rsa.Decrypt(dataToDecrypt, DoOAEPPadding);//设置填充模式并解密
string deString= Encoding.UTF8.GetString(deByte);//把明文字节数组转换为明文字符串
rsa.Clear()//清空敏感数据
同样Decrypt函数的第二个参数DoOAEPPadding也是用于设置填充模式的。
4 数字签名算法
在.NET Framework中可以使用RSA、DSA和ECD算法进行数字签名,这三种算法的数字签名函数均为SignData和SignHash。前者是计算指定数据的哈希值并对其签名,而后者则是计算指定哈希值的签名。两者对应的验证函数分别是VerifyData和VerifyHash。
其中ECD算法是椭圆曲线数字签名算法,其安全性比其他两种要高。另外在密钥管理方面ECD算法与其他两种算法有所不同。ECD算法的密钥是由系统统一管理的,不需要用户自行存储,这样可以减少因私密密钥泄露而导致的威胁。ECD算法的密钥由类CngKey管理,该类也提供了对密钥的导出,但是如果要导出私密密钥,则要在创建CngKey对象实例时传人CngKeyCreationParameters对象实例,并设置其ExportPolicy属性为AllowPlaintextArchiving或AllowPlaintextExport。另外使用带密钥名称的方式创建密钥后,系统会将其自动保存,待需要时可以使用CngKey.Open方法打开,如果要删除,则可以使用Delete方法删除。下面的代码演示了密钥的创建和导出。
CngKeyCreationParameters creationParameters= new CngKeyCreationParameters();//密钥的高级属性
creationParameters.KeyCreationOptions=CngKeyCreationOptions.OverwriteExistingKey;
creationParameters.ExportPolicy=CngExportPolicies.AllowPlaintextExport;,/设置策略允许私钥以纯文本形式导出多次
CngKey cngKey=CngKey.Create(CngAlgorithm.ECDsaP256,”ECDKey”,creationParameters);//创建密钥
byte[] privateKey=cngKey.Export(CngKeyBlobFormat.EccPrivateBlob);//导出私密密钥
byte[] publicKey=cngKey.Export( CngKeyBlobFormat.EccPublicBlob);//导出公开密钥
cngKey.Delete();//删除系统存储的密钥
使用ECD算法进行数字签名的流程一般为:
(1)构建ECD加密服务对象;
(2)导人或打开私密密钥;
(3)把文本转换为字节数组;
(4)对文本进行数字签名;
(5)清空敏感数据。
其核心代码如下所示:
CngKey cngkey=CngKey.Import(privateKey,CngKeyBlobFormat.EccPrivateBlob);//导人私有密钥
ECDsaCng ecd= new ECDsaCng(cngkey);//构建ECD加密服务对象
byte[] dataToSign= Encoding,UTF8.GetBytes(m);//把明文转换为字节数组
byte[] enByte=ecd.SignData(dataToSign);//数字签名
ecd.Clear()∥清空敏感数据
而验证签名时对方需要先导人公开密钥,然后再进行验证。其核心代码如下。
CngKey cngkey=CngKey.Import(publicKey,CngKeyBlobFo rmat.EccPublicBlob);//导人公开密钥
ECDsaCng ecd= new ECDsaCng(cngkey);//构建ECD加密服务对象
byte[] dataToSign= Encoding.UTF8.GetBytes(m);//把明文转换为字节数组
bool verify=ecd.VerifyData(dataTo Sign,dataToVerify);//验证数字签名
ecd.Clear()//清空敏感数据
另外ECD算法还提供了SignHash和VerifyHash方法对明文的摘要进行签名和验证,其使用方式与SignData和VerifyData类似,只是传人的是明文的摘要而不是明文,这里就不再赘述。
5 哈希算法
在.NET Framework中提供了MD5、SHA-1、SHA-2和RIPEMD等多种哈希算法,其计算哈希值的函数名称均为ComputeHash。2004年我国密码学家王小云教授已经宣布MD5、SHA-1和RIPEMD被破解,因此这些哈希算法已经是不安全的,所以在实际开发中建议使用SHA-2等算法。
生成哈希值的流程是:首先创建Hash转换器,然后调用ComputeHash方法。下面是使用SHA-2生成哈希值的核心代码。
byte[] dataToHash= Encoding.UTF8.GetBytes(m);//转换明文为字节数组
SHA256 sha=SHA256.Create()∥构建Hash转换器
byte[] hash= sha.ComputeHash(dataToHash);//计算哈希值
由于相同的明文使用相同的哈希算法产生的哈希值都是相同的,因此极容易遭受已知密文的攻击。例如,在许多系统中,用户的密码大部分都是用其哈希值存储,如果不做特殊处理,两个相同密码存储的哈希值也必定相同。基于安全考虑,用户更希望即使密码相同,但存储的哈希值却不同。要实现这种效果,可以在计算过程中做一些特殊处理。例如在明文中添加一些附加信息,然后把明文和附加信息作为整体,一起计算其哈希值。而这些附加信息可以使用与用户相关的数据,如用户名、身份证号等。当然为了提高系统的安全性,可以使用由系统随机产生的数据作为附加信息。在.NET Framework下可以使用RNGCryptoServiceProvider类来产生随机数。该类所生成的随机数安全性较高,其输出的可推算概率不高于50%;即任何推算下一个输出位的方法的成功概率低于随机猜测。下面是使用RNGCryptoServiceProvider生成随机数的代码。
byte[] randomBytes=new byte[size];//创建字节数组
RNGCryptoServiceProvider.Create().GetBytes(randomBytes);//生成随机数6增强型加密服务架构
由于私钥算法的加密速度要比公钥加密算法快,特别是对大数据量来说更是如此,所以在实际数据传输过程中以私钥加密算法使用居多。下面笔者以私钥加密算法为基础,提出如何结合公钥加密算法和数字签名算法构建安全的加密服务架构。
通过前面的分析可以看到,私钥加密算法的安全性很大程度上取决于对密钥和初始向量的保护。所以在.NET Framework中如何更加安全地使用私钥加密算法就转换为如何保护好私密密钥和初始向量。为此增强型的加密服务架构如下。
首先通讯双方都已经知道对方的RSA公钥和ECD公钥,这里分别记为RA、RB、EA和EB。通讯前,用户A会使用DiffieHellman算法生成公钥Al和A2,然后使用用户B的RSA公钥RB以及RSA算法对Al和A2进行加密,然后使用ECD算法对加密结果进行数字签名,接着把加密的结果和签名发给用户B。用户B收到后,首先使用用户A的ECD公钥EA以及ECD算法验证签名是否正常,正常后再使用RSA算法解密出公钥Al和A2,然后使用DiffieHellman算法生成自己的公开密钥Bl和B2,并结合密钥Al和A2生成相应的两个私密密钥,分别作为私钥算法的Key和初始向量IV,接着把Bl和B2使用用户A的RSA公钥RA以及RSA算法加密并使用ECD算法对加密结果进行数字签名,然后把加密结果和签名发给用户A。用户A收到后以同样的方法进行数字签名的验证并解密出密钥Bl和B2,然后使用DiffieHellman算法并结合Bl和B2生成两个私密密钥,这两个密钥和用户B所生成的两个私密密钥是相同的。同样这两个密钥分别作为私钥算法的Key和初始向量IV。然后用户A和用户B使用该Key和IV以及AES算法对数据进行加密,然后使用ECD算法进行签名,接着发送给对方。对方收到后首先使用ECD算法进行签名验证,然后使用AES算法进行解密得到明文数据。整个流程如图3所示。
其中使用DiffieHellman算法生成密钥的流程一般是:
(1)构建ECDiffieHellman对象;
(2)设置生成密钥的参数;
(3)获取自己的公开密钥;
(4)传输自己的公开密钥给对方;
(5)接收对方的公开密钥;
(6)根据对方的公开密钥生成私密密钥;
(7)清空敏感数据。
其核心代码如下:
ECDiffieHellmanCng ecd= new ECDiffieHellmanCng();//构建ECDiffieHellman对象
ecd.KeyDerivationFunction= ECDiffieHellmanKeyDerivationFunction.Hash;//设置密钥派生函数
ecd.HashAlgorithm= CngAlgorithm.Sha256;,/设置生成密钥所使用的哈希算法
byte[] senderPublicKey=ecd.PublicKey.ToByteArray();//获取自己的公开密钥
byte[] receiverPublicKey=SendKey(senderPublicKey);//传输自己的公开密钥给对方并获取对方的公开密钥
byte[] senderPrivateKey=ecd. DeriveKeyMaterial (CngKey. Import receiverPublicKey, CngKeyBlobFormat.EccPublicBlob));//根据对方的公开密码生成私密密钥
代码中要设置生成密钥所使用的哈希算法,这里建议使用SHA-2算法较为安全。另外要注意DiffieHellman算法生成的密钥长度与AES算法的密钥和初始向量的长度匹配问题,一般可通过截取或重复叠加的方式处理。
此架构中,私密密钥和初始向量都是由DiffieHellman算法生成的,并没有在信道上传输,这样可有效降低因私密密钥泄露而导致的威胁。另外,由于对应的私密密钥要结合双方的密钥信息方可生成,因此安全性较高。而且在传输公钥过程中,系统还对密钥进行了加密和数字签名,这样有效降低了密钥被非法窃取和篡改的可能。同样在数据传输过程中,系统对信息进行了加密和数字签名,这样有效提高了数据的安全性和可靠性。整个架构综合运用了私钥加密算法、公钥加密算法和数字签名技术各自的优点,取长补短,有效提高了系统的安全性和可靠性。
7 结论
本文对在NET Framework中如何更加安全可靠地使用私钥加密算法、公钥加密算法、数字签名算法和哈希算法做了详细的分析和介绍,并且提出了一种综合运用私钥加密算法、公钥加密算法和数字签名算法的增强型加密服务架构。该架构充分利用这些算法的优点,有效降低了因私密密钥泄露而导致的威胁,降低了信息被非法窃取和篡改地可能,有效提高了系统的安全性和可靠性。