密码应用技术系列之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硬件,这样便可杜绝私钥被复制的风险,做到绝对安全。

 

总结

本文以叙事的形式,循序渐进的讲述了密码应用技术的发展过程,其中包含了古典密码学和现代密码学的典型密码技术,也包含了一些信息安全技术。如果你没有这方面的基础,可能会看得有点晕。不过没有关系,本文只是对上述各种技术的概要论述,其中许多细节均未涉及,后续将针对这些技术作专题论述,并在结束篇中实现本文最终设计的通讯工具(包括未完待续部分)。希望本文对你了解密码应用技术有一定的帮助。

 

附件下载:示例源码+本文PDF版本

posted @ 2013-10-14 21:48 Apollo.NET 阅读(...) 评论(...) 编辑 收藏