2013-01-16 163 views
0

發現如何實施Rijndael的示例http://www.obviex.com/samples/Encryption.aspxRijndael是否足夠安全地用於生產系統?

此代碼是否足夠安全用於生產系統?

using System; 
using System.IO; 
using System.Text; 
using System.Security.Cryptography; 

此類使用對稱密鑰算法(Rijndael算法/ AES)來加密和解密 數據。只要加密和解密例程使用相同的參數來生成密鑰,密鑰保證是相同的。 該類使用帶有重複代碼的靜態函數,以便更容易地演示加密和解密邏輯。在實際的應用程序中,這可能不是處理加密的最有效方式,因此 - 如果您對此感到滿意,您可能需要重新設計此類。

public class RijndaelSimple 
{ 
    /// <summary> 
    /// Encrypts specified plaintext using Rijndael symmetric key algorithm 
    /// and returns a base64-encoded result. 
    /// </summary> 
    /// <param name="plainText"> 
    /// Plaintext value to be encrypted. 
    /// </param> 
    /// <param name="passPhrase"> 
    /// Passphrase from which a pseudo-random password will be derived. The 
    /// derived password will be used to generate the encryption key. 
    /// Passphrase can be any string. In this example we assume that this 
    /// passphrase is an ASCII string. 
    /// </param> 
    /// <param name="saltValue"> 
    /// Salt value used along with passphrase to generate password. Salt can 
    /// be any string. In this example we assume that salt is an ASCII string. 
    /// </param> 
    /// <param name="hashAlgorithm"> 
    /// Hash algorithm used to generate password. Allowed values are: "MD5" and 
    /// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes. 
    /// </param> 
    /// <param name="passwordIterations"> 
    /// Number of iterations used to generate password. One or two iterations 
    /// should be enough. 
    /// </param> 
    /// <param name="initVector"> 
    /// Initialization vector (or IV). This value is required to encrypt the 
    /// first block of plaintext data. For RijndaelManaged class IV must be 
    /// exactly 16 ASCII characters long. 
    /// </param> 
    /// <param name="keySize"> 
    /// Size of encryption key in bits. Allowed values are: 128, 192, and 256. 
    /// Longer keys are more secure than shorter keys. 
    /// </param> 
    /// <returns> 
    /// Encrypted value formatted as a base64-encoded string. 
    /// </returns> 
    public static string Encrypt(string plainText, 
           string passPhrase, 
           string saltValue, 
           string hashAlgorithm, 
           int  passwordIterations, 
           string initVector, 
           int  keySize) 
    { 
     // Convert strings into byte arrays. 
     // Let us assume that strings only contain ASCII codes. 
     // If strings include Unicode characters, use Unicode, UTF7, or UTF8 
     // encoding. 
     byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector); 
     byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue); 

     // Convert our plaintext into a byte array. 
     // Let us assume that plaintext contains UTF8-encoded characters. 
     byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); 

     // First, we must create a password, from which the key will be derived. 
     // This password will be generated from the specified passphrase and 
     // salt value. The password will be created using the specified hash 
     // algorithm. Password creation can be done in several iterations. 
     PasswordDeriveBytes password = new PasswordDeriveBytes(
                 passPhrase, 
                 saltValueBytes, 
                 hashAlgorithm, 
                 passwordIterations); 

     // Use the password to generate pseudo-random bytes for the encryption 
     // key. Specify the size of the key in bytes (instead of bits). 
     byte[] keyBytes = password.GetBytes(keySize/8); 

     // Create uninitialized Rijndael encryption object. 
     RijndaelManaged symmetricKey = new RijndaelManaged(); 

     // It is reasonable to set encryption mode to Cipher Block Chaining 
     // (CBC). Use default options for other symmetric key parameters. 
     symmetricKey.Mode = CipherMode.CBC;   

     // Generate encryptor from the existing key bytes and initialization 
     // vector. Key size will be defined based on the number of the key 
     // bytes. 
     ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
                 keyBytes, 
                 initVectorBytes); 

     // Define memory stream which will be used to hold encrypted data. 
     MemoryStream memoryStream = new MemoryStream();   

     // Define cryptographic stream (always use Write mode for encryption). 
     CryptoStream cryptoStream = new CryptoStream(memoryStream, 
                encryptor, 
                CryptoStreamMode.Write); 
     // Start encrypting. 
     cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); 

     // Finish encrypting. 
     cryptoStream.FlushFinalBlock(); 

     // Convert our encrypted data from a memory stream into a byte array. 
     byte[] cipherTextBytes = memoryStream.ToArray(); 

     // Close both streams. 
     memoryStream.Close(); 
     cryptoStream.Close(); 

     // Convert encrypted data into a base64-encoded string. 
     string cipherText = Convert.ToBase64String(cipherTextBytes); 

     // Return encrypted string. 
     return cipherText; 
    } 

    /// <summary> 
    /// Decrypts specified ciphertext using Rijndael symmetric key algorithm. 
    /// </summary> 
    /// <param name="cipherText"> 
    /// Base64-formatted ciphertext value. 
    /// </param> 
    /// <param name="passPhrase"> 
    /// Passphrase from which a pseudo-random password will be derived. The 
    /// derived password will be used to generate the encryption key. 
    /// Passphrase can be any string. In this example we assume that this 
    /// passphrase is an ASCII string. 
    /// </param> 
    /// <param name="saltValue"> 
    /// Salt value used along with passphrase to generate password. Salt can 
    /// be any string. In this example we assume that salt is an ASCII string. 
    /// </param> 
    /// <param name="hashAlgorithm"> 
    /// Hash algorithm used to generate password. Allowed values are: "MD5" and 
    /// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes. 
    /// </param> 
    /// <param name="passwordIterations"> 
    /// Number of iterations used to generate password. One or two iterations 
    /// should be enough. 
    /// </param> 
    /// <param name="initVector"> 
    /// Initialization vector (or IV). This value is required to encrypt the 
    /// first block of plaintext data. For RijndaelManaged class IV must be 
    /// exactly 16 ASCII characters long. 
    /// </param> 
    /// <param name="keySize"> 
    /// Size of encryption key in bits. Allowed values are: 128, 192, and 256. 
    /// Longer keys are more secure than shorter keys. 
    /// </param> 
    /// <returns> 
    /// Decrypted string value. 
    /// </returns> 
    /// <remarks> 
    /// Most of the logic in this function is similar to the Encrypt 
    /// logic. In order for decryption to work, all parameters of this function 
    /// - except cipherText value - must match the corresponding parameters of 
    /// the Encrypt function which was called to generate the 
    /// ciphertext. 
    /// </remarks> 
    public static string Decrypt(string cipherText, 
           string passPhrase, 
           string saltValue, 
           string hashAlgorithm, 
           int  passwordIterations, 
           string initVector, 
           int  keySize) 
    { 
     // Convert strings defining encryption key characteristics into byte 
     // arrays. Let us assume that strings only contain ASCII codes. 
     // If strings include Unicode characters, use Unicode, UTF7, or UTF8 
     // encoding. 
     byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector); 
     byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue); 

     // Convert our ciphertext into a byte array. 
     byte[] cipherTextBytes = Convert.FromBase64String(cipherText); 

     // First, we must create a password, from which the key will be 
     // derived. This password will be generated from the specified 
     // passphrase and salt value. The password will be created using 
     // the specified hash algorithm. Password creation can be done in 
     // several iterations. 
     PasswordDeriveBytes password = new PasswordDeriveBytes(
                 passPhrase, 
                 saltValueBytes, 
                 hashAlgorithm, 
                 passwordIterations); 

     // Use the password to generate pseudo-random bytes for the encryption 
     // key. Specify the size of the key in bytes (instead of bits). 
     byte[] keyBytes = password.GetBytes(keySize/8); 

     // Create uninitialized Rijndael encryption object. 
     RijndaelManaged symmetricKey = new RijndaelManaged(); 

     // It is reasonable to set encryption mode to Cipher Block Chaining 
     // (CBC). Use default options for other symmetric key parameters. 
     symmetricKey.Mode = CipherMode.CBC; 

     // Generate decryptor from the existing key bytes and initialization 
     // vector. Key size will be defined based on the number of the key 
     // bytes. 
     ICryptoTransform decryptor = symmetricKey.CreateDecryptor(
                 keyBytes, 
                 initVectorBytes); 

     // Define memory stream which will be used to hold encrypted data. 
     MemoryStream memoryStream = new MemoryStream(cipherTextBytes); 

     // Define cryptographic stream (always use Read mode for encryption). 
     CryptoStream cryptoStream = new CryptoStream(memoryStream, 
                 decryptor, 
                 CryptoStreamMode.Read); 

     // Since at this point we don't know what the size of decrypted data 
     // will be, allocate the buffer long enough to hold ciphertext; 
     // plaintext is never longer than ciphertext. 
     byte[] plainTextBytes = new byte[cipherTextBytes.Length]; 

     // Start decrypting. 
     int decryptedByteCount = cryptoStream.Read(plainTextBytes, 
                0, 
                plainTextBytes.Length); 

     // Close both streams. 
     memoryStream.Close(); 
     cryptoStream.Close(); 

     // Convert decrypted data into a string. 
     // Let us assume that the original plaintext string was UTF8-encoded. 
     string plainText = Encoding.UTF8.GetString(plainTextBytes, 
                0, 
                decryptedByteCount); 

     // Return decrypted string. 
     return plainText; 
    } 
} 

/// <summary> 
/// Illustrates the use of RijndaelSimple class to encrypt and decrypt data. 
/// </summary> 
public class RijndaelSimpleTest 
{ 
    /// <summary> 
    /// The main entry point for the application. 
    /// </summary> 
    [STAThread] 
    static void Main(string[] args) 
    { 
     string plainText   = "Hello, World!"; // original plaintext 

     string passPhrase   = "[email protected]";  // can be any string 
     string saltValue   = "[email protected]";  // can be any string 
     string hashAlgorithm  = "SHA1";    // can be "MD5" 
     int  passwordIterations = 2;     // can be any number 
     string initVector   = "@1B2c3D4e5F6g7H8"; // must be 16 bytes 
     int  keySize   = 256;    // can be 192 or 128 

     Console.WriteLine(String.Format("Plaintext : {0}", plainText)); 

     string cipherText = RijndaelSimple.Encrypt(plainText, 
                passPhrase, 
                saltValue, 
                hashAlgorithm, 
                passwordIterations, 
                initVector, 
                keySize); 

     Console.WriteLine(String.Format("Encrypted : {0}", cipherText)); 

     plainText   = RijndaelSimple.Decrypt(cipherText, 
                passPhrase, 
                saltValue, 
                hashAlgorithm, 
                passwordIterations, 
                initVector, 
                keySize); 

     Console.WriteLine(String.Format("Decrypted : {0}", plainText)); 
    } 
} 
+0

嗯,我不太確定這個答案是否定的,我花了很少的時間找到缺陷。回答這樣的問題肯定會更棘手,但在這種情況下,這不是必需的。但無論如何,這對於代碼評論網站來說更是如此。 –

+0

感謝您的指點。 – tom

回答

1

沒有。

對於不理解字符串和八位字符串之間的區別,這是一個明顯的錯誤。對於鹽這可能是可以原諒的,但它肯定不是這種情況。

此外,它使用Microsoft專有的擴展PKCS#5 PBKDF1實現PasswordDeriveBytes而不是如Rfc2898DeriveBytes中定義的PBKDF2。對於長度超過20個字節的任何字節(這是SHA-1輸出大小,以及PBKDF1的定義輸出大小),這種實現是不安全的,直到可以在輸出中重複字節爲止。超過20個字節的任何輸出將不能在其他框架上重現。即使莫諾已經指定這是「不會修復」。

初始化向量應該由安全的隨機數生成器生成,而不是作爲參數傳遞。

CBC模式可以是安全的,但它不提供完整性保護,因此在通過傳輸協議使用時(通常)不安全。

此列表可能並非詳盡無遺。

+1

而init的向量不應該是一個參數,但當然是隨機生成的。 –

+0

謝謝。你有沒有鏈接到任何有關這些問題的出版物? – tom

+0

關於它的發表很少,這是其中一個問題。但請參閱[此鏈接](http://www.mail-archive.com/[email protected]/msg45125.html)。有一個原因,有PBKDF2作爲PBKDF1的替代品,但更好地跟上時間。 –