MD5或者說HASH值是一種不可逆的算法。如果需要從密文還原成明文,那么就需要對稱和非對稱這兩類可逆算法。
首先,簡單介紹下這兩類算法。圖9-1是對稱算法的示意圖:
圖9-1 對稱算法
在對稱算法中,首先需要發送方和接收方協定一個密鑰K。K可以是一個密鑰對,但是必須要求加密密鑰和解密密鑰之間能夠互相推算出來。在最簡單也是最常用的對稱算法中,加密和解密共享一個密鑰。上圖中,我們為了簡單期間,使用的就是一個密鑰。密鑰K為了防止被第三方獲取,可以通過一個秘密通道由發送方傳送給接收方。當然,這個秘密通道可以是任何形式,如果覺得可以,你甚至可以寄送一封郵件給對方告訴他密鑰。
對稱加密中明文通過對稱加密成密文,在公開通道中進行傳輸。這個時候,即便第三方截獲了數據,由于他沒有掌握密鑰,也是解密不了密文的。
簡單介紹了對稱加密,現在我們來看非對稱加密。圖9-2是一個非對稱加密的示意圖:
圖9-2 非對稱算法
在非對稱算法中,首先得有一個密鑰對,這個密鑰對含有兩部分內容,分別稱作公鑰(PK)和私鑰(SK),公鑰通常用來加密,私鑰則用來解密。在對稱算法中,也講到了可以有兩個密鑰(分為加密和解密密鑰)。但是,對稱算法中的加解密密鑰可以互相轉換,而在非對稱算法中,則不能從公鑰推算出私鑰。所以,我們完全可以將公鑰公開到任何地方。
如上圖所以,發送者用接收方公開出來的公鑰PK進行加密。接受方在收到密文后,再用與公鑰對應的私鑰SK進行解密。同樣,密文可以被截獲,但是由于截獲者只有公鑰,沒有私鑰,他不能進行解密。
對稱算法和非對稱算法各有優缺點。非對稱加密的突出優點是用于解密的密鑰(也就是私鑰)永遠不需要傳遞給對方。但是,它的缺點也很突出:非對稱加密算法復雜,導致加解密速度慢,故只適合小量數據的場合。而對稱加密加解密效率高,系統開銷小,適合進行大數據量的加解密。由于文件一般比較大,這個特性決定了適合它的加密方式最好是對稱加密。下面是一個針對文件的對稱加密的實現:
static ?void ?Main() { ???? EncryptFile( @"c:\temp.txt" , @"c:\tempcm.txt" , "123" ); ???? Console.WriteLine( "加密成功!" ); ???? DecryptFile( @"c:\tempcm.txt" , @"c:\tempm.txt" , "123" ); ???? Console.WriteLine( "解密成功!" ); } //緩沖區大小 static ?int ?bufferSize = 128 * 1024; //密鑰salt static ?byte [] salt = { 134, 216, 7, 36, 88, 164, 91, 227, 174, 76, 191, 197, 192, 154, 200, 248 }; //初始化向量 static ?byte [] iv = { 134, 216, 7, 36, 88, 164, 91, 227, 174, 76, 191, 197, 192, 154, 200, 248 }; //初始化并返回對稱加密算法 static ?SymmetricAlgorithm CreateRijndael( string ?password, byte [] salt) { ???? PasswordDeriveBytes pdb = new ?PasswordDeriveBytes(password, salt, "SHA256" , 1000); ???? SymmetricAlgorithm sma = Rijndael.Create(); ???? sma.KeySize = 256; ???? sma.Key = pdb.GetBytes(32); ???? sma.Padding = PaddingMode.PKCS7; ???? return ?sma; } static ?void ?EncryptFile( string ?inFile, string ?outFile, string ?password) { ???? using ?(FileStream inFileStream = File.OpenRead(inFile), outFileStream = File.Open(outFile, FileMode.OpenOrCreate)) ???? using ?(SymmetricAlgorithm algorithm = CreateRijndael(password, salt)) ???? { ???????? algorithm.IV = iv; ???????? using ?(CryptoStream cryptoStream = new ?CryptoStream(outFileStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write)) ???????? { ???????????? byte [] bytes = new ?byte [bufferSize]; ???????????? int ?readSize = -1; ???????????? while ?((readSize = inFileStream.Read(bytes, 0, bytes.Length)) != 0) ???????????? { ???????????????? cryptoStream.Write(bytes, 0, readSize); ???????????? } ???????????? cryptoStream.Flush(); ???????? } ???? } } static ?void ?DecryptFile( string ?inFile, string ?outFile, string ?password) { ???? using ?(FileStream inFileStream = File.OpenRead(inFile), outFileStream = File.OpenWrite(outFile)) ???? using ?(SymmetricAlgorithm algorithm = CreateRijndael(password, salt)) ???? { ???????? algorithm.IV = iv; ???????? using ?(CryptoStream cryptoStream = new ?CryptoStream(inFileStream, algorithm.CreateDecryptor(), CryptoStreamMode.Read)) ???????? { ???????????? byte [] bytes = new ?byte [bufferSize]; ???????????? int ?readSize = -1; ???????????? int ?numReads = ( int )(inFileStream.Length / bufferSize); ???????????? int ?slack = ( int )(inFileStream.Length % bufferSize); ???????????? for ?( int ?i = 0; i < numReads; ++i) ???????????? { ???????????????? readSize = cryptoStream.Read(bytes, 0, bytes.Length); ???????????????? outFileStream.Write(bytes, 0, readSize); ???????????? } ???????????? if ?(slack > 0) ???????????? { ???????????????? readSize = cryptoStream.Read(bytes, 0, ( int )slack); ???????????????? outFileStream.Write(bytes, 0, readSize); ???????????? } ???????????? outFileStream.Flush(); ???????? } ???? } } |
備注:密鑰salt和初始化向量iv
有必要解釋下上面代碼中的密鑰salt和初始化向量iv。
密鑰salt在加密算法中主要被設計用來防止“字典攻擊”。字典攻擊也是一種窮舉的暴力破解法。字典中會假設一定數量的密碼值,攻擊者會嘗試用這些密碼來解密密文。Salt是在密鑰導出之前在密碼末尾引入的隨機字節,它使這類攻擊變得非常困難。
初始化向量IV在加密算法中起到的也是增強破解難度的作用。在加密過程中,如果遇到相同的數據塊,其加密出來的結果也一致,相對就會容易破解。加密算法在加密數據塊的時候,往往會同時使用密碼和上一個數據塊的加密結果。因為要加密的第一個數據塊顯然不存在上一個數據塊,所以這個初始化向量就是被設計用來當作初始數據塊的加密結果。
最后,我們在實際應用中,應該始終考慮使用對稱加密的方式進行文件的加解密工作。當然,如果文件加密后要傳給網絡中的其它接收者,而接收者始終要對文件進行解密的,這意味著密鑰也是始終要傳送給接收者的。這個時候,非對稱加密就可以派上用場了,它可以用于字符串的加解密及安全傳輸場景。關于這一點,我們會在下一個建議中講到。