密碼應用技術系列之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版本


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM