如何向 CryptoStream 添加查找和定位功能

发布于 2024-10-17 19:31:37 字数 800 浏览 4 评论 0原文

我尝试将 CryptoStream 与 A​​WS .NET SDK 一起使用,但失败了,因为 CryptoStream 不支持查找。我在某处读到内容长度已知我们应该能够将这些功能添加到 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 ;
}

I was trying to use CryptoStream with AWS .NET SDk it failed as seek is not supported on CryptoStream. I read somewhere with content length known we should be able to add these capabilities to CryptoStream. I would like to know how to do this; any sample code will be useful too.

I have a method like this which is passed with a FieStream and returns a cryptoStream. I assign the returned Stream object to InputStream of AWS SDk PutObjectRequest object.

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 ;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

红尘作伴 2024-10-24 19:31:37

通常,对于加密,输入字节和输出字节之间不存在 1:1 映射,因此为了向后查找(特别是),它必须做大量工作 - 甚至可能是正确的回到开始并继续处理数据以消耗解密流中的[n]个字节。即使它知道每个字节映射到哪里,加密的状态也取决于它之前的数据(它不是解码器环;p),所以同样 - 它要么必须从头开始读取(并且重置回初始化向量),否则它将必须跟踪位置和加密状态的快照,并返回到最近的快照,然后向前走。大量的工作和存储。

这也适用于相对于任一端的搜索。

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

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

作为解决方法 - 考虑将解密的数据复制到 MemoryStream 或文件中;然后您可以以随机访问方式访问完全解密的数据。

Generally with encryption there isn't a 1:1 mapping between input bytes and output bytes, so in order to seek backwards (in particular) it would have to do a lot of work - perhaps even going right back to the start and moving forwards processing the data to consume [n] bytes from the decrypted stream. Even if it knew where each byte mapped to, the state of the encryption is dependent on the data that came before it (it isn't a decoder ring ;p), so again - it would either have to read from the start (and reset back to the initialisation-vector), or it would have to track snapshots of positions and crypto-states, and go back to the nearest snapshot, then walk forwards. Lots of work and storage.

This would apply to seeking relative to either end, too.

Moving forwards from the current position wouldn't be too bad, but again you'd have to process the data - not just jump the base-stream's position.

There isn't a good way to implement this that most consumers could use - normally if you get a true from CanSeek that means "random access", but that is not efficient in this case.

As a workaround - consider copying the decrypted data into a MemoryStream or a file; then you can access the fully decrypted data in a random-access fashion.

决绝 2024-10-24 19:31:37

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

为每个流使用唯一的盐,否则没有加密

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);
    }
}

用法:

static 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);

    }

It is so simple, just generate a long key with the same size as data by the position of the stream (stream.Position) and use ECB or any other encryption methods you like and then apply XOR. It is seekable, very fast and 1 to 1 encryption, which the output length is exactly same as the input length. It is memory efficient and you can use it on huge files. I think this method is used in modern WinZip AES encryption too. The only thing that you MUST be careful is the salt

Use a unique salt for each stream otherwise there is no encryption.

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);
    }
}

Usage:

static 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);

    }
会傲 2024-10-24 19:31:37

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

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

As an extension to Mark Gravell's answer, the seekability of a cipher depends on the Mode Of Operation you're using for the cipher. Most modes of operation aren't seekable, because each block of ciphertext depends in some way on the previous one. ECB is seekable, but it's almost universally a bad idea to use it. CTR mode is another one that can be accessed randomly, as is CBC.

All of these modes have their own vulnerabilities, however, so you should read carefully and think long and hard (and preferably consult an expert) before choosing one.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文