簡體   English   中英

努力在Java中使用C# RSA

[英]Struggling to use C# RSA in Java

我正在嘗試復制現有的 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));
    }
}

誰能告訴我我做錯了什么?

由於在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 編碼主體(即沒有 header,沒有頁腳和換行符)。

另請注意,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 object 時除了算法之外還指定了填充,則會拋出此異常,例如,如果在getInstance()中傳遞RSA/ECB/PKCS1Padding而不是RSA . 相反,在實例化Cipher object 時應指定填充,例如應傳遞RSA/ECB/PKCS1Padding而不是RSA 如果此處僅指定算法,則應避免使用依賴於提供者的默認值進行填充。 請注意, PKCS1Padding表示 PKCS#1 v1.5 填充,並不對應於 C# 代碼中使用的 OAEP。

暫無
暫無

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

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