【问题标题】:Truncating a stream of unknown length x bytes from the end? (.NET)从末尾截断未知长度x字节的流? (。网)
【发布时间】:2012-03-21 12:36:03
【问题描述】:

我需要读取一个长度未知的流,不包括最后 20 个字节(哈希数据)。设置大致如下:

源流(SHA1 哈希最后 20 个字节)-> SHA1 哈希流(即时计算并在流结束时与嵌入的流哈希进行比较)-> AES 解密流-> 处理数据...

我无法在处理之前缓冲整个源流,因为它可能有很多 GB,这一切都需要即时发生。源流不可搜索。目前,SHA1 流正在将最后 20 个字节读取到其缓冲区中,这会破坏一切,我不知道有任何方法可以控制这种行为。

我正在考虑在 Source 和 SHA1 流之间插入一个包装器流,实现一个滚动缓冲区(?),它以 4096 字节块将源流呈现给 AES 包装器,然后“伪造”流的结尾上次读取时提前 20 个字节。然后将通过属性公开 20 字节哈希。

这是最好的解决方案,我将如何实施?

粗略的代码流程如下(凭记忆,可能不会编译):

SourceStream = TcpClient.Stream
HashedStream = New CryptoStream(SourceStream, Sha1Hasher, CryptoStreamMode.Read)
AesDecryptedStream = New CryptoStream(HashedStream, AesDecryptor, CryptoStreamMode.Read)

' Read out and deserialize data
AesDecryptedStream.Read(etc...)

' Check if signatures match, throw data away if not
If Not Sha1Hash.SequenceEqual(ExpectedHash)

' Do stuff with the data here

编辑:流格式如下:

[  StreamFormat  |  String  |  Required  ]
[  WrapperFlags  |  8 Bit BitArray  |  Required  ]
[  Sha1 Hashed Data Wrapper  |  Optional  ]
   [  AesIV  |  16 Bytes  |  Required if Aes Encrypted  ]
   [  Aes Encrypted Data Wrapper  |  Optional  ]
      [  Gzip Compressed Data Wrapper  |  Optional  ]
         [  Payload Data  |  Binary  |  Required  ]
      [  End Gzip Compressed Data  ]
   [  End Aes Encrypted Data  ]
[  End Sha1 Hashed Data  ]
[  Sha1HashValue  |  20 Bytes  |  Required if Sha1 Hashed  ]

【问题讨论】:

    标签: .net stream buffer cryptostream


    【解决方案1】:

    我已经给你写了一个快速缓冲 20 字节的小流。我正确覆盖的唯一真正实现是Read() 成员,您可能必须根据您的情况适当地检查其他Stream 成员。还提供免费测试课程!奖金!我对其进行了更彻底的测试,但您可以根据自己的意愿调整这些测试用例。哦,顺便说一下,我没有针对长度小于 20 字节的流进行测试。

    测试用例

    [TestClass]
        public class TruncateStreamTests
        {
            [TestMethod]
            public void TestTruncateLast20Bytes()
            {
                string testInput = "This is a string.-- final 20 bytes --";
                string expectedOutput = "This is a string.";
                string testOutput;
                using (var testStream = new StreamWhichEndsBeforeFinal20Bytes(new MemoryStream(Encoding.ASCII.GetBytes(testInput))))
                using (var streamReader = new StreamReader(testStream, Encoding.ASCII))
                {
                    testOutput = streamReader.ReadLine();
                }
    
                Assert.AreEqual(expectedOutput, testOutput);
            }
    
            [TestMethod]
            public void TestTruncateLast20BytesRead3BytesAtATime()
            {
                string testInput = "This is a really really really really really long string, longer than all the others\n\rit even has some carriage returns in it, etc.-- final 20 bytes --";
                string expectedOutput = "This is a really really really really really long string, longer than all the others\n\rit even has some carriage returns in it, etc.";
                StringBuilder testOutputBuilder = new StringBuilder();
                using (var testStream = new StreamWhichEndsBeforeFinal20Bytes(new MemoryStream(Encoding.ASCII.GetBytes(testInput))))
                {
                    int bytesRead = 0;
                    do
                    {
                        byte[] buffer = new byte[3];
                        bytesRead = testStream.Read(buffer, 0, 3);
                        testOutputBuilder.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead));
                    } while (bytesRead > 0);
                }
                Assert.AreEqual(expectedOutput, testOutputBuilder.ToString());
            }
        }
    

    流类

     public class StreamWhichEndsBeforeFinal20Bytes : Stream
        {
            private readonly Stream sourceStream;
    
            private static int TailBytesCount = 20;
    
            public StreamWhichEndsBeforeFinal20Bytes(Stream sourceStream)
            {
                this.sourceStream = sourceStream; 
            }
    
            public byte[] TailBytes { get { return previousTailBuffer; } }
    
            public override void Flush()
            {
                sourceStream.Flush();
            }
    
            public override long Seek(long offset, SeekOrigin origin)
            {
                return sourceStream.Seek(offset, origin);
            }
    
            public override void SetLength(long value)
            {
                sourceStream.SetLength(value);
            }
    
            private byte[] previousTailBuffer;
    
            public override int Read(byte[] buffer, int offset, int count)
            {
                byte[] tailBuffer = new byte[TailBytesCount];
                int expectedBytesRead;
    
                if (previousTailBuffer == null)
                    expectedBytesRead = count + TailBytesCount;
                else
                    expectedBytesRead = count;
    
                try
                {
                    byte[] readBuffer = new byte[expectedBytesRead];
                    int actualBytesRead = sourceStream.Read(readBuffer, offset, expectedBytesRead);
    
                    if (actualBytesRead == 0) return 0;
    
                    if (actualBytesRead < TailBytesCount)
                    {
                        int pickPreviousByteCount = TailBytesCount - actualBytesRead;
    
                        if (previousTailBuffer != null)
                        {
                            int pickFromIndex = previousTailBuffer.Length - pickPreviousByteCount;
                            Array.Copy(previousTailBuffer, 0, buffer, offset, count);
                            Array.Copy(previousTailBuffer, pickFromIndex, tailBuffer, 0, pickPreviousByteCount);
                        }
    
                        Array.Copy(readBuffer, 0, tailBuffer, pickPreviousByteCount, actualBytesRead);
                        return actualBytesRead;
                    }
    
                    Array.Copy(readBuffer, actualBytesRead - TailBytesCount, tailBuffer, 0, TailBytesCount);
                    Array.Copy(readBuffer, 0, buffer, offset, actualBytesRead - TailBytesCount);
    
                    if (actualBytesRead < expectedBytesRead)
                    {
                        return actualBytesRead - TailBytesCount;
                    }
                    return count;
                }
                finally
                {
                    previousTailBuffer = tailBuffer;
                }
            }
    
    
            public override void Write(byte[] buffer, int offset, int count)
            {
                sourceStream.Write(buffer, offset, count);
            }
    
            public override bool CanRead
            {
                get { return sourceStream.CanRead; }
            }
    
            public override bool CanSeek
            {
                get { return sourceStream.CanSeek; }
            }
    
            public override bool CanWrite
            {
                get { return sourceStream.CanWrite; }
            }
    
            public override long Length
            {
                get
                {
                    if (sourceStream.Length < TailBytesCount) return sourceStream.Length;
                    return sourceStream.Length - TailBytesCount;
                }
            }
    
            public override long Position
            {
                get { return sourceStream.Position; }
                set { sourceStream.Position = value; }
            }
        }
    

    【讨论】:

    • 很棒的答案,还有测试用例!我确实需要对代码做一点小改动,我不确定这是否是由于我的一些怪癖,但 finally 块是在读取 0 字节并擦除尾随缓冲区后执行的。在 finally 语句中的检查解决了这个问题:If Not ActualBytesRead = 0 Then previousTailBuffer = tailBuffer End If
    猜你喜欢
    • 1970-01-01
    • 2010-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多