Добавить IV в начало CryptoStream - PullRequest
0 голосов
/ 21 мая 2018

Я использую локальное шифрование в рамках существующей программы управления файлами.

Большая часть примера кода, который я могу найти, например, Microsoft , демонстрирует, как писать напрямую в файл, но мне нужно предоставить поток, который используется в другом месте вprogram:

CryptoStream GetEncryptStream(string filename)
{
    var rjndl = new RijndaelManaged();
    rjndl.KeySize = 256;
    rjndl.BlockSize = 256;
    rjndl.Mode = CipherMode.CBC;
    rjndl.Padding = PaddingMode.PKCS7;

    // Open read stream of unencrypted source fileStream:
    var fileStream = new FileStream(filename, FileMode.Open); 

    /* Get key and iv */

    var transform = rjndl.CreateEncryptor(key, iv);

    // CryptoStream in *read* mode:
    var cryptoStream = new CryptoStream(fileStream, transform, CryptoStreamMode.Read); 

    /* What can I do here to insert the unencrypted IV at the start of the
       stream so that the first X bytes returned by cryptoStream.Read are
       the IV, before the bytes of the encrypted file are returned? */

    return cryptoStream; // Return CryptoStream to be consumed elsewhere
}

Моя проблема описана в комментарии к последней строке, но одна: как я могу добавить IV к началу CryptoStream таким образом, чтобы это были первые X байтов, возвращенных при CryptoStreamчитается, учитывая, что контроль того, когда фактически начинать чтение потока и запись в файл, выходит за рамки моего кода?

Ответы [ 3 ]

0 голосов
/ 21 мая 2018

Спасибо тем, кто нашел время, чтобы ответить.В конце концов я понял, что мне нужно знать длину IV в буферизирующем коде, и нет никакого способа обойти его, поэтому я решил сохранить его простым:

Метод шифрования (псевдокод):

/* Get key and IV */

outFileStream.Write(IV); // Write IV to output stream

var transform = rijndaelManaged.CreateEncryptor(key, iv);

// CryptoStream in read mode:
var cryptoStream = new CryptoStream(inFileStream, transform, CryptoStreamMode.Read);

do
{
    cryptoStream.Read(chunk, 0, blockSize); // Get and encrypt chunk
    outFileStream.Write(chunk);             // Write chunk
}
while (chunk.Length > 0)

/* Cleanup */

Метод дешифрования (псевдокод):

/* Get key */

var iv = inFileStream.Read(ivLength); // Get IV from input stream

var transform = rijndaelManaged.CreateDecryptor(key, iv);

// CryptoStream in write mode:
var cryptoStream = new CryptoStream(outFileStream, transform, CryptoStreamMode.Write);

do
{
    inFileStream.Read(chunk, 0, blockSize); // Get chunk
    cryptoStream.Write(chunk);              // Decrypt and write chunk
}
while (chunk.Length > 0)

/* Cleanup */
0 голосов
/ 21 мая 2018

Хорошо ... теперь, когда ваша проблема ясна, это "довольно" просто ... К сожалению. .NET не включает класс для объединения двух Stream, но мы можем легко создать его.MergedStream - это многопотоковое слияние только для чтения и только для пересылки.

Вы используете:

var mergedStream = new MergedStream(new Stream 
{
    new MemoryStream(iv),
    cryptoStream,
}

Сейчас ... Когда кто-то пытается прочитать из MergedStream сначала будет использовано MemoryStream, содержащее IV, затем будет cryptoStream.

public class MergedStream : Stream
{
    private Stream[] streams;

    private int position = 0;

    private int currentStream = 0;

    public MergedStream(Stream[] streams)
    {
        this.streams = streams;
    }

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => throw new NotImplementedException();

    public override long Position { get => position; set => throw new NotSupportedException(); }

    public override void Flush()
    {
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        if (streams == null)
        {
            throw new ObjectDisposedException(nameof(MergedStream));
        }

        if (currentStream >= streams.Length)
        {
            return 0;
        }

        int read;

        while (true)
        {
            read = streams[currentStream].Read(buffer, offset, count);
            position += read;

            if (read != 0)
            {
                break;
            }

            currentStream++;

            if (currentStream == streams.Length)
            {
                break;
            }
        }

        return read;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotSupportedException();
    }

    public override void SetLength(long value)
    {
        throw new NotSupportedException();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotSupportedException();
    }

    protected override void Dispose(bool disposing)
    {
        try
        {
            if (disposing && streams != null)
            {
                for (int i = 0; i < streams.Length; i++)
                {
                    streams[i].Close();
                }
            }
        }
        finally
        {
            streams = null;
        }
    }
}
0 голосов
/ 21 мая 2018

Не рекомендуется использовать CryptoStream для связи между двумя местными сторонами.Вместо этого вы должны использовать общий InputStream или канал (для межпроцессного взаимодействия).Затем вы можете объединить MemoryStream для IV и CryptoStream и вернуть комбинацию.См. ответ xanatos о том, как это сделать (вам может потребоваться заполнить функциональность Seek, если это необходимо).

A CryptoStream сможет только когда-либообрабатывать шифротекст.Так как вам нужно изменить код в приемнике в любом случае , если вы хотите расшифровать, вы можете также изменить рефакторинг на InputStream.


Если вам необходимо сохранитьтекущий дизайн, то есть взломать доступны.Сначала «расшифруйте» IV, используя режим ECB без заполнения.Поскольку вызов одиночного блочного шифра всегда завершается успешно, результатом будет блок данных, который - при шифровании с использованием CipherStream - снова превращается в IV.

Шаги:

  1. генерирует 16 случайных байтов в массиве, это будет настоящий IV;
  2. расшифровывает 16-байтовый IV использует ECB без заполнения и ключ, используемый для CipherStream;
  3. инициализировать CipherStream, используя ключ и все ноль, 16 байт IV;
  4. зашифровывает "расшифрованный" IV, используя CipherStream;
  5. , оставшуюся часть открытого текста.

Вам нужно будет создать InputStream, которыйсначала получает расшифрованный IV (как MemoryStream), а затем открытый текст (как FileStream), чтобы это было осуществимо.Снова также см. Ответ xanatos о том, как это сделать.Или посмотрите, например, этот сумматор и этот HugeStream в хорошем старом StackOverflow.Затем используйте объединенный поток в качестве источника для CipherInputStream.

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


Примечания:

  • Этот трюк не сработаетв любом режиме;он работает для режима CBC, но другие режимы могут использовать IV по-разному;
  • Обратите внимание, что OutputStream, как правило, имеет больше смысла для шифрования, могут быть и другие проблемы с дизайном.
...