Reputation: 5635
RSA encryption and decryption work well in java side with modulus and exponent as below:
Java RSA Modulus and Exponent:
String nModulusPublic = "AJ+L/dVL9jnRX6IM87H8x2fR24t6wpzBDV7bcgPWblegR0LNK91z/OSX+lSLUgHSKJ9to/Eo8OMsREpNoJlEzI0=";
String eExponentPublic = "AQAB";
String eExponentPrivate = "AIpmE5C9TiAlgYG/Hn5dOlTS9FFv8fWseX65eZPepOUY4ivxN0lOZ+MsugZd03wmKvnxBuCGu5nv2qrUBTPzjcE=";
Java Public and Private Key Generators:
static PublicKey GetPublicKey(String publicKString, String publicExponentStr) throws Exception {
byte[] modulusBytes = Base64.getDecoder().decode(publicKString);
byte[] exponentBytes = Base64.getDecoder().decode(publicExponentStr);
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);
return pubKey;
}
static PrivateKey GetPrivateKey(String nModulusPublic, String eExponentPrivate) throws Exception {
byte[] modulusBytes = Base64.getDecoder().decode(nModulusPublic);
byte[] exponentBytes = Base64.getDecoder().decode(eExponentPrivate);
BigInteger modulus = new BigInteger(1, modulusBytes);
BigInteger exponent = new BigInteger(1, exponentBytes);
RSAPrivateKeySpec privSpec = new RSAPrivateKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PrivateKey privKey = fact.generatePrivate(privSpec);
return privKey;
}
I use nModulusPublic and eExponentPublic in c# to encrypting and decrypting in Java but doesn't work. Worked on RSA.Encrypt(textBytes, true); parameters in c# function change it to false and RSAEncryptionPadding.Pkcs1 but doesn't' work. When I use the result of the c# Encrypt function in java to decrypt it always encounter with this error :
javax.crypto.IllegalBlockSizeException: Data must not be longer than 64 bytes
C# encrypt function:
static string Encrypt(string text)
{
string outputB64 = string.Empty;
byte[] textBytes = Encoding.UTF8.GetBytes(text);
RSAParameters result = new RSAParameters()
{
Modulus = Convert.FromBase64String(nModulusPublic),
Exponent = Convert.FromBase64String(eExponentPublic)
};
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
RSA.ImportParameters(result);
byte[] encryptedData = RSA.Encrypt(textBytes, true);
outputB64 = Convert.ToBase64String(encryptedData);
}
return outputB64;
}
Extra Information, Java Encrypt and Decrypt main functions:
static String Decrypt(String encodedString,PrivateKey privKey) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encodedString));
return new String(decrypted, "UTF-8");
} catch (Exception err) {
return err.fillInStackTrace().toString();
}
}
static String Encrypt(String encodedString,PublicKey pubKey) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] plainBytes = new String(encodedString).getBytes("UTF-8");
byte[] cipherData = cipher.doFinal(plainBytes);
String encryptedString = Base64.getEncoder().encodeToString(cipherData);
return encryptedString;
} catch (Exception err) {
return err.fillInStackTrace().toString();
}
}
Update:
I was working on it and found that java encrypts function and c# have two different types, Java result always ended with "==" but c# function have one "=" at the end, It seems this is the problem.
C# Encrypt function result:
AJiltxqa1/8HU20XZlKJsJvclQ8PyQetpWdbCOpbqrXVg0q
/v4x5tXLxbzGKbO5InvKkib7tDQp+9BU0SYbZLv0=
Java Encrypt function result:
RlarFQBo2mcCWjidQ5l7ho2EOG6KGQWpR3ByXXHsGo6+HRQzmO4v7
TUTMdfB9wjI3aO6quruSReitrWu7QF9Vw==
Upvotes: 1
Views: 845
Reputation: 49131
Besides the padding error described in detail in Michael Fehr's answer, there is another issue which is actually responsible for the error message IllegalBlockSizeException: Data must not be longer than 64 bytes. The inconsistent padding would throw a BadPaddingException if the IllegalBlockSizeException would not be thrown first.
The posted ciphertext has a leading 0 byte and is therefore 65 bytes in size:
Base64: AJiltxqa1/8HU20XZlKJsJvclQ8PyQetpWdbCOpbqrXVg0q/v4x5tXLxbzGKbO5InvKkib7tDQp+9BU0SYbZLv0= Hex: 0098a5b71a9ad7ff07536d17665289b09bdc950f0fc907ada5675b08ea5baab5d5834abfbf8c79b572f16f318a6cee489ef2a489beed0d0a7ef415344986d92efd
If you try to decrypt this ciphertext on the Java side, you will get the posted error: IllegalBlockSizeException: Data must not be longer than 64 bytes.
Why does the C# code produce a too long ciphertext? This is because of the modulus, which also has a leading 0 byte and therefore a length of 65 bytes:
Base64: AJ+L/dVL9jnRX6IM87H8x2fR24t6wpzBDV7bcgPWblegR0LNK91z/OSX+lSLUgHSKJ9to/Eo8OMsREpNoJlEzI0= Hex: 009f8bfdd54bf639d15fa20cf3b1fcc767d1db8b7ac29cc10d5edb7203d66e57a04742cd2bdd73fce497fa548b5201d2289f6da3f128f0e32c444a4da09944cc8d
The modulus was derived with BigInteger.toByteArray()
(see this question, Update section), which returns the two's-complement representation and places a leading 0 byte in front if the frontmost byte has a value larger than 0x7f.
The leading 0 byte in the modulus results in a ciphertext generated by the C# code, which also has a leading 0 byte and thus an invalid length of 65 bytes. This does not make much sense and might be a bug.
To solve the problem the 0 byte in the modulus should be removed for the C# code, resulting in the following Base64 encoded modulus (which will produce 64 bytes ciphertexts):
n4v91Uv2OdFfogzzsfzHZ9Hbi3rCnMENXttyA9ZuV6BHQs0r3XP85Jf6VItSAdIon22j8Sjw4yxESk2gmUTMjQ==
Alternatively the 0 byte in the ciphertext can be removed, resulting in the following ciphertext (Base64 encoded):
mKW3GprX/wdTbRdmUomwm9yVDw/JB62lZ1sI6luqtdWDSr+/jHm1cvFvMYps7kie8qSJvu0NCn70FTRJhtku/Q==
which can now be successfully decrypted by the Java code to the plaintext (if a consistent padding is applied, see Michael Fehr's answer):
Davood
Upvotes: 1
Reputation: 6394
On C# encrypt function you give the parameter 'true':
byte[] encryptedData = RSA.Encrypt(textBytes, true);
This means that C# is NOT using the PKCS1Padding but the OEAPPadding.
Simply change on Java-side in your Decrypt-method (and in your ENCRYPT-method as well :-) the line
// change:
//Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// to
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING");
I tested it with your keypair and it works like expected.
Edit: On C#-side I run this code:
string plaintext = "The quick brown fox";
string encryptedDataBase64So = Encrypt(plaintext);
Console.WriteLine("encrypted SO : " + encryptedDataBase64So);
Console output:
encrypted SO : ZLylMsqcqbuDM7DprrmqIU8c8Q79fPXHudOY4INCNAo+iU7Oor3mZ8i+PP5PjtDkifqAXKYT8ON/ia9WjEFqRQ==
On Java-side I took the base64-String as input:
String fromCsharp = "Ew3nTEQuOX1tWfRNJEERa75A1o2bn6+HurVPYzGzA7kt+HAZAMdXKNACY2emvU6Bf42i8zpBO89lqvzuxNmRIw==";
String decryptedtext = DecryptWorking(fromCsharp, privateKey);
System.out.println("\ndecrypted from c#: " + decryptedtext);
Console output:
decrypted from c#: The quick brown fox
BTW: this is the PublicKey on C#-side that I generated from the PublicKey on Java-side and used it like this as source for the RSA-Encryption:
var publicKey = "<RSAKeyValue><Modulus>n4v91Uv2OdFfogzzsfzHZ9Hbi3rCnMENXttyA9ZuV6BHQs0r3XP85Jf6VItSAdIon22j8Sjw4yxESk2gmUTMjQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
RSA.FromXmlString(publicKey);
Upvotes: 2