Communicator 類別的程式碼分析 (CNG 範例)
Communicator.cs 檔案封裝入 Cryptography Next Generation (CNG) 安全通訊範例的加密和解密方法,其中僅包含一個類別,名為 Communicator。這個類別包含下列小節中所討論的成員和方法:
類別成員
類別建構函式
Dispose 方法
StoreDSKey 方法
Send_or_Receive_PublicCryptoKey 方法
SendMessage 方法
ReceiveMessage 方法
如需範例概觀和本主題所提及版本的說明,請參閱 Cryptography Next Generation (CNG) 安全通訊範例。
類別成員
Communicator 類別包含下列 Private 成員:
CngKey m_DSKey
這個成員是由 Communicator.StoreDSKey 用來儲存數位簽章金鑰。
ECDiffieHellmanCng m_ECDH_Cng
這個成員是由建構函式用來儲存 ECDiffieHellmanCng 類別的執行個體。ReceiveMessage 和 SendMessage 方法會使用這個成員衍生金鑰內容。
string m_ECDH_local_publicKey_XML
這個成員是由建構函式用來儲存本機公用 Elliptic Curve Diffie-Hellman (ECDH) 密碼編譯金鑰的 XML 字串表示。Alice、Bob 和 Mallory 會使用 Send_or_Receive_PublicCryptoKey 方法交換這個 XML 字串。
ECDiffieHellmanPublicKey m_ECDH_remote_publicKey
這個成員是由 Send_or_Receive_PublicCryptoKey 方法用來儲存遠端公用 ECDH 密碼編譯金鑰。
Communicator 類別也會提供下列 Public 成員:
ChannelManager ChMgr
這個成員是由 Alice、Bob 和 Mallory 用來提供具名管道服務。
類別建構函式
public Communicator(string mode, string ChannelName)
建構函式會建立下列三個物件:
ECDiffieHellmanCng 類別的執行個體,搭配使用金鑰大小為 521 位元的隨機金鑰組:
m_ECDH_Cng = new ECDiffieHellmanCng(521)
產生的物件會繫結到 Communicator 類別做為 Private 成員。每個 Communicator 物件會建立這個類別的單一執行個體。
Alice 和 Bob 會各自建立單一的 Communicator 物件,並各自存取單一的 m_ECDH_Cng 成員。Mallory 會建立兩個 Communicator 物件:一個用來與 Alice 搭配,一個用來與 Bob 搭配。因此,Mallory 可以存取兩個 m_ECDH_Cng 成員。
做為私用 XML 字串的 ECDH 公開金鑰:
m_ECDH_XmlString = m_ECDH_CryptoKey.ToXmlString(ECKeyXmlFormat.Rfc4050)
ECDiffieHellmanCng.ToXmlString 方法會藉由使用 ECKeyXmlFormat.Rfc4050 格式序列化公開 ECDH 金鑰。
在範例的版本 2 到 5,Alice 會藉由使用下列程式碼陳述式,在 Run 方法中將產生的 XML 字串傳送給 Bob:
Alice.Send_or_Receive_PublicCryptoKey("send", MyColor);
ChannelManager 類別的公用執行個體:
ChMgr = new ChannelManager(mode, ChannelName)
建構函式會接受下列兩個參數:
mode:字串,用於表示如何建立具名管道。這個參數可以是 "server" 或 "client"。
ChannelName:字串,用於提供名稱以識別新的具名管道。
Dispose 方法
public void Dispose()
Communicator 類別會繼承 IDisposable 介面並提供 Dispose 方法,以即刻釋放敏感性資源。其中包括 m_ECDH_Cng、m_ECDH_local_publicKey_XML 和 ChMgr 物件。
每個物件是在 C# using 陳述式內建立的,以確保物件會在超出範圍時立即釋放。
StoreDSKey 方法
public void StoreDSKey(byte[] DSKeyBlob)
這個方法會接受包含在位元組陣列中的二進位大型物件 (BLOB)。它會藉由使用 CngKey.Import(array<Byte[], CngKeyBlobFormat) 方法,擷取金鑰 BLOB 中的數位簽章金鑰。接著以下列程式碼儲存金鑰:
m_DSKey = CngKey.Import(DSKeyBlob,CngKeyBlobFormat.Pkcs8PrivateBlob);
依據下列版本,Alice、Bob 和 Mallory 會在其 Run 方法中呼叫 StoreDSKey 方法:
在版本 3 到 5,Alice、Bob 和 Mallory 會使用這個方法儲存公開傳輸的相同金鑰。
在版本 4 和 5,Alice 和 Bob 會二度呼叫這個方法,並使用私下傳輸的金鑰覆寫 m_DSKey 成員。
Send_or_Receive_PublicCryptoKey 方法
Send_or_Receive_PublicCryptoKey(string mode, int color)
這個方法會接受兩個參數:
mode:字串,用於表示是要建立管道伺服器傳送金鑰,還是要建立管道用戶端接收金鑰。這個參數可以是 "server" 或 "client"。
color:整數,用於指定顯示金鑰時使用的色彩。
Send_or_Receive_PublicCryptoKey 方法與 CommunicatorReceiveMessage 和 SendMessage 方法類似,不同之處在於前者會傳送或接收未加密的密碼編譯金鑰,而非加密訊息。ReceiveMessage 和 SendMessage 無法用於密碼編譯金鑰,因為這些方法會嘗試加密和解密金鑰。
交換金鑰後,範例的版本 3 到 5 會驗證金鑰的數位簽章。版本 3 使用的數位簽章是透過 PublicChannel 具名管道而傳輸的。Mallory 會攔截並使用這個簽章,簽署他傳送給 Alice 和 Bob 的替代金鑰和訊息。版本 3 一定會成功通過簽章驗證,因為 Alice、Bob 和 Mallory 使用相同的數位簽章金鑰。
注意事項 |
---|
版本 4 和 5 則使用私密的數位簽章來簽署金鑰和訊息,並顯示安全性警告。只有 Alice 和 Bob 接獲這兩個版本。因此,Mallory 並不知道自己的攔截作業已被偵測到。 |
SendMessage 方法
public bool SendMessage(string plainTextMessage, bool fShowMsg)
Alice、Bob 和 Mallory 會在其 Run 方法中呼叫這個方法。它會依據下列這些步驟加密、數位簽署並傳送訊息:
在 fShowMsg 值為 true 時顯示純文字訊息。
使用下列程式碼,將訊息轉換為 Unicode 位元組陣列:
byte[] UnicodeText = Encoding.Unicode.GetBytes(plainTextMessage);
判斷訊息是否要以純文字傳送。如果範例執行的是版本 1,則使用下列程式碼在 SendMessage 傳送訊息後傳回:
ChMgr.SendMessage(UnicodeText);
藉由呼叫 ECDiffieHellmanCng.DeriveKeyMaterial(ECDiffieHellmanPublicKey) 方法衍生金鑰內容:
byte[] aesKey = m_ECDH_Cng.DeriveKeyMaterial(m_ECDH_remote_publicKey)
建立暫存 Aes 物件:
Aes aes = new AesCryptoServiceProvider()
使用步驟 4 衍生的金鑰內容,初始化 Aes 物件:
aes.Key = aesKey;
建立暫存 MemoryStream 物件以存放加密字串。
建立暫存 CryptoStream 物件,並用來加密訊息再寫入到 MemoryStream 物件。
儲存密碼文字 (Ciphertext) 和初始化向量:
iv = aes.IV ciphertext = ms.ToArray();
如果範例執行的是版本 3、4 或 5,依據下列作業簽署密碼文字:
建立暫存 ECDsaCng.ECDsaCng(CngKey) 物件:
ECDsaCng ecdsa = new ECDsaCng(m_DSKey)
將物件的 HashAlgorithm 屬性初始化為 Sha512 安全雜湊演算法:
ecdsa.HashAlgorithm = CngAlgorithm.Sha512
藉由簽署密碼文字建立數位簽章:
signature = ecdsa.SignData(ciphertext);
依據下列作業準備輸出訊息:
建立三位元組陣列,存放訊息初始化向量、密碼文字和簽章的長度。
建立 4 個 System.Collections.Generic.List<T> 物件,存放前一步驟的長度陣列、初始化向量、密碼文字和簽章。
串連 4 個 System.Collections.Generic.List<T> 物件,並轉換為單一輸出訊息位元組陣列:
byte[] message = list1.ToArray();
傳送輸出訊息:
ChMgr.SendMessage(message)
ReceiveMessage 方法
public string ReceiveMessage()
Alice、Bob 和 Mallory 會在其 Run 方法中呼叫這個方法。它會接收和解密訊息,以及驗證其數位簽章。
訊息並不會傳遞做為這個方法的引數。而是由 Communicator 類別的 ChannelManager 成員使用下列程式碼來讀取訊息:
byteBuffer = ChMgr.ReadMessage();
ReceiveMessage 方法會執行下列步驟:
判斷訊息是否是以純文字傳送。如果範例執行的是版本 1,則訊息是純文字的,不需要解密。在這種情況下,會藉由使用下列程式碼,將訊息轉換為 ASCII 字串後傳回:
AsciiMessage = Encoding.Unicode.GetString(byteBuffer);
將訊息拆解為幾部分。在版本 2 到 5 中會加密訊息。在這種情況下,訊息會拆解為三個分開的位元組陣列。第一個陣列包含初始化向量,第二個陣列包含密碼文字,而第三個陣列則包含密碼文字的數位簽章。
如果有設定 fVerbose 旗標,則以 ASCII 文字顯示初始化向量、密碼文字和簽章。
衍生金鑰內容。Communicator 類別的 Private 成員 ECDiffieHellmanCng (m_ECDH_Cng) 會使用 ECDiffieHellmanCng.DeriveKeyMaterial 方法衍生共用金鑰內容,如下列程式碼所示:
byte[] aesKey = m_ECDH_Cng.DeriveKeyMaterial(m_ECDH_remote_publicKey);
藉由執行個體化 AesCryptoServiceProvider 物件建立 Aes:
Aes aes = new AesCryptoServiceProvider()
使用前面步驟衍生的金鑰內容和初始化向量,初始化 Aes 物件。
藉由使用 System.IO.MemoryStream 和 System.Security.Cryptography.CryptoStream 物件解密訊息。
顯示解密訊息。
在版本 3 到 5 中,會藉由使用數位簽章驗證訊息的密碼文字。版本 3 並不會顯示安全性警告,因為 Alice、Bob 和 Mallory 使用相同的簽章。版本 4 會在訊息的密碼文字具有無效簽章時,顯示安全性警告。然而,因為只有 Alice 和 Bob 有接獲版本 4,所以 Mallory 絕對不會看到任何警告。在版本 5 傳輸和驗證任何訊息前,簽署無效的密碼編譯金鑰會讓程式終止。
請參閱
概念
Cryptography Next Generation (CNG) 安全通訊範例