努力在 Java 中使用 C# RSA

发布于 2025-01-11 02:19:41 字数 2919 浏览 0 评论 0原文

我正在尝试复制现有的 C# 应用程序,其中一部分使用密钥加密一些数据。

简单的例子:

using System;
using System.Security.Cryptography;
using System.Text;

public class Program {
  private static string xmlKey = "<RSAKeyValue><Modulus>{REDACTED MODULUS}</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";

  public static void Main() {
    RSACryptoServiceProvider cipher = new RSACryptoServiceProvider();
    cipher.FromXmlString(xmlKey);

    Console.WriteLine("KeyExchangeAlgorithm: " + cipher.KeyExchangeAlgorithm);

    byte[] input = Encoding.UTF8.GetBytes("test");
    byte[] output = cipher.Encrypt(input, true);
    Console.WriteLine("Output: " + Convert.ToBase64String(output));
  }
}

哪个输出:

KeyExchangeAlgorithm: RSA-PKCS1-KeyEx
Output: {THE ENCRYPTED OUTPUT}

我用下面的代码在Java中复制了这个,但是当它运行正常时,下游系统无法解密数据,所以我做错了什么

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;

public class Program {
    // I tried "RSA/ECB/PKCS1Padding" but got "java.security.NoSuchAlgorithmException: RSA/ECB/PKCS1Padding KeyFactory not available at java.base/java.security.KeyFactory.<init>(KeyFactory.java:138)"
    private static final String ALGORITHM = "RSA";

    private static final String MODULUS = "{REDACTED MODULUS}";
    private static final String EXPONENT = "AQAB";

    // Converted from XML using
    // https://superdry.apphb.com/tools/online-rsa-key-converter
    private static final String PEM_KEY = "{REDACTED PEM KEY}";

    public static void main(final String[] args) throws Exception {
        final Cipher cipher = Cipher.getInstance(ALGORITHM);
        final PublicKey key = KeyFactory.getInstance(ALGORITHM).generatePublic(getX509Key());
        cipher.init(Cipher.ENCRYPT_MODE, key);

        System.out.println("Algorithm: " + cipher.getAlgorithm());

        final byte[] input = "test".getBytes(StandardCharsets.UTF_8);
        final byte[] output = cipher.doFinal(input);
        System.out.println("Output: " + Base64.getEncoder().encodeToString(output));
    }

    private static KeySpec getRSAKey() throws Exception {
        return new RSAPublicKeySpec(base64ToInt(MODULUS), base64ToInt(EXPONENT));
    }

    private static BigInteger base64ToInt(final String str) {
        return new BigInteger(1, Base64.getDecoder().decode(str.getBytes()));
    }

    private static KeySpec getX509Key() throws Exception {
        return new X509EncodedKeySpec(Base64.getDecoder().decode(PEM_KEY));
    }
}

任何人都可以告诉我做错了什么,请?

I'm trying to replicate an existing C# application, part of which encrypts some data using a key.

Simple example:

using System;
using System.Security.Cryptography;
using System.Text;

public class Program {
  private static string xmlKey = "<RSAKeyValue><Modulus>{REDACTED MODULUS}</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";

  public static void Main() {
    RSACryptoServiceProvider cipher = new RSACryptoServiceProvider();
    cipher.FromXmlString(xmlKey);

    Console.WriteLine("KeyExchangeAlgorithm: " + cipher.KeyExchangeAlgorithm);

    byte[] input = Encoding.UTF8.GetBytes("test");
    byte[] output = cipher.Encrypt(input, true);
    Console.WriteLine("Output: " + Convert.ToBase64String(output));
  }
}

Which outputs:

KeyExchangeAlgorithm: RSA-PKCS1-KeyEx
Output: {THE ENCRYPTED OUTPUT}

I've replicated this in Java with the following, but while it runs ok, the downstream system can't decrypt the data, so I've done something wrong

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;

public class Program {
    // I tried "RSA/ECB/PKCS1Padding" but got "java.security.NoSuchAlgorithmException: RSA/ECB/PKCS1Padding KeyFactory not available at java.base/java.security.KeyFactory.<init>(KeyFactory.java:138)"
    private static final String ALGORITHM = "RSA";

    private static final String MODULUS = "{REDACTED MODULUS}";
    private static final String EXPONENT = "AQAB";

    // Converted from XML using
    // https://superdry.apphb.com/tools/online-rsa-key-converter
    private static final String PEM_KEY = "{REDACTED PEM KEY}";

    public static void main(final String[] args) throws Exception {
        final Cipher cipher = Cipher.getInstance(ALGORITHM);
        final PublicKey key = KeyFactory.getInstance(ALGORITHM).generatePublic(getX509Key());
        cipher.init(Cipher.ENCRYPT_MODE, key);

        System.out.println("Algorithm: " + cipher.getAlgorithm());

        final byte[] input = "test".getBytes(StandardCharsets.UTF_8);
        final byte[] output = cipher.doFinal(input);
        System.out.println("Output: " + Base64.getEncoder().encodeToString(output));
    }

    private static KeySpec getRSAKey() throws Exception {
        return new RSAPublicKeySpec(base64ToInt(MODULUS), base64ToInt(EXPONENT));
    }

    private static BigInteger base64ToInt(final String str) {
        return new BigInteger(1, Base64.getDecoder().decode(str.getBytes()));
    }

    private static KeySpec getX509Key() throws Exception {
        return new X509EncodedKeySpec(Base64.getDecoder().decode(PEM_KEY));
    }
}

Can anyone advise what I've done wrong, please?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

拥有 2025-01-18 02:19:41

由于在 RSACryptoServiceProvider#Encrypt() 中,第二个参数中传递了 true,因此 OAEP 用作 OAEP 和 MGF1 摘要的填充和 SHA-1,即解密将在 Java 中执行,例如:

import java.nio.charset.StandardCharsets;
import java.security.spec.MGF1ParameterSpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
...
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-1", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, key, oaepParameterSpec);
byte[] ciphertext = cipher.doFinal("test".getBytes(StandardCharsets.UTF_8));
System.out.println(Base64.getEncoder().encodeToString(ciphertext));

请注意,X509EncodedKeySpec() 需要 DER 编码的 X.509/SPKI 密钥,即PEM_KEY 不得包含 PEM 编码的密钥,而只能包含 Base64 编码的正文(即没有页眉、页脚和换行符)。

另请注意,OAEP 是概率填充,即即使对于相同的输入数据,每次加密的密文也不同。因此,即使输入数据相同,两个代码的密文也不会匹配,这不是故障。


可以使用以下 C# 代码进行测试:

using System;
using System.Security.Cryptography;
using System.Text;
...
string xmlKeyPriv = "<private key in XML format>";
RSACryptoServiceProvider cipherDec = new RSACryptoServiceProvider();
cipherDec.FromXmlString(xmlKeyPriv);

byte[] ciphertext = Convert.FromBase64String("<Base64 encoded ciphertext>");
byte[] decrypted = cipherDec.Decrypt(ciphertext, true);
Console.WriteLine(Encoding.UTF8.GetString(decrypted));

该代码解密 C# 代码的密文以及 Java 代码的密文。


编辑:
关于评论中提到的Java代码中的密钥导入:如上所述,PEM_KEY仅包含PEM密钥的Base64编码主体,没有换行符。除此之外,密钥导入与您的代码相匹配:

import java.security.KeyFactory;
import java.security.PublicKey;
...
private static String PEM_KEY = "MIIBIjANB...IDAQAB"
...
PublicKey key = KeyFactory.getInstance("RSA").generatePublic(getX509Key());

关于异常 NoSuchAlgorithmException:RSA/ECB/PKCS1Padding KeyFactory 不可用:如果在 时除了算法之外还指定了填充,则会抛出此异常创建 KeyFactory 对象,例如,如果传递的是 RSA/ECB/PKCS1Padding 而不是 RSA getInstance()。相反,在实例化Cipher对象时应指定填充,例如应传递RSA/ECB/PKCS1Padding而不是RSA。如果此处仅指定算法,则将使用依赖于提供者的默认值进行填充,这是应该避免的。请注意,PKCS1Padding 表示 PKCS#1 v1.5 填充,与 C# 代码中使用的 OAEP 不对应。

Since in RSACryptoServiceProvider#Encrypt() a true is passed in the 2nd parameter, OAEP is used as padding and SHA-1 for the OAEP and the MGF1 digest, i.e. the decryption is to be performed in Java e.g. with:

import java.nio.charset.StandardCharsets;
import java.security.spec.MGF1ParameterSpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
...
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-1", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, key, oaepParameterSpec);
byte[] ciphertext = cipher.doFinal("test".getBytes(StandardCharsets.UTF_8));
System.out.println(Base64.getEncoder().encodeToString(ciphertext));

Note that X509EncodedKeySpec() expects a DER encoded X.509/SPKI key, i.e. PEM_KEY must not contain the PEM encoded key, but only the Base64 encoded body (i.e. without header, without footer and without line breaks).

Note also that OAEP is a probabilistic padding, i.e. the ciphertext is different for each encryption even for the same input data. For this reason, the ciphertexts of both codes will not match, even with identical input data, which is not a malfunction.


A test is possible with the following C# code:

using System;
using System.Security.Cryptography;
using System.Text;
...
string xmlKeyPriv = "<private key in XML format>";
RSACryptoServiceProvider cipherDec = new RSACryptoServiceProvider();
cipherDec.FromXmlString(xmlKeyPriv);

byte[] ciphertext = Convert.FromBase64String("<Base64 encoded ciphertext>");
byte[] decrypted = cipherDec.Decrypt(ciphertext, true);
Console.WriteLine(Encoding.UTF8.GetString(decrypted));

This code decrypts the ciphertext of the C# code as well as the ciphertext of the Java code.


EDIT:
Regarding the key import in the Java code mentioned in your comment: As explained above, PEM_KEY contains only the Base64 encoded body of the PEM key without line breaks. Apart from that, the key import matches your code:

import java.security.KeyFactory;
import java.security.PublicKey;
...
private static String PEM_KEY = "MIIBIjANB...IDAQAB"
...
PublicKey key = KeyFactory.getInstance("RSA").generatePublic(getX509Key());

Regarding the exception NoSuchAlgorithmException: RSA/ECB/PKCS1Padding KeyFactory not available: This is thrown if the padding is specified in addition to the algorithm when the KeyFactory object is created, e.g. if RSA/ECB/PKCS1Padding is passed instead of RSA in getInstance(). In contrast, padding should be specified when instantiating the Cipher object, e.g. RSA/ECB/PKCS1Padding should be passed instead of RSA. If only the algorithm is specified here, a provider-dependent default value would be used for the padding, which should be avoided. Note that PKCS1Padding denotes PKCS#1 v1.5 padding and does not correspond to OAEP used in the C# code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文