Как добавить возможности поиска и позиционирования в CryptoStream

Я пытался использовать CryptoStream с AWS .NET SDK. Не удалось, так как поиск не поддерживается на CryptoStream. Я читал где-то с длиной содержимого, известной, что мы должны иметь возможность добавить эти возможности в CryptoStream. Я хотел бы знать, как это сделать; любой пример кода тоже будет полезен.

У меня есть такой метод, который передается с FieStream и возвращает cryptoStream. Я присваиваю возвращенный объект Stream объекту InputStream объекта AWS SDk PutObjectRequest.

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

3 ответа

Как правило, при шифровании не существует отображения 1:1 между входными и выходными байтами, поэтому для поиска в обратном направлении (в частности) ему придется проделать большую работу - возможно, даже вернуться назад к началу и двигаться вперед. обработка данных для использования [n] байтов из расшифрованного потока. Даже если бы он знал, куда сопоставлен каждый байт, состояние шифрования зависит от данных, которые были до него (это не кольцо декодера;p), так что еще раз - ему придется либо читать с самого начала (и вернуться к исходному вектору инициализации), иначе придется отслеживать моментальные снимки позиций и криптосостояний, возвращаться к ближайшему моментальному снимку, а затем идти вперед. Много работы и хранения.

Это относится и к поиску относительно любого конца.

Движение вперед от текущей позиции не будет слишком плохим, но опять же вам придется обрабатывать данные, а не просто перескакивать позицию базового потока.

Существует не хороший способ реализовать это, что большинство потребителей могли бы использовать - обычно, если вы получаете true от CanSeek это означает "произвольный доступ", но в данном случае это неэффективно.

В качестве обходного пути - рассмотрите возможность копирования расшифрованных данных в MemoryStream или файл; тогда вы можете получить доступ к полностью расшифрованным данным в режиме произвольного доступа.

Это так просто, просто сгенерируйте длинный ключ того же размера, что и данные, по положению потока (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]; //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);
    }
}

Использование:

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

    }

В качестве дополнения к ответу Марка Гравелла, возможность поиска шифра зависит от режима работы, который вы используете для шифра. Большинство режимов работы недоступны для поиска, потому что каждый блок зашифрованного текста каким-то образом зависит от предыдущего. ЕЦБ можно искать, но почти всегда плохая идея использовать его. Режим CTR - это еще один режим, к которому можно получить произвольный доступ, как и CBC.

Однако все эти режимы имеют свои собственные уязвимости, поэтому вам следует внимательно прочитать и тщательно подумать (и желательно проконсультироваться с экспертом), прежде чем выбирать один.

Другие вопросы по тегам