天天看點

加密和解密的算法

摘要:

.NET将CryptoAPI改編進.NET的System.Security.Cryptography名字空間,使密碼服務擺脫了SDK平台的神秘性,變成了簡單的.NET名字空間的使用。由于随着整個架構元件一起共享,密碼服務更容易實作了,現在僅僅需要學習System.Security.Cryptography名字空間的功能和用于解決特定方案的類。

  .NET将原來獨立的API和SDK合并到一個架構中,這對于程式開發人員非常有利。

  

加密和解密的算法

  System.Security.Cryptography名字空間包含了實作安全方案的類,例如加密和解密資料、管理密鑰、驗證資料的完整性并確定資料沒有被篡改等等。本文重點讨論加密和解密。

  加密和解密的算法分為對稱(symmetric)算法和不對稱(asymmetric)算法。對稱算法在加密和解密資料時使用相同的密鑰和初始化矢量,典型的有DES、 TripleDES和Rijndael算法,它适用于不需要傳遞密鑰的情況,主要用于本地文檔或資料的加密。不對稱算法有兩個不同的密鑰,分别是公共密鑰和私有密鑰,公共密鑰在網絡中傳遞,用于加密資料,而私有密鑰用于解密資料。不對稱算法主要有RSA、DSA等,主要用于網絡資料的加密。

  

加密和解密本地文檔

  下面的例子是加密和解密本地文本,使用的是Rijndael對稱算法。

  對稱算法在資料流通過時對它進行加密。是以首先需要建立一個正常的流(例如I/O流)。文章使用FileStream類将文本檔案讀入位元組數組,也使用該類作為輸出機制。

  接下來定義相應的對象變量。在定義SymmetricAlgorithm抽象類的對象變量時我們可以指定任何一種對稱加密算法提供程式。代碼使用的是Rijndael算法,但是很容易改為DES或者TripleDES算法。.NET使用強大的随機密鑰設定了提供程式的執行個體,選擇自己的密鑰是比較危險的,接受計算機産生的密鑰是一個更好的選擇,文中的代碼使用的是計算機産生的密鑰。

  下一步,算法執行個體提供了一個對象來執行實際資料傳輸。每種算法都有CreateEncryptor和CreateDecryptor兩個方法,它們傳回實作ICryptoTransform接口的對象。

  最後,現在使用BinaryReader的ReadBytes方法讀取源檔案,它會傳回一個位元組數組。BinaryReader讀取源檔案的輸入流,在作為CryptoStream.Write方法的參數時調用ReadBytes方法。指定的CryptoStream執行個體被告知它應該操作的下層流,該對象将執行資料傳遞,無論流的目的是讀或者寫。

  下面是加密和解密一個文本檔案的源程式片斷:

namespace com.billdawson.crypto

{

class TextFileCrypt

{

public static void Main(string[] args)

{

string file = args[0];

string tempfile = Path.GetTempFileName();

//打開指定的檔案

FileStream fsIn = File.Open(file,FileMode.Open,

FileAccess.Read);

FileStream fsOut = File.Open(tempfile, FileMode.Open,

FileAccess.Write);

//定義對稱算法對象執行個體和接口

SymmetricAlgorithm symm = new RijndaelManaged();

ICryptoTransform transform = symm.CreateEncryptor();

CryptoStream cstream = new CryptoStream(fsOut,transform,

ryptoStreamMode.Write);

BinaryReader br = new BinaryReader(fsIn);

// 讀取源檔案到cryptostream

cstream.Write(br.ReadBytes((int)fsIn.Length),0,(int)fsIn.Length);

cstream.FlushFinalBlock();

cstream.Close();

fsIn.Close();

fsOut.Close();

Console.WriteLine("created encrypted file {0}", tempfile);

Console.WriteLine("will now decrypt and show contents");

// 反向操作--解密剛才加密的臨時檔案

fsIn = File.Open(tempfile,FileMode.Open,FileAccess.Read);

transform = symm.CreateDecryptor();

cstream = new CryptoStream(fsIn,transform,

CryptoStreamMode.Read);

StreamReader sr = new StreamReader(cstream);

Console.WriteLine("decrypted file text: " + sr.ReadToEnd());

fsIn.Close();

}

}

}

  加密網絡資料

  如果我有一個隻想自己看到的文檔,我不會簡單的通過e-mail發送給你。我将使用對稱算法加密它;如果有人截取了它,他們也不能閱讀該文檔,因為他們沒有用于加密的唯一密鑰。但是你也沒有密鑰。我需要使用某種方式将密鑰給你,這樣你才能解密文檔,但是不能冒密鑰和文檔被截取的風險。

  非對稱算法就是一種解決方案。這類算法使用的兩個密鑰有如下關系:使用公共密鑰加密的資訊隻能被相應的私有密鑰解密。是以,我首要求你給我發送你的公共密鑰。在發送給我的途中可能有人會截取它,但是沒有關系,因為他們隻能使用該密鑰給你的資訊加密。我使用你的公共密鑰加密文檔并發送給你。你使用私有密鑰解密該文檔,這是唯一可以解密的密鑰,并且沒有通過網絡傳遞。

  不對稱算法比對稱算法計算的花費多、速度慢。是以我們不希望線上對話中使用不對稱算法加密所有資訊。相反,我們使用對稱算法。下面的例子中我們使用不對稱加密來加密對稱密鑰。接着就使用對稱算法加密了。實際上安全接口層(SSL)建立伺服器和浏覽器之間的安全對話使用的就是這種工作方式。

示例是一個TCP程式,分為伺服器端和用戶端。伺服器端的工作流程是:

   從用戶端接收公共密鑰。

   使用公共密鑰加密未來使用的對稱密鑰。

   将加密了的對稱密鑰發送給用戶端。

   給用戶端發送使用該對稱密鑰加密的資訊。

  代碼如下:

namespace com.billdawson.crypto

{

public class CryptoServer

{

private const int RSA_KEY_SIZE_BITS = 1024;

private const int RSA_KEY_SIZE_BYTES = 252;

private const int TDES_KEY_SIZE_BITS = 192;

public static void Main(string[] args)

{

int port;

string msg;

TcpListener listener;

TcpClient client;

SymmetricAlgorithm symm;

RSACryptoServiceProvider rsa;

//擷取端口

try

{

port = Int32.Parse(args[0]);

msg = args[1];

}

catch

{

Console.WriteLine(USAGE);

return;

}

//建立監聽

try

{

listener = new TcpListener(port);

listener.Start();

Console.WriteLine("Listening on port {0}...",port);

client = listener.AcceptTcpClient();

Console.WriteLine("connection....");

}

catch (Exception e)

{

Console.WriteLine(e.Message);

Console.WriteLine(e.StackTrace);

return;

}

try

{

rsa = new RSACryptoServiceProvider();

rsa.KeySize = RSA_KEY_SIZE_BITS;

// 擷取用戶端公共密鑰

rsa.ImportParameters(getClientPublicKey(client));

symm = new TripleDESCryptoServiceProvider();

symm.KeySize = TDES_KEY_SIZE_BITS;

//使用用戶端的公共密鑰加密對稱密鑰并發送給客。

encryptAndSendSymmetricKey(client, rsa, symm);

//使用對稱密鑰加密資訊并發送

encryptAndSendSecretMessage(client, symm, msg);

}

catch (Exception e)

{

Console.WriteLine(e.Message);

Console.WriteLine(e.StackTrace);

}

finally

{

try

{

client.Close();

listener.Stop();

}

catch

{

//錯誤

}

Console.WriteLine("Server exiting...");

}

}

private static RSAParameters getClientPublicKey(TcpClient client)

{

// 從位元組流擷取串行化的公共密鑰,通過串并轉換寫入類的執行個體

byte[] buffer = new byte[RSA_KEY_SIZE_BYTES];

NetworkStream ns = client.GetStream();

MemoryStream ms = new MemoryStream();

BinaryFormatter bf = new BinaryFormatter();

RSAParameters result;

int len = 0;

int totalLen = 0;

while(totalLen (len = ns.Read(buffer,0,buffer.Length))>0)

{

totalLen+=len;

ms.Write(buffer, 0, len);

}

ms.Position=0;

result = (RSAParameters)bf.Deserialize(ms);

ms.Close();

return result;

}

private static void encryptAndSendSymmetricKey(

TcpClient client,

RSACryptoServiceProvider rsa,

SymmetricAlgorithm symm)

{

// 使用用戶端的公共密鑰加密對稱密鑰

byte[] symKeyEncrypted;

byte[] symIVEncrypted;

NetworkStream ns = client.GetStream();

symKeyEncrypted = rsa.Encrypt(symm.Key, false);

symIVEncrypted = rsa.Encrypt(symm.IV, false);

ns.Write(symKeyEncrypted, 0, symKeyEncrypted.Length);

ns.Write(symIVEncrypted, 0, symIVEncrypted.Length);

}

private static void encryptAndSendSecretMessage(TcpClient client,

SymmetricAlgorithm symm,

string secretMsg)

{

// 使用對稱密鑰和初始化矢量加密資訊并發送給用戶端

byte[] msgAsBytes;

NetworkStream ns = client.GetStream();

ICryptoTransform transform =

symm.CreateEncryptor(symm.Key,symm.IV);

CryptoStream cstream =

new CryptoStream(ns, transform, CryptoStreamMode.Write);

msgAsBytes = Encoding.ASCII.GetBytes(secretMsg);

cstream.Write(msgAsBytes, 0, msgAsBytes.Length);

cstream.FlushFinalBlock();

}

}

  用戶端的工作流程是:

   建立和發送公共密鑰給伺服器。

   從伺服器接收被加密的對稱密鑰。

   解密該對稱密鑰并将它作為私有的不對稱密鑰。

   接收并使用不對稱密鑰解密資訊。

  代碼如下:

namespace com.billdawson.crypto

{

public class CryptoClient

{

private const int RSA_KEY_SIZE_BITS = 1024;

private const int RSA_KEY_SIZE_BYTES = 252;

private const int TDES_KEY_SIZE_BITS = 192;

private const int TDES_KEY_SIZE_BYTES = 128;

private const int TDES_IV_SIZE_BYTES = 128;

public static void Main(string[] args)

{

int port;

string host;

TcpClient client;

SymmetricAlgorithm symm;

RSACryptoServiceProvider rsa;

if (args.Length!=2)

{

Console.WriteLine(USAGE);

return;

}

try

{

host = args[0];

port = Int32.Parse(args[1]);

}

catch

{

Console.WriteLine(USAGE);

return;

}

try //連接配接

{

client = new TcpClient();

client.Connect(host,port);

}

catch(Exception e)

{

Console.WriteLine(e.Message);

Console.Write(e.StackTrace);

return;

}

try

{

Console.WriteLine("Connected. Sending public key.");

rsa = new RSACryptoServiceProvider();

rsa.KeySize = RSA_KEY_SIZE_BITS;

sendPublicKey(rsa.ExportParameters(false),client);

symm = new TripleDESCryptoServiceProvider();

symm.KeySize = TDES_KEY_SIZE_BITS;

MemoryStream ms = getRestOfMessage(client);

extractSymmetricKeyInfo(rsa, symm, ms);

showSecretMessage(symm, ms);

}

catch(Exception e)

{

Console.WriteLine(e.Message);

Console.Write(e.StackTrace);

}

finally

{

try

{

client.Close();

}

catch { //錯誤

}

}

}

private static void sendPublicKey(

RSAParameters key,

TcpClient client)

{

NetworkStream ns = client.GetStream();

BinaryFormatter bf = new BinaryFormatter();

bf.Serialize(ns,key);

}

private static MemoryStream getRestOfMessage(TcpClient client)

{

//擷取加密的對稱密鑰、初始化矢量、秘密資訊。對稱密鑰用公共RSA密鑰

//加密,秘密資訊用對稱密鑰加密

MemoryStream ms = new MemoryStream();

NetworkStream ns = client.GetStream();

byte[] buffer = new byte[1024];

int len=0;

// 将NetStream 的資料寫入記憶體流

while((len = ns.Read(buffer, 0, buffer.Length))>0)

{

ms.Write(buffer, 0, len);

}

ms.Position = 0;

return ms;

}

private static void extractSymmetricKeyInfo(

RSACryptoServiceProvider rsa,

SymmetricAlgorithm symm,

MemoryStream msOrig)

{

MemoryStream ms = new MemoryStream();

// 擷取TDES密鑰--它被公共RSA密鑰加密,使用私有密鑰解密

byte[] buffer = new byte[TDES_KEY_SIZE_BYTES];

msOrig.Read(buffer,0,buffer.Length);

symm.Key = rsa.Decrypt(buffer,false);

// 擷取TDES初始化矢量

buffer = new byte[TDES_IV_SIZE_BYTES];

msOrig.Read(buffer, 0, buffer.Length);

symm.IV = rsa.Decrypt(buffer,false);

}

private static void showSecretMessage(

SymmetricAlgorithm symm,

MemoryStream msOrig)

{

//記憶體流中的所有資料都被加密了

byte[] buffer = new byte[1024];

int len = msOrig.Read(buffer,0,buffer.Length);

MemoryStream ms = new MemoryStream();

ICryptoTransform transform =

symm.CreateDecryptor(symm.Key,symm.IV);

CryptoStream cstream =new CryptoStream(ms, transform,

CryptoStreamMode.Write);

cstream.Write(buffer, 0, len);

cstream.FlushFinalBlock();

// 記憶體流現在是解密資訊,是位元組的形式,将它轉換為字元串

ms.Position = 0;

len = ms.Read(buffer,0,(int) ms.Length);

ms.Close();

string msg = Encoding.ASCII.GetString(buffer,0,len);

Console.WriteLine("The host sent me this secret message:");

Console.WriteLine(msg);

}

}

}

   結論

  使用對稱算法加密本地資料時比較适合。在保持代碼通用時我們可以選擇多種算法,當資料通過特定的CryptoStream時算法使用轉換對象加密該資料。需要将資料通過網絡發送時,首先使用接收的公共不對稱密鑰加密對稱密鑰。

  本文隻涉及到System.Security.Cryptography名字空間的一部分服務。盡管文章保證隻有某個私有密鑰可以解密相應公共密鑰加密的資訊,但是它沒有保證是誰發送的公共密鑰,發送者也可能是假的。需要使用處理數字證書的類來對付該風險。

繼續閱讀