简体   繁体   English

努力在Java中使用C# RSA

[英]Struggling to use C# RSA in Java

I'm trying to replicate an existing C# application, part of which encrypts some data using a key.我正在尝试复制现有的 C# 应用程序,其中一部分使用密钥加密一些数据。

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我已经在 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));
    }
}

Can anyone advise what I've done wrong, please?谁能告诉我我做错了什么?

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, ie the decryption is to be performed in Java eg with:由于在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));

Note that X509EncodedKeySpec() expects a DER encoded X.509/SPKI key, ie PEM_KEY must not contain the PEM encoded key, but only the Base64 encoded body (ie without header, without footer and without line breaks).请注意, X509EncodedKeySpec()需要 DER 编码的 X.509/SPKI 密钥,即PEM_KEY不得包含 PEM 编码密钥,而只能包含 Base64 编码主体(即没有 header,没有页脚和换行符)。

Note also that OAEP is a probabilistic padding, ie the ciphertext is different for each encryption even for the same input data.另请注意,OAEP 是一种概率填充,即即使对于相同的输入数据,每次加密的密文也不同。 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:可以使用以下 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));

This code decrypts the ciphertext of the C# code as well as the ciphertext of the Java code.此代码解密C#代码的密文以及Java代码的密文。


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.关于您评论中提到的 Java 代码中的密钥导入:如上所述, PEM_KEY仅包含 PEM 密钥的 Base64 编码主体,没有换行符。 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, eg if RSA/ECB/PKCS1Padding is passed instead of RSA in getInstance() .关于异常NoSuchAlgorithmException:RSA/ECB/PKCS1Padding KeyFactory 不可用:如果在创建KeyFactory object 时除了算法之外还指定了填充,则会抛出此异常,例如,如果在getInstance()中传递RSA/ECB/PKCS1Padding而不是RSA . In contrast, padding should be specified when instantiating the Cipher object, eg RSA/ECB/PKCS1Padding should be passed instead of RSA .相反,在实例化Cipher object 时应指定填充,例如应传递RSA/ECB/PKCS1Padding而不是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.请注意, PKCS1Padding表示 PKCS#1 v1.5 填充,并不对应于 C# 代码中使用的 OAEP。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM