如何向CryptoStream添加搜索和位置功能

Kri*_*mar 8 c# cryptography stream

我试图使用带有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 ;
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 6

通常使用加密,输入字节和输出字节之间不存在1:1的映射,因此为了向后搜索(特别是),它必须做很多工作 - 甚至可能直接回到开始并向前移动处理数据以消耗解密流中的[n]个字节.即使它知道每个字节映射到的位置,加密的状态也依赖于它之前的数据(它不是解码器环; p),所以再次 - 它要么必须从头开始读取(和重置回初始化矢量),或者它必须跟踪位置和加密状态的快照,然后返回最近的快照,然后向前走.大量的工作和存储.

这也适用于相对于任何一端的寻求.

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

没有一种很好的方法来实现这一点,大多数消费者可以使用 - 通常如果你true从那CanSeek意味着"随机访问",但在这种情况下效率不高.

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

  • @ Justin808 如果您有办法以有用的方式处理加密数据而无需对其进行解密,那么加密社区很乐意听取您的意见。 (2认同)

Moh*_*van 5

它是如此简单,只需根据流的位置(stream.Position)生成与数据大小相同的长密钥,然后使用ECB或您喜欢的任何其他加密方法,然后应用XOR。它是可搜索的,非常快的并且是一对一的加密,其输出长度与输入长度完全相同。它具有高效的内存功能,您可以在大型文件上使用它。我认为这种方法也用于现代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]; //zero buffer is adequate since we have to use new salt for each stream
            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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

    }
Run Code Online (Sandbox Code Playgroud)