密码应用技术系列之1:密码应用技术概述
前言
老张和Apollo分处中美两国,是生意上的合作伙伴。Apollo在美国经营一家商业软件设计公司,他会根据最新的市场需求进行软件产品设计,然后将详细设计方案交由老张的软件外包公司完成软件开发。最初他们是这样交流的:
- Apollo通过邮件/IM工具将具有商业秘密的详细设计方案直接发送给老张;
- 老张根据方案完成软件开发,并将源码以同样的方式发送给Apollo。
这种方式方便快捷,Apollo既可以在美国这样一个处于软件业流行前沿的国家,更好地把握行业发展方向;又可以利用发展中国家的廉价劳动力,降低软件产品成本。
但是,不好的事情很快发生了,每当Apollo设计出一款新产品,马上就会被人山寨出来,有时甚至在老张还没有开发完成前,市面上就已经有雷同的产品在售了。Apollo逐渐意识到互联网在提供方便快捷的同时,也蕴藏着巨大的信息安全风险。他觉得是时候为此做些什么了。
V1.0 - 简单编码
Apollo认为免费的邮箱和IM工具不靠谱,得自己做一个通讯工具,才能按需对信息进行保护。这个东西很简单嘛,就是最基础的网络通讯,Apollo花了一周的业余时间就搞定了。他还对数据做了简单的倒序编码保护,算法如下:
1 /// <summary> 2 /// 简单编码 3 /// </summary> 4 /// <param name="plainText">原文</param> 5 /// <returns>密文</returns> 6 public static string SimpleEncoding(string plainText) 7 { 8 var array = plainText.ToCharArray(); 9 Array.Reverse(array); 10 return new string(array); 11 } 12 13 /// <summary> 14 /// 简单解码 15 /// </summary> 16 /// <param name="cipherText">密文</param> 17 /// <returns>原文</returns> 18 public static string SimpleDecoding(string cipherText) 19 { 20 var array = cipherText.ToCharArray(); 21 Array.Reverse(array); 22 return new string(array); 23 }
当Apollo有新方案要给老张时,他这样对方案信息进行编码:
CryptoUtil.SimpleEncoding("方案");
老张拿到方案后,这样进行解码:
CryptoUtil.SimpleDecoding("案方");
经过这样的编码加密处理后,信息的流转如下图所示:
图1简单编码保护
简单对方案内容倒序编码保护,一开始尚能瞒天过海,但由于编码规则过于简单,很快便被识破了。
V1.1 - 复杂编码
Apollo决定对编码算法进行改善,改用Base64编码保护,算法如下:
1 /// <summary> 2 /// 编码 3 /// </summary> 4 /// <param name="plainText">原文</param> 5 /// <returns>密文</returns> 6 public static string Encoding(string plainText) 7 { 8 var data = System.Text.Encoding.UTF8.GetBytes(plainText); 9 var cipherText = System.Convert.ToBase64String(data); 10 return cipherText; 11 } 12 13 /// <summary> 14 /// 解码 15 /// </summary> 16 /// <param name="cipherText">密文</param> 17 /// <returns>原文</returns> 18 public static string Decoding(string cipherText) 19 { 20 var data = System.Convert.FromBase64String(cipherText); 21 var plainText = System.Text.Encoding.UTF8.GetString(data); 22 return plainText; 23 }
当Apollo有方案要发送给老张时,他这样对信息进行编码:
CryptoUtil.Encoding("方案");
老张拿到方案后,这样进行解码:
CryptoUtil.Decoding("5pa55qGI");
经过这样的编码加密处理后,信息的流转如下图所示:
图2复杂编码保护
传输的信息不仅内容与原文不同,而且长度也不一样,看起来貌似还挺安全的。于是,Apollo与老张决定采用自主研发的这个通讯工具进行信息交互。试运行3个月效果不错,Apollo没有再被山寨问题所烦恼,从而能将精力更多地投入到新产品设计上。但好景不长,不久,问题似乎又有所反复。
V2.0 - 对称加密
Apollo又对问题进行了深入的分析,觉得问题出在每次都按同样的规律进行编码,次数越多,越容易被人猜到编码规则。就好像下一局象棋,马后炮足以一招致胜,但如果每局都用马后炮,那注定不能常胜。
怎么办呢,不断更换规则吗?似乎后期维护太麻烦伤不起;增加编码规则复杂度吗?似乎治标不治本。Apollo清楚问题的实质在于编码技术是基于规则的安全技术,一旦规则曝光,谁都可以解密。因此,他选择了更好的解决方案:使用基于密钥的安全技术——对称加密。
所谓基于密钥的安全,就是在密码算法中引入了密钥因子,使用加密密钥加密的数据,只有提供唯一对应的解密密钥才能解密。对称加密即加密密钥和解密密钥是同一个密钥。Apollo使用对称加密技术后的算法大致如下:
1 /// <summary> 2 /// 对称加密 3 /// </summary> 4 /// <param name="plainText">原文</param> 5 /// <param name="symmetricKey">对称密钥</param> 6 /// <returns>密文</returns> 7 public static byte[] SymmetricEncrypt(byte[] plainText, byte[] symmetricKey) 8 { 9 var cipher = SecurityContext.Current.CipherProvider; 10 using (var encryptor = cipher.CreateEncryptor()) 11 { 12 return encryptor.SymmetricEncrypt(plainText, symmetricKey); 13 } 14 } 15 16 /// <summary> 17 /// 对称解密 18 /// </summary> 19 /// <param name="cipherText">密文</param> 20 /// <param name="symmetricKey">对称密钥</param> 21 /// <returns>原文</returns> 22 public static byte[] SymmetricDecrypt(byte[] cipherText, byte[] symmetricKey) 23 { 24 var cipher = SecurityContext.Current.CipherProvider; 25 using (var encryptor = cipher.CreateEncryptor()) 26 { 27 return encryptor.SymmetricDecrypt(cipherText, symmetricKey); 28 } 29 }
这样,Apollo和老张约定一个对称密钥,当Apollo有新方案要发送给老张时,他就用这个固定的对称密钥进行加密:
1 var plainText = Encoding.UTF8.GetBytes("方案"); 2 var symmetricKey = Apollo.Common.Utils.FileUtil.ReadFile("SymmetricKey.dat"); 3 var cipherText = CryptoUtil.SymmetricEncrypt(plainText, symmetricKey);
Apollo将加密后的方案文件传输给老张,老张拿到密文和密钥后,这样解密:
1 var symmetricKey = Apollo.Common.Utils.FileUtil.ReadFile("SymmetricKey.dat"); 2 var plainText = CryptoUtil.SymmetricDecrypt(cipherText, symmetricKey);
经过对称加密后的方案传输情况如下图所示:
图3对称加密保护
经过对称加密后,密文表现为一系列毫无意义的二进制数,想来应该是相当安全了。事实证明了这点,Apollo又安身了大半年。但最终还是道高一尺魔高一丈,方案在一段时间后又出现了泄露问题,这是为什么呢?
V2.1 - 动态密钥对称加密
任何事情一旦重复多次,都会被人找到规律,这是目前问题的关键。Apollo每次都用相同的对称密钥加密数据,次数越多越容易被他人发现规律,从而破解。Apollo想出了一个巧妙的解决办法,他每次加密都对当日的日期作SHA1摘要运算,得到20字节的摘要值,并用0在其后填充12个字节,最终得到32字节的对称密钥。对称加解密算法调整为:
1 /// <summary> 2 /// 根据当前日期产生对称密钥 3 /// </summary> 4 /// <returns></returns> 5 private static byte[] GenerateSymmetricKey() 6 { 7 var sha1 = SHA1.Create(); 8 var data = Encoding.Default.GetBytes(DateTime.Now.ToShortDateString()); 9 var symmetricKey = sha1.ComputeHash(data); 10 Apollo.Common.Utils.ByteUtil.Append(ref symmetricKey, new byte[12]); 11 12 return symmetricKey; 13 } 14 15 /// <summary> 16 /// 对称加密 17 /// </summary> 18 /// <param name="plainText">原文</param> 19 /// <returns>密文</returns> 20 public static byte[] SymmetricEncrypt(byte[] plainText) 21 { 22 var symmetricKey = GenerateSymmetricKey(); 23 return SymmetricEncrypt(plainText, symmetricKey); 24 } 25 26 /// <summary> 27 /// 对称解密 28 /// </summary> 29 /// <param name="cipherText">密文</param> 30 /// <returns>原文</returns> 31 public static byte[] SymmetricDecrypt(byte[] cipherText) 32 { 33 var symmetricKey = GenerateSymmetricKey(); 34 return SymmetricDecrypt(cipherText, symmetricKey); 35 }
相应地,Apollo发送方案时的做法调整为:
1 var plainText = Encoding.UTF8.GetBytes("方案"); 2 var cipherText = CryptoUtil.SymmetricEncrypt(plainText);
Apollo将加密后的方案文件传输给老张,然后电话告诉老张密钥。老张拿到密文和密钥后,这样解密:
var plainText = CryptoUtil.SymmetricDecrypt(cipherText);
这种方式实现了更为安全的一次一密,而且Apollo和老张都不用再关心密钥的事情了。但这个算法并不是无懈可击的,它的短板就在密钥本身——密钥的产生是有规律性的(就是本节提到的密钥产生算法),这实际是把基于规则的原文安全转移为了基于规则的密钥安全,只是密钥不需要分发。
Apollo也想过用随机数作为密钥,这样就能补上这块短板,但随之而来的是繁琐的密钥分发管理工作(每次加密使用的对称密钥都要告知老张)。
V3.0 - 对称+非对称加密
Apollo现在是左右为难。一方面,如果沿用目前的算法,密钥的机密性短板问题可能让他所有的努力功亏一篑;另一方面,如果改为随机的一次一密,密钥分发问题同样令他头大。Apollo决定迎难而上,使用对称+非对称加密技术来解决对称密钥的分发问题。
在开始对称+非对称加密技术之前,让我们先来了解下非对称加密。所谓非对称加密,是指加密和解密运算所使用的密钥是不相同,且总是成对的。其中一个密钥叫私钥,由密钥所有人唯一持有;另一个密钥叫公钥,对所有人公开。使用其中一个密钥加密的数据,仅能由配对的另一个密钥解密。
非对称加密的以上特性给Apollo提供了强有力的技术支持。他产生了两对RSA1024密钥对,一对自己留用,一对给老张,并且把老张的公钥保留了一份,把自己的公钥也一并给了老张。Apollo将加解密密算法调整为:
1 /// <summary> 2 /// 对称加密 3 /// </summary> 4 /// <param name="plainText">原文</param> 5 /// <param name="symmetricKey">对称密钥</param> 6 /// <returns>密文</returns> 7 public static byte[] SymmetricEncrypt(byte[] plainText, out byte[] symmetricKey) 8 { 9 var cipher = SecurityContext.Current.CipherProvider; 10 using (var keyGenerator = cipher.CreateKeyGenerator()) 11 { 12 symmetricKey = keyGenerator.GenerateRandom(32); 13 } 14 15 return SymmetricEncrypt(plainText, symmetricKey); 16 } 17 18 /// <summary> 19 /// 对称解密 20 /// </summary> 21 /// <param name="cipherText">密文</param> 22 /// <param name="symmetricKey">对称密钥</param> 23 /// <returns>原文</returns> 24 public static byte[] SymmetricDecrypt(byte[] cipherText, byte[] symmetricKey) 25 { 26 var cipher = SecurityContext.Current.CipherProvider; 27 28 using (var encryptor = cipher.CreateEncryptor()) 29 { 30 return encryptor.SymmetricDecrypt(cipherText, symmetricKey); 31 } 32 } 33 34 /// <summary> 35 /// 非对称加密 36 /// </summary> 37 /// <param name="plainText">原文</param> 38 /// <param name="publicKey">加密公钥</param> 39 /// <returns>密文</returns> 40 public static byte[] AsymmetricEncrypt(byte[] plainText, PublicKey publicKey) 41 { 42 var cipher = SecurityContext.Current.CipherProvider; 43 44 using (var encryptor = cipher.CreateEncryptor()) 45 { 46 return encryptor.AsymmetricEncrypt(plainText, publicKey); 47 } 48 } 49 50 /// <summary> 51 /// 非对称解密 52 /// </summary> 53 /// <param name="cipherText">密文</param> 54 /// <returns>明文</returns> 55 public static byte[] AsymmetricDecrypt(byte[] cipherText) 56 { 57 var cipher = SecurityContext.Current.CipherProvider; 58 using (var encryptor = cipher.CreateEncryptor()) 59 { 60 return encryptor.AsymmetricDecrypt(cipherText); 61 } 62 }
这样,当他有新方案要发送给老张时,使用上面的对称加密算法加密方案文件,同时获得一个随机对称密钥:
1 var data = Encoding.UTF8.GetBytes(plainText); 2 byte[] symmetricKey = null; 3 var cipherText = CryptoUtil.SymmetricEncrypt(data, out symmetricKey);
然后用老张的公钥加密该对称密钥:
var encryptedSymmetricKey = CryptoUtil.AsymmetricEncrypt(symmetricKey, publicKey);
完成后,将方案文件密文和对称密钥密文发给老张。老张拿到这两件东西后,首先用自己的私钥解密对称密钥密文,获得对称密钥原文:
var symmetricKey = CryptoUtil.AsymmetricDecrypt(encryptedSymmetricKey);
然后用对称密钥解密方案密文,得到方案原文:
var plainText = CryptoUtil.SymmetricDecrypt(cipherText, decryptedSymmetricKey);
这套对称+非对称加密保护方案的信息传递过程如下图所示:
图4对称+非对称加密保护
V3.1 - 数字信封
对称+非对称加密保护方案技术上已经能保证方案的机密性了,但数据的发送仍略显繁琐(需要将对称密钥密文和方案文件密文分别发给老张,并且要告知老张哪个是对称密钥密文,哪个是方案文件密文)。Apollo是一个怕麻烦的人,所以他决定努力解决这个问题。
经过一翻研究,Apollo了解到PKCS #7(RFC2315)标准中已经定义了数字信封的ASN1数据结构,其主要结构定义如下:
EnvelopedData ::= SEQUENCE { version Version, // 语法版本 recipientInfos RecipientInfos, // 接收者信息 encryptedContentInfo EncryptedContentInfo, // 数据密文 }
EnvelopedData数据的生成与解析需要数字证书(参见X.509标准)支持,为此,Apollo向第三方运营CA申请购买了两份证书及私钥(参见PKCS#12标准)。Apollo将加解密密算法调整为:
1 /// <summary> 2 /// 封装数字信封 3 /// </summary> 4 /// <param name="plainText">原文</param> 5 /// <param name="cert">接收方证书</param> 6 /// <returns>数字信封</returns> 7 public static byte[] ToEnvelopedData(byte[] plainText, X509Certificate cert) 8 { 9 var cipher = SecurityContext.Current.CipherProvider; 10 11 using (var encryptor = cipher.CreateEncryptor()) 12 { 13 var envelopedData = encryptor.ToEnvelopedData(plainText, cert); 14 return envelopedData.GetDerEncoded(); 15 } 16 } 17 18 /// <summary> 19 /// 拆开数字信封 20 /// </summary> 21 /// <param name="cipherText">数字信封</param> 22 /// <returns>原文</returns> 23 public static byte[] FromEnvelopedData(byte[] cipherText) 24 { 25 var cipher = SecurityContext.Current.CipherProvider; 26 var envelopedData = EnvelopedData.GetInstance(Asn1Object.FromByteArray(cipherText)); 27 28 using (var encryptor = cipher.CreateEncryptor()) 29 { 30 return encryptor.FromEnvelopedData(envelopedData); 31 } 32 }
这样,当他有新方案要发送给老张时,使用上面的算法将方案文件封装为数字信封:
1 var data = Encoding.UTF8.GetBytes(plainText); 2 var cert = X509Certificate.Parse(FileUtil.ReadFile(@"..\..\..\Reference\老张.cer")); 3 var cipherText = CryptoUtil.ToEnvelopedData(data, cert);
老张拿到数字信封后,使用上面的算法拆开数字信封,得到方案原文:
CryptoUtil.FromEnvelopedData(cipherText);
数字信封加密保护方案的信息传递过程如下图所示:
图5数字信封加密保护
走到这一步,Apollo表示非常满意了,既实现了一次一密,使数据的机密性得到保障;又不用为密钥的分发问题操心,真的是一劳永逸了。
V4.0 - 未完待续
虽然Apollo的通讯工具已更新到了V3.x版本,但问题只解决了一半。截至目前,他所做的所有努力只解决了方案明文不被非法获取,即保证了方案数据的机密性。除此之外,其实还存在以下安全需求:
一、真实性
真实性也叫不可抵赖性,指的是从数据本身就能识别出该数据源于何人。保证数据的真实性很重要,首先,老张在收到一个方案数据时,需要确认是否来自Apollo,而不是被假冒的;其次,一旦发生纠纷,Apollo不能否认该方案不是来自他的。
二、完整性
数据的完整性保护指的是,接收方获取数据后,可以验证数据是否和源数据一致,从而确定数据是否被篡改。
数据的真实性和完整性保护一般采用数字签名技术来实现。PKCS #7(RFC2315)中定义的SignedData类型可用于封装数字签名数据;另外,PKCS #7(RFC2315)中还定义了SignedAndEnvelopedData类型,可用于封装数字签名和数字信封结构数据。
三、时效性
再想多点的话,数据还需要保证时效性,即需要证明该数据是在哪个时间产生的。这样,一旦发生纠纷,Apollo便不能否认他发送方案数据给老张的时间。
除此之外,该通讯工具还有一个短板,即证书及私钥是以文件的形式存在通讯双方的电脑中的,一旦文件被非法窃取,便无安全可言。为此,可以考虑将证书及私钥文件改为使用USBKey硬件,这样便可杜绝私钥被复制的风险,做到绝对安全。
总结
本文以叙事的形式,循序渐进的讲述了密码应用技术的发展过程,其中包含了古典密码学和现代密码学的典型密码技术,也包含了一些信息安全技术。如果你没有这方面的基础,可能会看得有点晕。不过没有关系,本文只是对上述各种技术的概要论述,其中许多细节均未涉及,后续将针对这些技术作专题论述,并在结束篇中实现本文最终设计的通讯工具(包括未完待续部分)。希望本文对你了解密码应用技术有一定的帮助。