zoukankan      html  css  js  c++  java
  • 密码应用技术系列之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版本

    作者:张博
    出处:http://yilin.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在页面明显位置给出原文链接,否则保留追究法律责任的权利。
  • 相关阅读:
    jquery点击tr换背景颜色
    poi导出excel
    Lua 可变参数 ... 的一点测试
    改写Unity DropDown 支持多次点击同一选项均回调
    g++ 生成C++ .so库文件,并调用示例
    Unity NavMesh 格式 解析 分析 对比 Recast Navigation
    SVN 问题解决之 Working copy path does not exist in repository
    SVN 问题解决之 The XML response contains invalid XML
    C++ DLL debug版本在其他PC上缺少依赖的处理方式
    Unity 宽度适配 NGUI
  • 原文地址:https://www.cnblogs.com/yilin/p/apollo_blog_cryptography_chapter1.html
Copyright © 2011-2022 走看看