Rijndael文件加密/解密
Rijndael file encryption / decryption
过去几天,我根据 RijndaelManaged class 可用的 Rijndael 加密标准创建了一个文件加密/解密 class,并搜索了我能找到的所有资源和示例.这些示例不是过时的、损坏的就是有限的,但至少设法学到了很多东西,并且认为我会 post 在确保它是健壮的并且经过您的批评之后我会 post 它的最新版本。
到目前为止我发现的唯一问题是需要知道盐,因为没有办法将它存储在加密文件中,就像您对字符串所做的那样,除非您转换每个字节 read/write 到基于 read/write 的缓冲区,但是你需要在解密时满足它,并且还需要至少 4 个字节的数据来加密(虽然我真的不认为这是一个问题但确实需要被提及)。
我也不完全确定 1 个 salt 是否足以同时满足密钥和初始化向量,或者出于安全原因两个更好?
任何其他意见和/或优化也将不胜感激
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. 重构为单个 salt
和 Rfc2898DerivedBytes
,现在从 password
+ salt
推导出 IV
。
3. 使加密/解密线程安全(如果我做的不正确请告诉我)。
编辑 2:
1. 重构使读/写使用缓冲区而不是单字节读/写。
2. 在加密文件中嵌入盐并清理变量(但仍然允许 passPhrase
默认为 "copy/paste" 示例。
3. 重构文件句柄。
您可能每次都应该使用不同的 IV。如果您对相同的数据使用相同的 IV,则结果是相同的。攻击者现在可以推断出文件(部分)相同,这就是泄漏。您可以生成 16 个强随机字节并将它们用作 Rfc2898DeriveBytes
的盐。将这些字节添加到文件中。仅使用一个 Rfc2898DeriveBytes
来生成 IV 和密钥。或者,您可以完全不对密钥使用盐并随机生成 IV。 salt 可用于使密钥派生对于您的用例是唯一的,或者例如为您的应用程序的每个用户提供不同的密钥派生算法。
请注意,按字节处理流非常慢。使用缓冲区。也许,你应该使用 Stream.Copy
.
过去几天,我根据 RijndaelManaged class 可用的 Rijndael 加密标准创建了一个文件加密/解密 class,并搜索了我能找到的所有资源和示例.这些示例不是过时的、损坏的就是有限的,但至少设法学到了很多东西,并且认为我会 post 在确保它是健壮的并且经过您的批评之后我会 post 它的最新版本。
到目前为止我发现的唯一问题是需要知道盐,因为没有办法将它存储在加密文件中,就像您对字符串所做的那样,除非您转换每个字节 read/write 到基于 read/write 的缓冲区,但是你需要在解密时满足它,并且还需要至少 4 个字节的数据来加密(虽然我真的不认为这是一个问题但确实需要被提及)。
我也不完全确定 1 个 salt 是否足以同时满足密钥和初始化向量,或者出于安全原因两个更好?
任何其他意见和/或优化也将不胜感激
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. 重构为单个 salt
和 Rfc2898DerivedBytes
,现在从 password
+ salt
推导出 IV
。
3. 使加密/解密线程安全(如果我做的不正确请告诉我)。
编辑 2:
1. 重构使读/写使用缓冲区而不是单字节读/写。
2. 在加密文件中嵌入盐并清理变量(但仍然允许 passPhrase
默认为 "copy/paste" 示例。
3. 重构文件句柄。
您可能每次都应该使用不同的 IV。如果您对相同的数据使用相同的 IV,则结果是相同的。攻击者现在可以推断出文件(部分)相同,这就是泄漏。您可以生成 16 个强随机字节并将它们用作 Rfc2898DeriveBytes
的盐。将这些字节添加到文件中。仅使用一个 Rfc2898DeriveBytes
来生成 IV 和密钥。或者,您可以完全不对密钥使用盐并随机生成 IV。 salt 可用于使密钥派生对于您的用例是唯一的,或者例如为您的应用程序的每个用户提供不同的密钥派生算法。
请注意,按字节处理流非常慢。使用缓冲区。也许,你应该使用 Stream.Copy
.