How to add seek and position capabilities to CryptoStream

前端 未结 3 776
春和景丽
春和景丽 2021-01-12 05:07

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 th

3条回答
  •  执笔经年
    2021-01-12 05:51

    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;
    
        /// //** WARNING **: MUST be unique for each stream otherwise there is NO security
        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);
    
        }
    

提交回复
热议问题