【问题标题】:Rijndael file encryption / decryptionRijndael 文件加密/解密
【发布时间】:2015-06-11 09:19:20
【问题描述】:

过去几天,我根据 RijndaelManaged 类提供的 Rijndael 加密标准创建了一个文件加密/解密类,并搜索了我能找到的所有资源和示例。这些示例要么已过时,要么损坏,要么有限,但至少设法学到了很多东西,并认为我会在确保它健壮并通过您的批评后发布它的最新版本。

到目前为止,我发现的唯一问题是需要知道 salt,因为无法像对字符串那样将其存储在加密文件中,除非将每个字节的读/写转换为缓冲区基于读/写,但是你需要在解密时满足这一点,并且还需要至少 4 个字节的数据来加密(虽然我并不认为这是一个问题,但确实需要提及)。

我也不完全确定 1 个盐是否足以满足密钥和初始化向量的需求,或者出于安全原因,两个盐是否更好?

任何其他观察和/或优化也将不胜感激

class FileEncDec
{
    private int keySize;
    private string passPhrase;

    internal FileEncDec( int keySize = 256, string passPhrase = @"This is pass phrase key to use for testing" )
    {
        this.keySize = keySize;
        this.passPhrase = passPhrase; // Can be user selected and must be kept secret
    }

    private static byte[] GenerateSalt( int length )
    {
        byte[] salt = new byte[ length ];

        // Populate salt with cryptographically strong bytes.
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

        rng.GetNonZeroBytes( salt );

        // Split salt length (always one byte) into four two-bit pieces and store these pieces in the first four bytes 
        // of the salt array.
        salt[ 0 ] = (byte)( ( salt[ 0 ] & 0xfc ) | ( length & 0x03 ) );
        salt[ 1 ] = (byte)( ( salt[ 1 ] & 0xf3 ) | ( length & 0x0c ) );
        salt[ 2 ] = (byte)( ( salt[ 2 ] & 0xcf ) | ( length & 0x30 ) );
        salt[ 3 ] = (byte)( ( salt[ 3 ] & 0x3f ) | ( length & 0xc0 ) );

        return salt;
    }

    internal bool EncryptFile( string inputFile, string outputFile )
    {
        try
        {
            byte[] salt = GenerateSalt( 16 ); // Salt needs to be known for decryption (can be safely stored in the file)
            Rfc2898DeriveBytes derivedBytes = new Rfc2898DeriveBytes( passPhrase, salt, 10000 );
            int bytesRead, bufferSize = keySize / 8;
            byte[] data = new byte[ bufferSize ];
            RijndaelManaged cryptor = new RijndaelManaged();
            cryptor.Key = derivedBytes.GetBytes( keySize / 8 );
            cryptor.IV = derivedBytes.GetBytes( cryptor.BlockSize / 8 );

            using ( var fsIn = new FileStream( inputFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan ) )
            {
                using ( var fsOut = new FileStream( outputFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.SequentialScan ) )
                {
                    // Add the salt to the file
                    fsOut.Write( salt, 0, salt.Length );

                    using ( CryptoStream cs = new CryptoStream( fsOut, cryptor.CreateEncryptor(), CryptoStreamMode.Write ) )
                    {
                        while ( ( bytesRead = fsIn.Read( data, 0, bufferSize ) ) > 0 )
                        {
                            cs.Write( data, 0, bytesRead );
                        }
                    }
                }
            }

            return true;
        }
        catch ( Exception )
        {
            return false;
        }
    }

    internal bool DecryptFile( string inputFile, string outputFile )
    {
        try
        {
            int bytesRead = 0, bufferSize = keySize / 8, saltLen;
            byte[] data = new byte[ bufferSize ], salt;
            Rfc2898DeriveBytes derivedBytes;
            RijndaelManaged cryptor = new RijndaelManaged();    // Create new cryptor so it's thread safe and don't need to use locks

            using ( var fsIn = new FileStream( inputFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan ) )
            {
                // Retrieve the salt length from the file
                fsIn.Read( data, 0, 4 );

                saltLen =   ( data[ 0 ] & 0x03 ) |
                            ( data[ 1 ] & 0x0c ) |
                            ( data[ 2 ] & 0x30 ) |
                            ( data[ 3 ] & 0xc0 );

                salt = new byte[ saltLen ];
                Array.Copy( data, salt, 4 );

                // Retrieve the remaining salt from the file and create the cryptor
                fsIn.Read( salt, 4, saltLen - 4 );
                derivedBytes = new Rfc2898DeriveBytes( passPhrase, salt, 10000 );
                cryptor.Key = derivedBytes.GetBytes( keySize / 8 );
                cryptor.IV = derivedBytes.GetBytes( cryptor.BlockSize / 8 );

                using ( var fsOut = new FileStream( outputFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.SequentialScan ) )
                {
                    using ( var cs = new CryptoStream( fsIn, cryptor.CreateDecryptor(), CryptoStreamMode.Read ) )
                    {
                        while ( ( bytesRead = cs.Read( data, 0, bufferSize ) ) > 0 )
                        {
                            fsOut.Write( data, 0, bytesRead );
                        }
                    }
                }
            }

            return true;
        }
        catch ( Exception )
        {
            return false;
        }
    }
}

编辑: 1.新增盐生成器。 2. 重构为单个saltRfc2898DerivedBytes,现在从password + salt 推导出IV。 3. 使加密/解密线程安全(如果我没有正确执行,请告诉我)。

编辑 2: 1. 重构使得读/写使用缓冲区而不是单字节读/写。 2.在加密文件中嵌入盐并清理变量(但仍然允许passPhrase默认为“复制/粘贴”示例。 3. 重构文件句柄。

【问题讨论】:

  • 代码审查应发布在codereview.stackexchange.com。所以要求你有一个具体的问题。
  • @MaartenBodewes 我确实有一个具体的问题,事实上我有很多问题,当 usr 回答他们时,我实施了他的答案/建议(并确保我引用了编辑中的更改),以便其他人看因为同样的事情将能够得到一个适当的例子。不过谢谢你的链接,以后会记住的

标签: c# cryptography rijndael rijndaelmanaged


【解决方案1】:

您可能应该每次使用不同的 IV。如果您对相同的数据使用相同的 IV,则结果是相同的。攻击者现在可以推断出文件(部分)相同,这是一个泄漏。您可以生成 16 个强随机字节并将它们用作Rfc2898DeriveBytes 的盐。将这些字节添加到文件中。仅使用一个Rfc2898DeriveBytes 来生成 IV 和密钥。或者,您可以完全不使用盐作为密钥并随机生成 IV。 salt 可用于使密钥派生对您的用例具有唯一性,或者例如为您的应用的每个用户提供不同的密钥派生算法。

请注意,按字节处理流非常慢。使用缓冲区。也许,你应该使用Stream.Copy

【讨论】:

  • 感谢您提供宝贵的见解。我将更新源以反映单盐和Rfc2898DeriveBytes。关于盐,据我了解,当 key / iv 输入太短时它会有所帮助,所以我认为应该始终存在?
  • 它只有助于使输出独一无二。如果密码是一个字符,那么无论您使用什么盐,仍然只有 256 个可能的密码。
  • 那么将salt 存储在加密文件中是否完全安全?只要你知道password,你就可以从Rfc2898DeriveBytes推导出IV,并且可以从加密文件的头部检索salt
  • 是的,只有密钥是秘密的,不能派生。盐是可选的。警告:攻击者可以在您无法发现的情况下更改您的数据。使用经过身份验证的加密来确保内容没有被更改。
  • 我已经实施了您的建议,但经过身份验证的加密除外,因为有几种方法可以做到这一点,因此超出了 OP 的范围。感谢您的宝贵见解!
猜你喜欢
  • 1970-01-01
  • 2012-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-06
  • 1970-01-01
相关资源
最近更新 更多