在 .NET 中实现 en/decrypting 流的正确方法?
Correct way to implement en/decrypting stream in .NET?
我想导入和导出一个旧的游戏文件格式,它的数据是加密的。详情见here;简而言之,文件被分成多个块,每个块使用一种基于前一个 uint 的特定类型的 XOR 加密,并且校验和跟踪每个块,我在读取数据时需要跳过它。
通常,我想设计放置在游戏文件上的可重复使用的流,如果有一个流在后台进行加密/解密,而开发人员只使用 BinaryReader/Writer
做一些 ReadUInt32()
事情等
到目前为止,我研究过 .NET 中有一个 CryptoStream
class,"correct" 实现 en/decryption 的方法是否从继承 [=23] 开始=]?我没有找到任何关于以这种方式尝试过的人的文章,因此我不确定我在这里是否完全错误。
不,从 CryptoStream
继承不是正确的做法。如果您想走那条路,正确的方法是创建一个实现 ICryptoTransform
的 class 并将解密和加密逻辑放在那里。然后将 ICryptoTransform
class 作为参数传递给 CryptoStream
.
虽然不是 C#,this MSDN page may provide some insight, showing implementation of the ICryptoTransform
interface。
这是一个在 C# 中可能看起来如何的示例,使用您在用例中提到的 XOR-with-previous-block(毫无疑问,您将不得不调整它以匹配您的确切算法):
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
class XORCryptoTransform : ICryptoTransform
{
uint previous;
bool encrypting;
public XORCryptoTransform(uint iv, bool encrypting)
{
previous = iv;
this.encrypting = encrypting;
}
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
for (int i = 0; i < inputCount; i+=4)
{
uint block = BitConverter.ToUInt32(inputBuffer, inputOffset + i);
byte[] transformed = BitConverter.GetBytes(block ^ previous);
Array.Copy(transformed, 0, outputBuffer, outputOffset + i, Math.Min(transformed.Length, outputBuffer.Length - outputOffset -i));
if (encrypting)
{
previous = block;
}
else
{
previous = BitConverter.ToUInt32(transformed, 0);
}
}
return inputCount;
}
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
{
var transformed = new byte[inputCount];
TransformBlock(inputBuffer, inputOffset, inputCount, transformed, 0);
return transformed;
}
public bool CanReuseTransform
{
get { return true; }
}
public bool CanTransformMultipleBlocks
{
get { return true; }
}
public int InputBlockSize
{
// 4 bytes in uint
get { return 4; }
}
public int OutputBlockSize
{
get { return 4; }
}
public void Dispose()
{
}
}
class Program
{
static void Main()
{
uint iv = 0; // first block will not be changed
byte[] plaintext = Guid.NewGuid().ToByteArray();
byte[] ciphertext;
using (var memoryStream = new MemoryStream())
{
using (var encryptStream = new CryptoStream(
memoryStream,
new XORCryptoTransform(iv, true),
CryptoStreamMode.Write))
{
encryptStream.Write(plaintext, 0, plaintext.Length);
}
ciphertext = memoryStream.ToArray();
}
byte[] decrypted = new byte[ciphertext.Length];
using (var memoryStream = new MemoryStream(ciphertext))
using (var encryptStream = new CryptoStream(
memoryStream,
new XORCryptoTransform(iv, false),
CryptoStreamMode.Read))
{
encryptStream.Read(decrypted, 0, decrypted.Length);
}
bool matched = plaintext.SequenceEqual(decrypted);
Console.WriteLine("Matched: {0}", matched);
}
}
在此示例中,如果输入数据是块长度的倍数(在您的情况下 uint
为 4 个字节),则在 TransformFinalBlock
中将无事可做。但是,如果数据不是块长度的倍数,剩余的字节将在那里处理。
.NET 自动用零填充传递给 TransformFinalBlock
的数组以使其达到块长度,但您可以通过检查 inputCount
(这将是实际的输入长度,而不是填充长度)并在您的算法需要时替换为您自己的自定义(非零)填充。
我想导入和导出一个旧的游戏文件格式,它的数据是加密的。详情见here;简而言之,文件被分成多个块,每个块使用一种基于前一个 uint 的特定类型的 XOR 加密,并且校验和跟踪每个块,我在读取数据时需要跳过它。
通常,我想设计放置在游戏文件上的可重复使用的流,如果有一个流在后台进行加密/解密,而开发人员只使用 BinaryReader/Writer
做一些 ReadUInt32()
事情等
到目前为止,我研究过 .NET 中有一个 CryptoStream
class,"correct" 实现 en/decryption 的方法是否从继承 [=23] 开始=]?我没有找到任何关于以这种方式尝试过的人的文章,因此我不确定我在这里是否完全错误。
不,从 CryptoStream
继承不是正确的做法。如果您想走那条路,正确的方法是创建一个实现 ICryptoTransform
的 class 并将解密和加密逻辑放在那里。然后将 ICryptoTransform
class 作为参数传递给 CryptoStream
.
虽然不是 C#,this MSDN page may provide some insight, showing implementation of the ICryptoTransform
interface。
这是一个在 C# 中可能看起来如何的示例,使用您在用例中提到的 XOR-with-previous-block(毫无疑问,您将不得不调整它以匹配您的确切算法):
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
class XORCryptoTransform : ICryptoTransform
{
uint previous;
bool encrypting;
public XORCryptoTransform(uint iv, bool encrypting)
{
previous = iv;
this.encrypting = encrypting;
}
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
for (int i = 0; i < inputCount; i+=4)
{
uint block = BitConverter.ToUInt32(inputBuffer, inputOffset + i);
byte[] transformed = BitConverter.GetBytes(block ^ previous);
Array.Copy(transformed, 0, outputBuffer, outputOffset + i, Math.Min(transformed.Length, outputBuffer.Length - outputOffset -i));
if (encrypting)
{
previous = block;
}
else
{
previous = BitConverter.ToUInt32(transformed, 0);
}
}
return inputCount;
}
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
{
var transformed = new byte[inputCount];
TransformBlock(inputBuffer, inputOffset, inputCount, transformed, 0);
return transformed;
}
public bool CanReuseTransform
{
get { return true; }
}
public bool CanTransformMultipleBlocks
{
get { return true; }
}
public int InputBlockSize
{
// 4 bytes in uint
get { return 4; }
}
public int OutputBlockSize
{
get { return 4; }
}
public void Dispose()
{
}
}
class Program
{
static void Main()
{
uint iv = 0; // first block will not be changed
byte[] plaintext = Guid.NewGuid().ToByteArray();
byte[] ciphertext;
using (var memoryStream = new MemoryStream())
{
using (var encryptStream = new CryptoStream(
memoryStream,
new XORCryptoTransform(iv, true),
CryptoStreamMode.Write))
{
encryptStream.Write(plaintext, 0, plaintext.Length);
}
ciphertext = memoryStream.ToArray();
}
byte[] decrypted = new byte[ciphertext.Length];
using (var memoryStream = new MemoryStream(ciphertext))
using (var encryptStream = new CryptoStream(
memoryStream,
new XORCryptoTransform(iv, false),
CryptoStreamMode.Read))
{
encryptStream.Read(decrypted, 0, decrypted.Length);
}
bool matched = plaintext.SequenceEqual(decrypted);
Console.WriteLine("Matched: {0}", matched);
}
}
在此示例中,如果输入数据是块长度的倍数(在您的情况下 uint
为 4 个字节),则在 TransformFinalBlock
中将无事可做。但是,如果数据不是块长度的倍数,剩余的字节将在那里处理。
.NET 自动用零填充传递给 TransformFinalBlock
的数组以使其达到块长度,但您可以通过检查 inputCount
(这将是实际的输入长度,而不是填充长度)并在您的算法需要时替换为您自己的自定义(非零)填充。