【问题标题】:Unable to decrypt a string in C# which is encrypted with RSA public key in Node.js无法解密 C# 中使用 Node.js 中的 RSA 公钥加密的字符串
【发布时间】:2021-01-23 10:15:26
【问题描述】:

我已经用 c# 创建了 xml 键:

RSACryptoServiceProvider RSA = new RSACryptoServiceProvider (2048);
string str = RSA.ToXmlString (true);
System.IO.File.WriteAllText (@"c:\path\to\keypair.xml", str);

然后使用this online tool,我将 XML 密钥转换为 pem 私钥。 然后使用开放的 SSL,我从私钥中提取了公钥:

openssl rsa -in ./private.pem -pubout -out public.pem 

使用 public.pem 我正在加密 Node 中的秘密字符串:

const encryptWithRsa = (secretStringToEncrypt) => {
  const publicKeyPath = path.join(appRoot, `${process.env.PUBLIC_KEY_PATH}`);
  const publicKeyString = fs.readFileSync(publicKeyPath, 'utf8');
  const buffer = Buffer.from(secretStringToEncrypt, 'utf8');
  const encrypted = crypto.publicEncrypt({
    key: publicKeyString,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha256",
  }, buffer);
  const encryptedString = encrypted.toString('base64');
  console.log("encryptWithRsa -> encryptedString", encryptedString);
  return encryptedString;
};

然后我尝试在 c# 中解密字符串:

public string RSADecrypt (string encryptedStringFromNode) {
            string keyPair = System.IO.File.ReadAllText (@"c:\path\to\keypair.xml");
            using var csp = new RSACryptoServiceProvider ();
            ImportKey (csp, keyPair);
            var decrypted = csp.Decrypt (Convert.FromBase64String (encryptedStringFromNode), true);
            return Encoding.UTF8.GetString (
                decrypted
            );
        }

        public void ImportKey (RSA rsa, string xmlKey) {
            var parameters = new RSAParameters ();

            var xmlDoc = new XmlDocument ();
            xmlDoc.LoadXml (xmlKey);

            if (xmlDoc.DocumentElement.Name.Equals ("RSAKeyValue")) {
                foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes) {
                    var value = ToByteArray (node.InnerText);
                    switch (node.Name) {
                        case nameof (parameters.Modulus):
                            parameters.Modulus = value;
                            break;
                        case nameof (parameters.Exponent):
                            parameters.Exponent = value;
                            break;
                        case nameof (parameters.P):
                            parameters.P = value;
                            break;
                        case nameof (parameters.Q):
                            parameters.Q = value;
                            break;
                        case nameof (parameters.DP):
                            parameters.DP = value;
                            break;
                        case nameof (parameters.DQ):
                            parameters.DQ = value;
                            break;
                        case nameof (parameters.InverseQ):
                            parameters.InverseQ = value;
                            break;
                        case nameof (parameters.D):
                            parameters.D = value;
                            break;
                    }
                }
            } else {
                throw new Exception ("");
            }
            rsa.ImportParameters (parameters);
        }

        public byte[] ToByteArray (string input) {
            return string.IsNullOrEmpty (input) ? null : Convert.FromBase64String (input);
        }

但是当我尝试解密由 Node.js 加密的字符串时,出现以下错误:

    Exception has occurred: CLR/Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException
    An exception of type 'Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException' occurred in 
System.Security.Cryptography.Csp.dll but was not handled in user code: 'The parameter is incorrect.'

但是,如果我尝试在 this online tool 上使用相同的公钥进行加密,然后使用我的 C# 代码进行解密,它会起作用并且我会得到原始未加密的字符串。

我在 Node.js 中用于 RSA 加密的 Padding 有什么问题吗?

【问题讨论】:

  • 这是一个非常痛苦的 XML 处理。为什么不在 ImportKey 中使用 RSA.FromXMLString()
  • RSACryptoServiceProvider 除 PKCS#1v1.5 外仅支持 SHA1 的 OAEP。在 NodeJS 代码中使用了带有 SHA256 的 OAEP,因此使用 C# 代码解密失败。为了解决这个问题,可以在 NodeJS 代码中使用 SHA1。或者,可以在 C# 代码中应用支持 SHA256 的 RSA 实现。这里RSACng 是可能的,前提是您正在使用的.NET 版本中提供了它。由于双方也支持 PKCS#1 v1.5,这是另一种选择(见 Michael Fehr 的回答)。
  • 哪行代码导致异常?
  • @Topaco - 使用 RSA_PKCS1_OAEP_PADDING 和 sha1 解决了这个问题。

标签: javascript c# node.js encryption rsa


【解决方案1】:

首先 - 很抱歉懒得分析您的代码以找出失败的真正原因。大多数情况下,平台之间的填充不匹配,定义参数始终是一种很好的编程技术 即使它们被称为“标准”或“默认”,因为它们将来可能会发生变化。

在我的示例中,我使用 PKCS1 填充进行 RSA 加密,这在两个平台上都可用。

第二:我想使用在线编译器进行演示,所以我使用的是固定/字符串公共和私有 RSA 密钥。请注意,我使用的是 *UNSECURE RSA 密钥对,密钥长度为 512 位(只是为了缩短密钥字符串),是的 - 密钥是示例密钥。

第三:这两种解决方案都仅用于教育目的,因为它们没有任何异常处理。

第四:两种解决方案都提供“全轮”意味着都进行加密和解密,C#程序进行 使用来自 Node.JS-Solution 的加密字符串进行第二次解密。

这是 Node.JS 程序的输出(在此处运行:https://repl.it/@javacrypto/RSAEncryptionNodeJstoC):

enc: QPCE4WtNPLFGjWvwpN/JTITdr2k9IhGrsohuW0yPue4dQ2Cv63i+LlohmjsSUcnaiB/zbeItGDIx3s11ayBIUA==
dec: The quick brown fox jumps over the lazy dog

如果您运行 C# 版本,您将获得此输出(在此处运行:https://repl.it/@javacrypto/RSADecryptionCfromNodeJs-1

encryptedData: jnAyBvjvjG/NXc/HUmfrTks3SWgeYz3HEksPvk9D9eH/DF0PK6nZu36wTAeZ/fHr3v8dWYwXtT8nXZgvokJJYg==
decryptedData: The quick brown fox jumps over the lazy dog
original Data: VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==
*** enc string from NodeJs ***
decryptedData: The quick brown fox jumps over the lazy dog

Node.JS 代码:

const crypto = require('crypto')

// SECURITY WARNING: these sample keys are UNSECURE 512 bit long RSA keys
const publicKey = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJn4LYaoLyuT+pD9NKI8wOvPrRjMIxGn
HbqIxUrpGsyj162fEOV4836oCZg0N8HFnt4Vivdjt8/7ZgLjeOygNGUCAwEAAQ==
-----END PUBLIC KEY-----`;

const privateKey = `-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAmfgthqgvK5P6kP00
ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN4
7KA0ZQIDAQABAkEAmCpGy/rxS08e5iXn26LRQvvm5UfyLKMNZWmAGk2QF8cRGFB7
dds/SI9wGTC9xyOoF4N2kWzYdLx+dYbR9lqwbQIhAPFQkWqNIWZiL+YFRhS6jg/+
5+k0CpXpAPelNUFc4pijAiEAo1bchWA5ddDd59FED37QcrakoTXNoxRspFtsLDKE
p1cCIQC6AXRVQTvBg2WQU/hU+geO5Nk1tFuEefm46atHGqW/KQIgXoGggC9Hr8pU
yo9DIPAP7X+Ny5TU0Vm87m/TK9Ni+2sCIGKjhxD/HYAr8+UPJ4aUIHayn3ZZo5DQ
BcShe496ZtPS
-----END PRIVATE KEY-----`;

function encrypt(toEncrypt, relativeOrAbsolutePathToPublicKey) {
  //const absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey)
  //const publicKey = fs.readFileSync(absolutePath, 'utf8')
  const buffer = Buffer.from(toEncrypt, 'utf8')
  var constants = require("constants");
  const encrypted = crypto.publicEncrypt(
  {"key":publicKey, padding:constants.RSA_PKCS1_PADDING}, buffer)
  return encrypted.toString('base64')
}

function decrypt(toDecrypt, relativeOrAbsolutePathtoPrivateKey) {
  //const absolutePath = path.resolve(relativeOrAbsolutePathtoPrivateKey)
  //const privateKey = fs.readFileSync(absolutePath, 'utf8')
  const buffer = Buffer.from(toDecrypt, 'base64')
  var constants = require("constants");
  const decrypted = crypto.privateDecrypt(
    {
      key: privateKey.toString(),
      passphrase: '',
      padding:constants.RSA_PKCS1_PADDING,
    },
    buffer,
  )
  return decrypted.toString('utf8')
}

const enc = encrypt('The quick brown fox jumps over the lazy dog')
console.log('enc:', enc)
const dec = decrypt(enc)
console.log('dec:', dec)

C#代码:

using System;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Generic;

class RSACSPSample {
    static void Main() {

        try {
            ASCIIEncoding ByteConverter = new ASCIIEncoding();
            string dataString = "The quick brown fox jumps over the lazy dog";

            // Create byte arrays to hold original, encrypted, and decrypted data.
            byte[] originalData = ByteConverter.GetBytes(dataString);
            byte[] encryptedData;
            byte[] decryptedData;

      // SECURITY WARNING: these sample keys are UNSECURE 512 bit long RSA keys
            // get private and public key
            var publicKey = "<RSAKeyValue><Modulus>mfgthqgvK5P6kP00ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN47KA0ZQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            var privateKey = "<RSAKeyValue><Modulus>mfgthqgvK5P6kP00ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN47KA0ZQ==</Modulus><Exponent>AQAB</Exponent><P>8VCRao0hZmIv5gVGFLqOD/7n6TQKlekA96U1QVzimKM=</P><Q>o1bchWA5ddDd59FED37QcrakoTXNoxRspFtsLDKEp1c=</Q><DP>ugF0VUE7wYNlkFP4VPoHjuTZNbRbhHn5uOmrRxqlvyk=</DP><DQ>XoGggC9Hr8pUyo9DIPAP7X+Ny5TU0Vm87m/TK9Ni+2s=</DQ><InverseQ>YqOHEP8dgCvz5Q8nhpQgdrKfdlmjkNAFxKF7j3pm09I=</InverseQ><D>mCpGy/rxS08e5iXn26LRQvvm5UfyLKMNZWmAGk2QF8cRGFB7dds/SI9wGTC9xyOoF4N2kWzYdLx+dYbR9lqwbQ==</D></RSAKeyValue>";

            RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider(512); // SECURITY WARNING: 512 is UNSECURE
            RSAalg.PersistKeyInCsp = false;
            // encrypt with xml-public key
      RSAalg.FromXmlString(publicKey);
            encryptedData = RSAalg.Encrypt(originalData, false);
            string encryptedDataBase64 = Convert.ToBase64String(encryptedData);
            Console.WriteLine("encryptedData: " + encryptedDataBase64);

            // decrypt with xml-private key
            RSAalg.FromXmlString(privateKey);
            decryptedData = RSAalg.Decrypt(encryptedData, false);
            Console.WriteLine("decryptedData: " + ByteArrayToString(decryptedData));
            Console.WriteLine("original Data: " + Convert.ToBase64String(originalData));

      Console.WriteLine("*** enc string from NodeJs ***");

      string b64string = "QPCE4WtNPLFGjWvwpN/JTITdr2k9IhGrsohuW0yPue4dQ2Cv63i+LlohmjsSUcnaiB/zbeItGDIx3s11ayBIUA==";
      encryptedData = Convert.FromBase64String(b64string);
      decryptedData = RSAalg.Decrypt(encryptedData, false);
            Console.WriteLine("decryptedData: " + ByteArrayToString(decryptedData));
        }
        catch(ArgumentNullException) {
            Console.WriteLine("error in data en-/decryption");
        }
    }
    
  private static string ByteArrayToString(byte[] arr)
  {
    System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
    return enc.GetString(arr);
  }
}

【讨论】:

  • RSAalg.Decrypt(encryptedData, false);您将 OEAP 填充设置为 false。如果设置为true,将无法解密nodejs加密字符串。
  • 你明白了 - 填充效果不同!正如我的回答中所解释的,我正在使用 Javascript- 上的 PKCS1-padding 在 C# 端。如果您想要其他填充,请遵循@Topaco 在他的评论中给出的建议。
猜你喜欢
  • 2013-06-08
  • 1970-01-01
  • 2014-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-08
  • 2011-11-13
  • 1970-01-01
相关资源
最近更新 更多