Как использовать RSA для шифрования файлов (огромных данных) в C#

Я новичок в шифровании. Мне нужно реализовать алгоритм асимметричного шифрования, который, я думаю, использует закрытый / открытый ключ. Я начал использовать образец RSACryptoServiceProvider. это было нормально с небольшими данными для шифрования. Но при использовании его на относительно больших данных "2 строки" я получаю исключение CryptographicException "Bad Length"!

//Create a new instance of RSACryptoServiceProvider.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{

    //Import the RSA Key information. This only needs
    //toinclude the public key information.
    //RSA.ImportParameters(RSAKeyInfo);
    byte[] keyValue = Convert.FromBase64String(publicKey);
    RSA.ImportCspBlob(keyValue);

    //Encrypt the passed byte array and specify OAEP padding.  
    //OAEP padding is only available on Microsoft Windows XP or
    //later.  
    encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
}

Затем я нашел несколько примеров шифрования больших данных (или файлов) с помощью CryptoStream и использую только симметричные алгоритмы, такие как DES или 3DES, которые имеют функцию CreateEncryptor для возврата ICryptoTransform в качестве одного из входных данных для конструктора CryptoStream!!!

CryptoStream cStream = new CryptoStream(fStream,
                new TripleDESCryptoServiceProvider().CreateEncryptor(Key, IV),
                CryptoStreamMode.Write);

Какой способ шифрования файлов с использованием RSA?

7 ответов

Решение

Как уже упоминалось в других ответах, асимметричное шифрование предназначено только для шифрования данных, размер которых меньше размера его ключа.

Одна из опций, которую я реализовал, когда нужно передавать большие объемы зашифрованных данных между двумя системами, - это иметь пару ключей RSA, открытый ключ которой известен и отправителю, и получателю, тогда, когда данные должны быть отправлены, получатель генерирует новую пару ключей RSA., шифрует открытый ключ этой пары ключей с помощью общего открытого ключа и отправляет зашифрованный открытый ключ отправителю. Отправитель расшифровывает открытый ключ получателя, используя свой закрытый ключ (который не должен знать получатель, так же как отправителю не нужно знать сгенерированный закрытый ключ получателя), генерирует симметричный ключ шифрования, шифрует данные симметричным ключом и затем шифрует симметричный ключ, используя открытый ключ, полученный от получателя. Затем зашифрованный симметричный ключ и зашифрованные данные отправляются получателю, который использует сгенерированный закрытый ключ для дешифрования симметричного ключа, а затем дешифрует данные.

Вы можете использовать RSACryptoServiceProvider.ToXMLString() а также RSACryptoServiceProvider.FromXMLString() методы для хранения общего открытого ключа в виде строкового литерала XML в приложении-получателе.

Не забывайте, когда вы генерируете симметричный ключ шифрования для использования RNGCryptoServiceProvider() генерировать ключ, так как это гораздо более безопасный метод генерации (псевдо) случайных чисел.

Кроме того, я настоятельно рекомендую не использовать 3DES в качестве алгоритма симметричного шифрования, он устарел и начинает показывать свой возраст. Используйте симметричное шифрование AES либо с AesCryptoServiceProvicer или же RijndaelManaged классы.

RSA может шифровать только блоки данных, длина которых меньше длины ключа, поэтому обычно

  1. Генерируйте случайный ключ правильной длины, необходимой для AES (или аналогичного).
  2. Зашифруйте ваши данные, используя AES или аналогичный, используя этот ключ
  3. Зашифруйте случайный ключ, используя свой ключ RSA

Затем вы публикуете оба выхода из 2 и 3

Расшифровать

  1. Расшифруйте ключ AES с помощью ключа RSA.
  2. Расшифруйте данные, используя этот ключ AES

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

Асимметричное шифрование недостаточно эффективно для передачи большого количества данных.

Для дальнейших поисков в отношении исключений неправильной длины RSA...

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

((KeySize - 384) / 8) + 37

Однако если параметр оптимального асимметричного дополнения шифрования (OAEP) имеет значение true, для вычисления максимального количества байтов можно использовать следующее:

((KeySize - 384) / 8) + 7

Размеры легального ключа - от 384 до 16384 с размером пропуска 8.

.NET-реализации RSA (и все алгоритмы с открытым / закрытым ключом) не поддерживают большие блоки данных - потому что это не цель открытого / закрытого ключа.

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

У нас есть:

MaxBlockSize=((KeySize - 384) / 8) + 37

ИЛИ ЖЕ

MaxBlockSize=((KeySize - 384) / 8) + 7

Таким образом, мы можем разделить данные на несколько блоков, а затем зашифровать каждый из них, а затем объединить их

      public class RsaService : System.IDisposable
{
public delegate int TransformBlockCall(System.ReadOnlySpan<byte> data, System.Span<byte> destination);

private readonly RSA _encoder;
private readonly RSAEncryptionPadding _padding;

private readonly TransformBlockCall _encryptBlockCall;
private readonly TransformBlockCall _decryptBlockCall;

private int _encrypt_InputBlockSize;
private int _encrypt_OutputBlockSize;
private int _decrypt_InputBlockSize;
private int _decrypt_OutputBlockSize;

public RsaService(RSA encoder) {
    if(encoder == null)
        throw new System.ArgumentNullException(nameof(encoder));
    _encoder = encoder;

    _padding = RSAEncryptionPadding.Pkcs1;

    _encryptBlockCall = new TransformBlockCall(EncryptBlock);
    _decryptBlockCall = new TransformBlockCall(DecryptBlock);

    OnEndSetParameters();
}

private void OnEndSetParameters() {
    _encrypt_InputBlockSize = GetSizeOutputEncryptOfKeySize(_encoder.KeySize);
    _encrypt_OutputBlockSize = _encoder.KeySize / 8;
    _decrypt_InputBlockSize = _encrypt_OutputBlockSize;
    _decrypt_OutputBlockSize = _encrypt_OutputBlockSize;
}

public void ImportParameters(RSAParameters parameters) {
    _encoder.ImportParameters(parameters);
    OnEndSetParameters();
}

public byte[] Encrypt(byte[] data) {
    if(data == null) throw new System.ArgumentNullException(nameof(data));
    if(data.Length == 0) return data;
    int outputLength = GetEncryptOutputMaxByteCount(data.Length);
    byte[] outputData = new byte[outputLength];
    Encrypt(data, outputData);
    return outputData;
}


public byte[] Decrypt(byte[] data) {
    if(data == null) throw new System.ArgumentNullException(nameof(data));
    if(data.Length == 0) return data;
    int maxOutputLength = GetDecryptOutputMaxByteCount(data.Length);
    byte[] outputData = new byte[maxOutputLength];
    int actual_OutputLength = Decrypt(data, outputData);
    if(maxOutputLength > actual_OutputLength)
        System.Array.Resize(ref outputData, actual_OutputLength);
    return outputData;
}

public int Encrypt(System.ReadOnlySpan<byte> data, System.Span<byte> destination) {
#if DEBUG
    int inputBlockSize = _encrypt_InputBlockSize;
    int outputBlockSize = _encoder.KeySize / 8;
    int blockCount = (data.Length / inputBlockSize);
    if(data.Length % inputBlockSize != 0)
        blockCount++;
    System.Diagnostics.Debug.Assert((blockCount * outputBlockSize) <= destination.Length);
#endif

    if(data.Length > _encrypt_InputBlockSize)
        return TransformFinal(_encryptBlockCall, data, destination, _encrypt_InputBlockSize);
    else
        return _encryptBlockCall(data, destination);
}


public int Decrypt(System.ReadOnlySpan<byte> data, System.Span<byte> destination) {
    if(data.Length > _decrypt_InputBlockSize)
        return TransformFinal(_decryptBlockCall, data, destination, _decrypt_InputBlockSize);
    else
        return _decryptBlockCall(data, destination);
}

private int EncryptBlock(System.ReadOnlySpan<byte> data, System.Span<byte> destination) => _encoder.Encrypt(data, destination, _padding);
private int DecryptBlock(System.ReadOnlySpan<byte> data, System.Span<byte> destination) => _encoder.Decrypt(data, destination, _padding);

public int GetEncryptOutputMaxByteCount(int inputCount) => GetBlockCount(inputCount, _encrypt_InputBlockSize) * _encrypt_OutputBlockSize;
public int GetDecryptOutputMaxByteCount(int inputCount) => GetBlockCount(inputCount, _decrypt_InputBlockSize) * _decrypt_OutputBlockSize;
public void Dispose() {
    _encoder.Dispose();
    System.GC.SuppressFinalize(this);
}


#region Methods_Helper

public static RsaService Create(RSAParameters parameters) => new RsaService(RSA.Create(parameters));

public static RsaService Create() => new RsaService(RSA.Create());

// [keySize] ÷ 8 - [11 bytes for padding] = Result
// Exsimple: [2048 key size] ÷ 8 - [11 bytes for padding] = 245
public static int GetSizeOutputEncryptOfKeySize(int keySize) => (keySize / 8) - 11;

private static int GetBlockCount(int dataLength,int inputBlockSize) {
    int blockCount = (dataLength / inputBlockSize);
    if(dataLength % inputBlockSize != 0)
        blockCount++;
    return blockCount;
}

public static int TransformFinal(TransformBlockCall transformBlockCall, System.ReadOnlySpan<byte> data, System.Span<byte> destination, int inputBlockSize) {

    int blockCount = GetBlockCount(data.Length, inputBlockSize);

    int data_writtenCount = 0;
    int destination_writtenCount = 0;
    while(blockCount-- > 0) {
        if(blockCount == 0) {
            inputBlockSize = data.Length - data_writtenCount;
            if(inputBlockSize == 0) break;
        }
        destination_writtenCount += transformBlockCall(data: data.Slice(data_writtenCount, inputBlockSize)
            , destination: destination.Slice(destination_writtenCount));
        data_writtenCount += inputBlockSize;
    }
    return destination_writtenCount;
}


public static (RSAParameters keyPublic, RSAParameters keyPrivate) GenerateKeyPair(int keySize = 2048) {
    RSAParameters keyPriv;
    RSAParameters keyPub;
    using(var rsa = RSA.Create(keySize)) {
        keyPriv = rsa.ExportParameters(true);
        keyPub = rsa.ExportParameters(false);
    }
    return (keyPub, keyPriv);
}

#endregion Methods_Helper


}


public static class Program
{

static void Main() {

    var (keyPublic, keyPrivate) = RsaService.GenerateKeyPair();

    var encryptor = RsaService.Create(keyPublic);
    var decryptor = RsaService.Create(keyPrivate);
    string originalText = "";
    for(int i = 0; i < 1000; i++) {
        originalText += "ABC123456789";
    }
    byte[] inputData = Encoding.UTF8.GetBytes(originalText); // data random for test
    System.Console.WriteLine("inputData.Length: {0}", inputData.Length);

    var encryptedData = encryptor.Encrypt(inputData);

    System.Console.WriteLine("encryptedData.Length: {0}", encryptedData.Length);


    byte[] decryptedData = decryptor.Decrypt(encryptedData);
    string decryptedText = Encoding.UTF8.GetString(decryptedData);

    System.Console.WriteLine("status: {0}", decryptedText == originalText);

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