C# 如何向CryptoStream添加搜索和定位功能

C# 如何向CryptoStream添加搜索和定位功能,c#,stream,cryptography,C#,Stream,Cryptography,我试图在AWS.NET SDk中使用CryptoStream,但失败了,因为CryptoStream不支持seek。我在某个地方读到,内容长度已知,我们应该能够将这些功能添加到CryptoStream。我想知道如何做到这一点;任何示例代码都将非常有用 我有一个类似这样的方法,它通过FieStream传递并返回一个cryptoStream。我将返回的Stream对象分配给AWS SDk PutObjectRequest对象的InputStream public static Stream GetE

我试图在AWS.NET SDk中使用CryptoStream,但失败了,因为CryptoStream不支持seek。我在某个地方读到,内容长度已知,我们应该能够将这些功能添加到CryptoStream。我想知道如何做到这一点;任何示例代码都将非常有用

我有一个类似这样的方法,它通过FieStream传递并返回一个cryptoStream。我将返回的Stream对象分配给AWS SDk PutObjectRequest对象的InputStream

public static Stream GetEncryptStream(Stream existingStream,
    SymmetricAlgorithm cryptoServiceProvider,
    string encryptionKey, string encryptionIV)
{
    Stream existingStream = this.dataStream;

    cryptoServiceProvider.Key = ASCIIEncoding.ASCII.GetBytes(encryptionKey);
    cryptoServiceProvider.IV = ASCIIEncoding.ASCII.GetBytes(encryptionIV);
    CryptoStream cryptoStream = new CryptoStream(existingStream,
        cryptoServiceProvider.CreateEncryptor(), CryptoStreamMode.Read);

    return cryptoStream ;
}

通常,使用加密时,输入字节和输出字节之间没有1:1的映射,因此为了向后(特别是)查找,它必须做大量的工作——甚至可能回到开始,向前处理数据,以消耗解密流中的[n]个字节。即使它知道每个字节映射到何处,加密的状态也取决于它之前的数据(它不是解码器环;p),因此,它必须从头开始读取(并重置回初始化向量),或者必须跟踪位置和加密状态的快照,然后返回最近的快照,然后向前走。大量的工作和存储

这也适用于相对两端的搜索

从当前位置向前移动不会太糟糕,但是您必须再次处理数据,而不仅仅是跳转基本流的位置

没有一种好的方法可以实现这一点,大多数消费者都可以使用——通常情况下,如果您从
CanSeek
获得
true
,这意味着“随机访问”,但在这种情况下,这是不有效的


作为解决方案——考虑将解密后的数据复制到<代码>内存流< /代码>或文件中;然后,您可以以随机访问方式访问完全解密的数据。

作为标记Gravell答案的扩展,密码的可查找性取决于您使用的密码。大多数操作模式是不可查找的,因为每个密文块在某种程度上取决于前一个密文块。欧洲央行是可行的,但使用它几乎是一个普遍的坏主意。CTR模式是另一种可以随机访问的模式,CBC也是


但是,所有这些模式都有其自身的漏洞,因此在选择之前,您应该仔细阅读并仔细考虑(最好咨询专家)。

非常简单,只需根据流的位置(stream.position)生成与数据大小相同的长密钥即可使用ECB或任何其他您喜欢的加密方法,然后应用XOR。它是可查找的、非常快速的1对1加密,输出长度与输入长度完全相同。它的内存效率很高,您可以在大型文件上使用它。我认为现代WinZip AES加密中也使用了这种方法。你唯一需要注意的是盐

对每个流使用唯一的salt,否则没有加密

public class SeekableAesStream : Stream
{
    private Stream baseStream;
    private AesManaged aes;
    private ICryptoTransform encryptor;
    public bool autoDisposeBaseStream { get; set; } = true;

    /// <param name="salt">//** WARNING **: MUST be unique for each stream otherwise there is NO security</param>
    public SeekableAesStream(Stream baseStream, string password, byte[] salt)
    {
        this.baseStream = baseStream;
        using (var key = new PasswordDeriveBytes(password, salt))
        {
            aes = new AesManaged();
            aes.KeySize = 128;
            aes.Mode = CipherMode.ECB;
            aes.Padding = PaddingMode.None;
            aes.Key = key.GetBytes(aes.KeySize / 8);
            aes.IV = new byte[16]; //useless for ECB
            encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
        }
    }

    private void cipher(byte[] buffer, int offset, int count, long streamPos)
    {
        //find block number
        var blockSizeInByte = aes.BlockSize / 8;
        var blockNumber = (streamPos / blockSizeInByte) + 1;
        var keyPos = streamPos % blockSizeInByte;

        //buffer
        var outBuffer = new byte[blockSizeInByte];
        var nonce = new byte[blockSizeInByte];
        var init = false;

        for (int i = offset; i < count; i++)
        {
            //encrypt the nonce to form next xor buffer (unique key)
            if (!init || (keyPos % blockSizeInByte) == 0)
            {
                BitConverter.GetBytes(blockNumber).CopyTo(nonce, 0);
                encryptor.TransformBlock(nonce, 0, nonce.Length, outBuffer, 0);
                if (init) keyPos = 0;
                init = true;
                blockNumber++;
            }
            buffer[i] ^= outBuffer[keyPos]; //simple XOR with generated unique key
            keyPos++;
        }
    }

    public override bool CanRead { get { return baseStream.CanRead; } }
    public override bool CanSeek { get { return baseStream.CanSeek; } }
    public override bool CanWrite { get { return baseStream.CanWrite; } }
    public override long Length { get { return baseStream.Length; } }
    public override long Position { get { return baseStream.Position; } set { baseStream.Position = value; } }
    public override void Flush() { baseStream.Flush(); }
    public override void SetLength(long value) { baseStream.SetLength(value); }
    public override long Seek(long offset, SeekOrigin origin) { return baseStream.Seek(offset, origin); }

    public override int Read(byte[] buffer, int offset, int count)
    {
        var streamPos = Position;
        var ret = baseStream.Read(buffer, offset, count);
        cipher(buffer, offset, count, streamPos);
        return ret;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        cipher(buffer, offset, count, Position);
        baseStream.Write(buffer, offset, count);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            encryptor?.Dispose();
            aes?.Dispose();
            if (autoDisposeBaseStream)
                baseStream?.Dispose();
        }

        base.Dispose(disposing);
    }
}
公共类seekableasstream:Stream
{
私有流基流;
私人管理的aes;
专用加密机;
公共bool autodisposebastream{get;set;}=true;
/////**警告**:每个流必须是唯一的,否则没有安全性
public-seekableastream(流基流,字符串密码,字节[]salt)
{
this.baseStream=baseStream;
使用(var key=newpasswordderivebytes(password,salt))
{
aes=新aes管理();
aes.KeySize=128;
aes.Mode=CipherMode.ECB;
aes.Padding=PaddingMode.None;
aes.Key=Key.GetBytes(aes.KeySize/8);
aes.IV=新字节[16];//对ECB无效
encryptor=aes.CreateEncryptor(aes.Key,aes.IV);
}
}
专用无效密码(字节[]缓冲区、整数偏移量、整数计数、长streamPos)
{
//查找块号
var blockSizeInByte=aes.BlockSize/8;
变量blockNumber=(streamPos/blockSizeInByte)+1;
var keyPos=streamPos%blockSizeInByte;
//缓冲区
var exputffer=新字节[blockSizeInByte];
var nonce=新字节[blockSizeInByte];
var init=false;
for(int i=偏移量;istatic void test()
    {
        var buf = new byte[255];
        for (byte i = 0; i < buf.Length; i++)
            buf[i] = i;

        //encrypting
        var uniqueSalt = new byte[16]; //** WARNING **: MUST be unique for each stream otherwise there is NO security
        var baseStream = new MemoryStream();
        var cryptor = new SeekableAesStream(baseStream, "password", uniqueSalt);
        cryptor.Write(buf, 0, buf.Length);

        //decrypting at position 200
        cryptor.Position = 200;
        var decryptedBuffer = new byte[50];
        cryptor.Read(decryptedBuffer, 0, 50);

    }