如何在C#中使用RSA加密大型文件

45

我对加密不是很了解。我需要实现非对称加密算法,我认为它使用私钥/公钥。我开始使用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加密文件?


17
私有/公共算法不适用于加密大数据。在实践中,它们被用于在两个参与方之间交换一个私有秘钥,该秘钥将被用于对大数据进行对称加密/解密。 - Darin Dimitrov
7个回答

65

RSA只能加密比密钥长度更短的数据块,因此通常需要执行以下步骤:

  1. 生成一个正确长度的AES(或类似密码)随机密钥。
  2. 使用该密钥使用AES或类似密码对数据进行加密。
  3. 使用RSA密钥加密随机密钥。

然后将步骤2和3的输出都发布出去。

要解密,请执行以下步骤:

  1. 使用RSA密钥解密AES密钥。
  2. 使用AES密钥解密数据。

如果您这样做,那么只有拥有您的RSA密钥对中的私钥的人才能解密数据。 - Joe Kuemerle

37

正如其他答案中提到的那样,非对称加密仅适用于加密小于其密钥大小的数据。

当我需要在两个系统之间传输大量加密数据时,我实现的一种选项是拥有一个RSA密钥对,其公钥为发送方和接收方所知。然后,当需要发送数据时,接收方生成一个新的RSA密钥对,用公共公钥加密该密钥对的公钥,并将加密的公钥发送给发送方。发送方使用其私钥(接收方不需要知道此私钥,就像发送方不需要知道接收方生成的私钥一样)解密接收方的公钥,生成对称加密密钥,使用该对称密钥加密数据,然后使用从接收方接收的公钥加密对称密钥。然后发送加密的对称密钥和加密的数据到接收方,接收方使用其生成的私钥来解密对称密钥,然后解密数据。

您可以使用RSACryptoServiceProvider.ToXMLString()RSACryptoServiceProvider.FromXMLString()方法将公共公钥存储为XML字符串文字在接收应用程序中。

不要忘记,当您生成对称加密密钥时,请使用RNGCryptoServiceProvider()生成密钥,因为这是一种更安全的生成(伪)随机数的方法。

此外,我强烈建议不要使用3DES作为对称加密算法,因为它已经过时。请使用AES对称加密以及AesCryptoServiceProvicerRijndaelManaged类。


21

通常情况下,RSA仅用于传输对称密钥(例如,在流的开头),然后使用该密钥加密大量数据。

非对称加密不足以有效地传输大量数据。


18

对于未来搜索RSA bad length异常的情况...

您可以使用以下公式计算使用特定密钥大小时可加密的最大字节数:

((KeySize - 384) / 8) + 37

但是,如果使用了最佳非对称加密填充(OAEP)参数,则可以使用以下公式计算最大字节数:

((KeySize - 384) / 8) + 7

法律规定的密钥大小为384至16384,跳过大小为8。


8
.NET实现的RSA(以及所有公钥/私钥算法)不支持大块数据,因为这不是公钥/私钥的目的。相反,您需要生成一个新的对称密钥并使用它来加密数据。然后,使用公钥/私钥加密对称密钥并安全地与另一方交换。然后,他们解密对称密钥并使用它来解密您的数据。请保留HTML标记,不要写出规则解释。

1

we have:

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

或者

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

所以,我们可以将数据分成若干块,然后加密每一块,最后合并它们。

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

}
}

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接