簡體   English   中英

用 C# 中的 JS 問題解密 RSA 公鑰加密數據

[英]Issue decrypting RSA public-key encrypted data by JS in C#

我收到錯誤在 C# 中解密 RSA 加密字符串時The data to be decrypted exceeds the maximum for this modulus of 256 bytes

我正在努力實現的目標:

  1. 在 C# (RSA) 中生成公鑰/私鑰對
  2. 將私鑰保存在會話/臨時存儲中以在解密期間使用
  3. 向客戶端/JS 發送/返回公鑰以用於加密
  4. 使用公鑰在 JS 中加密字符串(最多 20 個字符)並發送到服務器
  5. 使用步驟 2 中保存的私鑰解密服務器中的加密字符串

什么工作:

  1. 生成公鑰/私鑰對
  2. 使用庫jsencrypt在 JS 中加密

到目前為止編寫的代碼:

C#

    ///Source: https://stackoverflow.com/questions/17128038/c-sharp-rsa-encryption-decryption-with-transmission
    public static void GenerateKeys()
    {
        //CSP with a new 2048 bit rsa key pair
        var csp = new RSACryptoServiceProvider(2048);

        //Private key
        var privKey = csp.ExportParameters(true);

        //Public key
        var pubKey = csp.ExportParameters(false);

        string privKeyString = string.Empty;

        var sw = new System.IO.StringWriter();
        var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
        xs.Serialize(sw, privKey);
        privKeyString = sw.ToString();

        //This will give public key in the following format which is required by the JS library
        //-----BEGIN PUBLIC KEY-----
        //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        //-----END PUBLIC KEY-----
        string publicKeyBase64 = ConvertPublicKeyForJS(pubKey);

        //Will be saved in sesssion for later use during decryption
        string privateKeyBase64 = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(privKeyString));

        //Save in session/temp location
        //Return publicKeyBase64 to JS
    }

    /// <summary>
    /// Source: https://stackoverflow.com/questions/28406888/c-sharp-rsa-public-key-output-not-correct/28407693#28407693
    /// </summary>
    /// <param name="publicKey"></param>
    /// <returns></returns>
    public static string ConvertPublicKeyForJS(RSAParameters publicKey)
    {
        string output = string.Empty;

        using (var stream = new MemoryStream())
        {
            var writer = new BinaryWriter(stream);
            writer.Write((byte)0x30); // SEQUENCE
            using (var innerStream = new MemoryStream())
            {
                var innerWriter = new BinaryWriter(innerStream);
                innerWriter.Write((byte)0x30); // SEQUENCE
                EncodeLength(innerWriter, 13);
                innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER
                var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
                EncodeLength(innerWriter, rsaEncryptionOid.Length);
                innerWriter.Write(rsaEncryptionOid);
                innerWriter.Write((byte)0x05); // NULL
                EncodeLength(innerWriter, 0);
                innerWriter.Write((byte)0x03); // BIT STRING
                using (var bitStringStream = new MemoryStream())
                {
                    var bitStringWriter = new BinaryWriter(bitStringStream);
                    bitStringWriter.Write((byte)0x00); // # of unused bits
                    bitStringWriter.Write((byte)0x30); // SEQUENCE
                    using (var paramsStream = new MemoryStream())
                    {
                        var paramsWriter = new BinaryWriter(paramsStream);
                        EncodeIntegerBigEndian(paramsWriter, publicKey.Modulus); // Modulus
                        EncodeIntegerBigEndian(paramsWriter, publicKey.Exponent); // Exponent
                        var paramsLength = (int)paramsStream.Length;
                        EncodeLength(bitStringWriter, paramsLength);
                        bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength);
                    }
                    var bitStringLength = (int)bitStringStream.Length;
                    EncodeLength(innerWriter, bitStringLength);
                    innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength);
                }
                var length = (int)innerStream.Length;
                EncodeLength(writer, length);
                writer.Write(innerStream.GetBuffer(), 0, length);
            }

            var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length);

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("-----BEGIN PUBLIC KEY-----");
            sb.AppendLine(base64);
            sb.AppendLine("-----END PUBLIC KEY-----");

            output = sb.ToString();
        }

        return output;
    }

    private static void EncodeLength(BinaryWriter stream, int length)
    {
        if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
        if (length < 0x80)
        {
            // Short form
            stream.Write((byte)length);
        }
        else
        {
            // Long form
            var temp = length;
            var bytesRequired = 0;
            while (temp > 0)
            {
                temp >>= 8;
                bytesRequired++;
            }
            stream.Write((byte)(bytesRequired | 0x80));
            for (var i = bytesRequired - 1; i >= 0; i--)
            {
                stream.Write((byte)(length >> (8 * i) & 0xff));
            }
        }
    }

    private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
    {
        stream.Write((byte)0x02); // INTEGER
        var prefixZeros = 0;
        for (var i = 0; i < value.Length; i++)
        {
            if (value[i] != 0) break;
            prefixZeros++;
        }
        if (value.Length - prefixZeros == 0)
        {
            EncodeLength(stream, 1);
            stream.Write((byte)0);
        }
        else
        {
            if (forceUnsigned && value[prefixZeros] > 0x7f)
            {
                // Add a prefix zero to force unsigned if the MSB is 1
                EncodeLength(stream, value.Length - prefixZeros + 1);
                stream.Write((byte)0);
            }
            else
            {
                EncodeLength(stream, value.Length - prefixZeros);
            }
            for (var i = prefixZeros; i < value.Length; i++)
            {
                stream.Write(value[i]);
            }
        }
    }

    public static string DecryptValue(string cypherText)
    {
        string plainTextData = string.Empty;

        string privKeyBase64 = ""; //get value from session/temp storage

        string privKeyString = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(privKeyBase64));

        var sr = new System.IO.StringReader(privKeyString);
        var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
        var privKey = (RSAParameters)xs.Deserialize(sr);

        var csp = new RSACryptoServiceProvider();
        csp.ImportParameters(privKey);

        var bytesCypherText = Convert.FromBase64String(cypherText);

        //Problematic line
        var bytesPlainTextData = csp.Decrypt(bytesCypherText, false);

        plainTextData = System.Text.Encoding.Unicode.GetString(bytesPlainTextData);

        return plainTextData;
    }

JS

   function encrypt(msg, publicKey) {
        var jsEncrypt = new JSEncrypt();
        jsEncrypt.setPublicKey(publicKey);
        var encrypted = jsEncrypt.encrypt(msg);

        var base64result = btoa(encrypted);

        return base64result;            
    }

更新/變化:

如果我將密鑰大小The data to be decrypted exceeds the maximum for this modulus of 128 bytes. 1024 而不是 2048,我得到的錯誤是The data to be decrypted exceeds the maximum for this modulus of 128 bytes. . 根據我在調試器中看到的內容,加密字符串的總字節數為 172。

更新 | 解決方案:

  1. 刪除 JS var base64result = btoa(encrypted);中的這一行 . 直接返回加密值即可。
  2. 在 C# 解密方法中,更改這一行var bytesPlainTextData = csp.Decrypt(bytesCypherText, false); var bytesPlainTextData = csp.Decrypt(bytesCypherText, RSAEncryptionPadding.Pkcs1); .

筆記:

  • 請忽略不正確的方法返回類型,這仍然是一個 POC,很多值都是手動填充的。
  • 我不是安全問題的專家,所以很多代碼都是從不同的來源中挑選出來的。

您正面臨非對稱加密的限制。 對於大塊數據來說非常慢,並且加密字符串的大小受您使用的 RSA 密鑰大小的限制。

RSA 通常用於交換對稱密鑰和處理大部分數據。 如果您必須對大量數據使用非對稱,那么您需要將有效負載分解為較小的負載並在另一側重建。

RSA 只能將數據加密到最大數量的密鑰大小(2048 位 = 256 字節)減去填充/標頭數據(PKCS#1 v1.5 填充為 11 字節)。

如果如您所說,您只發送 20 個字符,那么確實使用斷點檢查,您的解密函數是否確實獲得了足夠小的密碼文本。 如果不是,您需要回溯並檢查發送錯誤內容的位置。

也可能是 JSEncrypt 中的 RSA 標准和 C# 中的 RSA 不同,正如在此 SO 答案中可以看到的那樣

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM