RSA .NET加密Java解密

17

我正在尝试使用RSA算法在.NET中加密字符串,并在Java中解密结果。目前为止,我已经能够做到相反的操作(在Java中加密,在.NET中解密)。 这是我的代码,实际上可以工作(JAVA加密):

byte[] modulusBytes = Base64.decode("2rRVVVFJRbH/wAPDtnwZwu+nxU+AZ6uXxh/sW+AMCBogg7vndZsnRiHoLttYYPqOyOhfgaBOQogrIfrKL4lipK4m52SBzw/FfcM9DsKs/rYR83tBLiIAfgdnVjF27tZID+HJMFTiI30mALjr7+tfp+2lIACXA1RIKTk7S9pDmX8=");
byte[] exponentBytes = Base64.decode("AQAB");
BigInteger modulus = new BigInteger(1, modulusBytes );
BigInteger exponent = new BigInteger(1, exponentBytes);

RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(rsaPubKey);

Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);

byte[] plainBytes = new String("big kitty dancing").getBytes("UTF-8");
byte[] cipherData = cipher.doFinal( plainBytes );
String encryptedString = Base64.encode(cipherData);
return encryptedString;

同时 (.NET 解密)

const int PROVIDER_RSA_FULL = 1;
const string CONTAINER_NAME = "Tracker";

CspParameters cspParams;
cspParams = new CspParameters(PROVIDER_RSA_FULL);
cspParams.KeyContainerName = CONTAINER_NAME;
RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider(cspParams);
rsa1.FromXmlString("<RSAKeyValue><Modulus>2rRVVVFJRbH/wAPDtnwZwu+nxU+AZ6uXxh/sW+AMCBogg7vndZsnRiHoLttYYPqOyOhfgaBOQogrIfrKL4lipK4m52SBzw/FfcM9DsKs/rYR83tBLiIAfgdnVjF27tZID+HJMFTiI30mALjr7+tfp+2lIACXA1RIKTk7S9pDmX8=</Modulus><Exponent>AQAB</Exponent><P>+lXMCEwIN/7+eMpBrq87kQppxu3jJBTwztGTfXNaPUTx+A6uqRwug5oHBbSpYXKNDNCBzVm/0VxB3bo4FJx+ZQ==</P><Q>yasOGaJaE9xlF9T2xRuKeG9ZxCiyjhYaYB/mbtL+SIbtkRLi/AxaU4g2Il/UxhxhSXArKxIzV28zktispPJx1Q==</Q><DP>ueRgQIEFUV+fY979a1RgrVHIPpqEI1URhOMH3Q59oiXCcOumM5njyIHmWQxRAzXnG+7xlKXi1PrnRll0L4oOKQ==</DP><DQ>dfEMNgG1HJhwpxdtmqkYuoakwQvsIRzcIAuIAJh1DoWaupWJGk8/JEstHb1d+t7uJrzrAi2KyT/HscH2diE0YQ==</DQ><InverseQ>YoYF9PF6FiC0YngVeaC/eqt/ea8wMYNN3YO1LuzWpcy2exPRj2U0ZbWMvHXMUb4ea2qmhZGx1QlK4ULAuWKpXQ==</InverseQ><D>g1WAWI4pEK9TA7CA2Yyy/2FzzNiu0uQCuE2TZYRNiomo96KQXpxwqAzZLw+VDXfJMypwDMAVZe/SqzSJnFEtZxjdxaEo3VLcZ1mnbIL0vS7D6iFeYutF9kF231165qGd3k2tgymNMMpY7oYKjS11Y6JqWDU0WE5hjS2X35iG6mE=</D></RSAKeyValue>");

string data2Decrypt = "BaB21vY+RD/jiY3AAsb269fIWTEH38s0xLUfJ7CoVUgaQ6vYzB0tiJ1Ag9HNEdCcuZdGchhqnms8jpsqsHC1iKrz6QCLsgUU7VNWDfQqZYR6Rl/GwR0biK2STnOL+g06f/JUdixHOHOgROify1m8qppYo5plpOVMqYFzEMREMkM=";

byte[] encyrptedBytes = Convert.FromBase64String(data2Decrypt);

byte[] plain = rsa1.Decrypt(encyrptedBytes, false);
string decryptedString = System.Text.Encoding.UTF8.GetString(plain);
Console.WriteLine("SALIDA: " + decryptedString);

现在我想要做相反的事情……但是我遇到了一些错误,比如(密钥的大小应该为128个字节……等等)。我该怎么办呢?

这里我添加了当前不起作用的代码:

.NET

public string Encrypt(string text)
{
    const int PROVIDER_RSA_FULL = 1;
    const string CONTAINER_NAME = "Tracker";

    CspParameters cspParams;
    cspParams = new CspParameters(PROVIDER_RSA_FULL);
    cspParams.KeyContainerName = CONTAINER_NAME;
    RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider(cspParams);
    rsa1.FromXmlString("<RSAKeyValue><Modulus>2rRVVVFJRbH/wAPDtnwZwu+nxU+AZ6uXxh/sW+AMCBogg7vndZsnRiHoLttYYPqOyOhfgaBOQogrIfrKL4lipK4m52SBzw/FfcM9DsKs/rYR83tBLiIAfgdnVjF27tZID+HJMFTiI30mALjr7+tfp+2lIACXA1RIKTk7S9pDmX8=</Modulus><Exponent>AQAB</Exponent><P>92jJJyzFBSx6gL4Y1YpALmc5CNjoE/wETjqb3ci2v0+3rZWvJKmKy1ZEdlXpyuvXVksJ6cMdUpNAkMknUk9pTQ==</P><Q>4kxkABZOXyDLryYGCGY0b8N0FIdu5BTCFDYEdcatxl/f7ZGDS1NgHJpUWxkVXFfHy2Y/GuDOIbpcwlsO739H+w==</Q><DP>5bNFvrdUHF+VRN45VFjNCcgQLeSkY5mBrdfASoNFGA29LM5iE5nNIMfxPCS7sQiRnq6Af6YFHVtVgJchiMvtqQ==</DP><DQ>j+ng1qVY5epnXlWiFIla45C7K6sNfIMvAcdwgq39KWEjeWPGyYqWXtpOtzh2eylf6Bx4GVHKBW0NPJTIJMsfLQ==</DQ><InverseQ>8uu0dfPVDqB2qFM1Vdi8hl+2uZtN7gjT2co1cEWy29HVYBZD0k9KKCf2PbkeuSfpgFpE70wW5Hrp8V7l/SwSOw==</InverseQ><D>MM/c18zroJ2Iqi9s5/asvUBF3pjO3NSEbFjFpP/NT6WdKimvECWPz2xT6NlV0Vc6tQaAAmtn7Bt+HPhfVdrA4/ysYVe3/6TWkPjW+bvAhMWu/ZqISx11/jPYSGD9g3ZXgUiqcQM8UbOjlswoq4fpheEXTB0xdVutDLpO3qgHN6k=</D></RSAKeyValue>");

    System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
    byte[] textBytes = encoding.GetBytes(text);
    byte[] encryptedOutput = rsa1.Encrypt(textBytes, false);
    string outputB64 = Convert.ToBase64String(encryptedOutput);
    Console.WriteLine(outputB64);
    return outputB64;
}

Java

public static String Decrypt(String encodedString) throws IllegalBlockSizeException, UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, BadPaddingException
{
    byte[] modulusBytes = Base64.decode("2rRVVVFJRbH/wAPDtnwZwu+nxU+AZ6uXxh/sW+AMCBogg7vndZsnRiHoLttYYPqOyOhfgaBOQogrIfrKL4lipK4m52SBzw/FfcM9DsKs/rYR83tBLiIAfgdnVjF27tZID+HJMFTiI30mALjr7+tfp+2lIACXA1RIKTk7S9pDmX8=");
    byte[] exponentBytes = Base64.decode("AQAB");
    BigInteger modulus = new BigInteger(1, modulusBytes );
    BigInteger exponent = new BigInteger(1, exponentBytes);

    RSAPrivateKeySpec rsaPrivKey = new RSAPrivateKeySpec(modulus, exponent);
    KeyFactory fact = KeyFactory.getInstance("RSA");
    PrivateKey privKey = fact.generatePrivate(rsaPrivKey);

    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.DECRYPT_MODE, privKey);

    byte[] base64String = Base64.decode(encodedString);
    byte[] plainBytes = new String(base64String).getBytes("UTF-8");
    byte[] cipherData = cipher.doFinal(plainBytes);

    System.out.println(cipherData);
    return cipherData.toString();
}

请提供一些堆栈跟踪。您是否指定了相同的填充方案?我能够在.Net中加密,在Java中解密,反之亦然,没有问题。尽管我使用PKCS5Padding(Java)和PKCS7(.Net),但不会硬编码密钥。 - Petey B
我无法提供任何跟踪信息,因为我得到的错误是不断变化的(就像我说的密钥大小、填充异常,例如:javax.crypto.BadPaddingException: 数据必须以零开头)...我可以发布不起作用的代码,但现在我正在处理它。如果您能请发一下您的解决方法,我将非常感激,因为对我来说重要的是在接下来的几天内解决这个问题... - Reixons
3个回答

10

你的Java解密代码中的最后几行没有意义。这些行是:

byte[] base64String = Base64.decode(encodedString);
byte[] plainBytes = new String(base64String).getBytes("UTF-8");
byte[] cipherData = cipher.doFinal(plainBytes);

System.out.println(cipherData);
return cipherData.toString();

您需要逆转在.NET中加密时使用的步骤顺序。首先,您应该对编码后的字符串进行Base64解码以获取密码字节。您已经做到了这一点,但是您将结果误标为base64String。您可能应该将此结果称为cipherData。其次,您需要解密cipherData以获取明文。第三,您应该使用二元String构造函数创建来自plainbytes的字符串,并将字符集用作第二个参数。以下是代码应该看起来的样子,或者接近它。

byte[] cipherData = Base64.decode(encodedString);
byte[] plainBytes = cipher.doFinal(cipherData);

return new String(plainBytes, "UTF-8");

在Java中,每个对象都有一个toString()方法,但它并不总是做你想要的事情。对于数组,toString()方法只是返回该数组的对象ID表示,类似于JVM中内存地址的等效物。

编辑:

我错过了你在解密代码中也使用了错误的密钥。你正在使用RSA公钥,但你必须改用RSA私钥。


我正在使用私钥,但为了生成私钥,我使用的是与生成公钥相同的模数和指数...这不正确吗? - Reixons
你是对的,我的第一段代码问题在于我同时使用指数来生成私钥,而我应该使用密钥的“D”部分。现在它可以工作了。谢谢大家!! - Reixons
我在做非常相似的事情,但是由于解密的结果,我在代码的某个地方得到了一个空输入缓冲区:java.lang.IllegalArgumentException: Null input buffer。我打印了我的私钥,它似乎是这样的:Sun RSA私钥,1024位, 模数:17117003084657245951324093201042962541622648817557411074899862199275295356525279994079227, 私钥指数:994733020251546255267347320447594310775787731259309481548242001068408479929746858409912925575444823141577188360613158181121242481。 我删掉了其中的一部分,因为它太长了。有什么想法为什么会发生这种情况吗? - C0D3
我注意到在执行byte[] cipherData = Base64.decode(encodedString);之后,cipherData为null。这可能是为什么?基本上:return new String(cipherData, "UTF-8");返回null。 - C0D3
@c0d3Junk13:请以正常方式提出问题,不要在评论中提问。 - President James K. Polk
显示剩余5条评论

0

根据您的请求,这里是一些代码片段。RSA密钥来自x509证书。

Java RSA / AES:

// symmetric algorithm for data encryption
final String ALGORITHM = "AES";
// Padding for symmetric algorithm
final String PADDING_MODE = "/CBC/PKCS5Padding";
// character encoding
final String CHAR_ENCODING = "UTF-8";
// provider for the crypto
final String CRYPTO_PROVIDER = "Entrust";
// RSA algorithm used to encrypt symmetric key
final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
// symmetric key size (128, 192, 256) if using 192+ you must have the Java
// Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files
// installed
int AES_KEY_SIZE = 256;

private byte[] encryptWithRSA(byte[] aesKey, X509Certificate cert)
        throws NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
    // get the public key from the encryption certificate to encrypt with
    PublicKey pubKey = cert.getPublicKey();

    // get an instance of the RSA Cipher
    Cipher rsaCipher = Cipher.getInstance(RSA_ALGORITHM);

    // set the cipher to use the public key
    rsaCipher.init(Cipher.ENCRYPT_MODE, pubKey);

    // encrypt the aesKey
    return rsaCipher.doFinal(aesKey);
}

private AESEncryptedContents encryptWithAes(byte[] dataToEncrypt)
        throws NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException, NoSuchProviderException {
    // get the symmetric key generator
    KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
    keyGen.init(AES_KEY_SIZE); // set the key size

    // generate the key
    SecretKey skey = keyGen.generateKey();

    // convert to binary
    byte[] rawAesKey = skey.getEncoded();

    // initialize the secret key with the appropriate algorithm
    SecretKeySpec skeySpec = new SecretKeySpec(rawAesKey, ALGORITHM);

    // get an instance of the symmetric cipher
    Cipher aesCipher = Cipher.getInstance(ALGORITHM + PADDING_MODE,
            CRYPTO_PROVIDER);

    // set it to encrypt mode, with the generated key
    aesCipher.init(Cipher.ENCRYPT_MODE, skeySpec);

    // get the initialization vector being used (to be returned)
    byte[] aesIV = aesCipher.getIV();

    // encrypt the data
    byte[] encryptedData = aesCipher.doFinal(dataToEncrypt);

    // package the aes key, IV, and encrypted data and return them
    return new AESEncryptedContents(rawAesKey, aesIV, encryptedData);
}

private byte[] decryptWithAES(byte[] aesKey, byte[] aesIV,
        byte[] encryptedData) throws NoSuchAlgorithmException,
        NoSuchPaddingException, InvalidKeyException,
        InvalidAlgorithmParameterException, IllegalBlockSizeException,
        BadPaddingException, UnsupportedEncodingException,
        NoSuchProviderException {
    // initialize the secret key with the appropriate algorithm
    SecretKeySpec skeySpec = new SecretKeySpec(aesKey, ALGORITHM);

    // get an instance of the symmetric cipher
    Cipher aesCipher = Cipher.getInstance(ALGORITHM + PADDING_MODE,
            CRYPTO_PROVIDER);

    // set it to decrypt mode with the AES key, and IV
    aesCipher.init(Cipher.DECRYPT_MODE, skeySpec,
            new IvParameterSpec(aesIV));

    // decrypt and return the data
    byte[] decryptedData = aesCipher.doFinal(encryptedData);

    return decryptedData;
}

private byte[] decryptWithRSA(byte[] encryptedAesKey, PrivateKey privKey)
        throws IllegalBlockSizeException, BadPaddingException,
        InvalidKeyException, NoSuchAlgorithmException,
        NoSuchPaddingException, NoSuchProviderException {
    // get an instance of the RSA Cipher
    Cipher rsaCipher = Cipher.getInstance(RSA_ALGORITHM, CRYPTO_PROVIDER);

    // set the cipher to use the public key
    rsaCipher.init(Cipher.DECRYPT_MODE, privKey);

    // encrypt the aesKey
    return rsaCipher.doFinal(encryptedAesKey);
}

C# .Net:

public byte[] encryptData(byte[] data, out byte[] encryptedAesKey, out byte[] aesIV) {
    if (data == null)
        throw new ArgumentNullException("data");

    byte[] encryptedData; // data to return

    // begin AES key generation
    RijndaelManaged aesAlg = new RijndaelManaged();
    aesAlg.KeySize = AES_KEY_SIZE;
    aesAlg.GenerateKey();
    aesAlg.GenerateIV();
    aesAlg.Mode = CipherMode.CBC;
    aesAlg.Padding = PaddingMode.PKCS7;

    // aes Key to be encrypted
    byte[] aesKey = aesAlg.Key;

    // aes IV that is passed back by reference
    aesIV = aesAlg.IV;

    //get a new RSA crypto service provider to encrypt the AES key with the certificates public key
    using (RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider())
    {
        //add the certificates public key to the RSA crypto provider
        rsaCSP.FromXmlString(encryptionCertificate.PublicKey.Key.ToXmlString(false));

        //encrypt AES key with RSA Public key
        //passed back by reference
        encryptedAesKey = rsaCSP.Encrypt(aesKey, false);

        //get an aes encryptor instance
        ICryptoTransform aesEncryptor = aesAlg.CreateEncryptor();

        encryptedData = encryptWithAes(aesEncryptor, data);
    }

    if (encryptedData == null)
        throw new CryptographicException(
                "Fatal error while encrypting with AES");

    return encryptedData;
}

private byte[] encryptWithAes(ICryptoTransform aesEncryptor, byte[] data) {
    MemoryStream memStream = null; // stream to write encrypted data to
    CryptoStream cryptoStream = null; // crypto stream to encrypted data

    try {
        memStream = new MemoryStream();

        // initiate crypto stream telling it to write the encrypted data to
        // the memory stream
        cryptoStream = new CryptoStream(memStream, aesEncryptor,
                CryptoStreamMode.Write);

        // write the data to the memory stream
        cryptoStream.Write(data, 0, data.Length);
    } catch (Exception ee) {
        // rethrow
        throw new Exception("Error while encrypting with AES: ", ee);
    } finally {
        // close 'em
        if (cryptoStream != null)
            cryptoStream.Close();
        if (memStream != null)
            memStream.Close();
    }

    // return the encrypted data
    return memStream.ToArray();
}

非常感谢,Petey!我将尝试这段代码...希望它能够工作,并且不会在字节数组转换为字符串时出现任何问题。我将尝试进行调整(我没有使用证书或AES),并在我尝试后发布结果。再次感谢! - Reixons
@Reixons,没问题,如果需要帮忙请告诉我。你将加密什么?如果长度超过RSA密钥长度,应该使用AES+RSA。 - Petey B
好的,我已经测试了代码,但是出现了一些问题。我得到了以下错误:javax.crypto.BadPaddingException: Data must start with zero。我会在两个小时后尝试更好地描述它。现在我没有足够的声望来这样做!:( - Reixons

0

这是我昨天无法发布的答案,与我的帖子的第一个答案有关。

好吧,我已经测试了代码并遇到了一些问题。我尽量不改变任何东西,除非完全必要。 首先,我在这里遇到了一个错误:

Cipher rsaCipher = Cipher.getInstance(RSA_ALGORITHM, CRYPTO_PROVIDER);

“Entrust”加密提供程序未被识别…所以我只留下了第一个参数。然后我收到了这个错误:

javax.crypto.BadPaddingException: Data must start with zero

我尝试通过一个用.NET编写的Web服务,它总是返回字节数组。也许翻译中存在某种问题。我知道我必须使用Base64数字,并且(如果我不使用AES)我必须将我的字符串分成大小为128字节的块(受RSA密钥限制)。 我仍在努力解决这个问题,弄清楚为什么我可以在Java中加密并在.NET中解密,但反过来却不行。

再次感谢您的帮助!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接