【问题标题】:Correct way to implement en/decrypting stream in .NET?在 .NET 中实现加密/解密流的正确方法?
【发布时间】:2015-03-08 13:59:40
【问题描述】:

我想导入和导出一个旧的游戏文件格式,它的数据是加密的。详情见here;简要总结一下,文件被分成块,每个块都使用基于前一个 uint 的特定类型的 XOR 加密,并且在读取数据时我需要跳过的每个块都有一个校验和。

通常,我想设计放置在游戏文件上的流以可重用,如果有一个流在后台进行加密/解密,那就太好了,而开发人员只使用BinaryReader/Writer做一些ReadUInt32() 的东西等等。

到目前为止,我研究了 .NET 中有一个 CryptoStream 类,实现加密/解密的“正确”方法会从继承该类开始吗?我没有找到关于有人尝试过这种方式的文章,因此我不确定我是否完全错了。

【问题讨论】:

    标签: c# encryption stream file-format


    【解决方案1】:

    虽然不是 C#,但 this MSDN page 可能会提供一些见解,展示 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 来检测它(这将是实际的输入长度,而不是填充的长度)并在您的算法需要时用您自己的自定义(非零)填充替换。

    【讨论】:

    • 感谢 C# 代码示例,.NET 框架中的摘要会引发我一些代码回答的问题 :)
    • Array.Copy 行中,长度不应该是最大值。 inputCount?我试图转换最后一个块,实际上只有 1 个字节,但他想将 4 个字节写入长度为 1 的字节数组。
    • 根据我对您问题的理解,您在 uint-length(即 4 个字节)的块中进行异或运算,因此如果您输入的长度不是 4 个字节的倍数那么你需要把它填充到那个长度。
    • 真实想法。这个问题在实践中几乎不会出现。我只是在用一串愚蠢的长度进行测试。我的错!
    • 这是一个很好的观点,我已经更新了我的示例,以便它可以处理不是块长度倍数的数据。我还注意到从CryptoStream.Dispose 调用TransformFinalBlock 的方式有一种特殊性,因此我将MemoryStream.ToArray 移至CryptoStream 在加密时被处理后。
    【解决方案2】:

    不,从CryptoStream 继承不是正确的做法。如果您想以正确的方式走这条路,请创建一个实现ICryptoTransform 的类,并将解密和加密逻辑放入其中。然后将ICryptoTransform 类作为参数传递给CryptoStream

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-05
      • 2022-12-11
      • 1970-01-01
      • 1970-01-01
      • 2014-11-21
      • 2019-10-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多