RSA Decryption using private key in NodeJs - java

I am integrating a third-party API service, for security they have asked us to exchange information in an encrypted format. So, they have asked me to create an RSA public/private key and share the public key with them.
This is what they told me to do
Public Key certificate in 4096 bit. For encryption and decryption. (Zip File) OR Public key Self-signed certificate required(In 4096-bit format). Certificate format should be X.509 Certificate should start with “Begin Certificate” and end with “End Certificate”
Encryption Process:-
Asymmetric Encryption(RSA_Encrypt) with RSA 4096 bit Public Key(Certificate), with mode/padding as ECB/PKCS1.
Base64Encode.
In Encryption Process Partner should use our Public Key Certificate.
Decryption Process:-
Base64Decode
Asymmetric Decryption(RSA_Decrypt) by using Partner private Key certificate.
So, I ran the following command and gave them the public key certificate
openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout private.key -out public.pem
And, on my end, I have written the following functions to encrypt using their public key and decrypt using my private key
async function RSA_Encrypt(str) {
const publicKey =
fs.readFileSync(path.resolve("./their/UAT_PUBLIC_CERT.txt"), "utf8");
const buffer = Buffer.from(str);
const encrypted = publicEncrypt({ key: publicKey, padding:
constants.RSA_PKCS1_PADDING }, buffer);
return encrypted.toString("base64");
}
async function RSA_Decrypt(str) {
const privateKey = fs.readFileSync(path.resolve("./my/private.key"),
"utf8");
const buffer = Buffer.from(str, "base64");
const decrypted = privateDecrypt({ key: privateKey, padding:
constants.RSA_PKCS1_PADDING }, buffer);
return decrypted.toString("utf8");
}
Now, I am sending the request to them by encrypting the request body with RSA_Encrypt function. They are responding back with an encrypted response, upon decrypting the response using the RSA_Decrypt function I am getting the following error
error:04065072:rsa routines:RSA_EAY_PRIVATE_DECRYPT:padding check failed
Also, I've tested the encryption using my public key and decryption using my private key which is working fine. Here are the functions I've written for test purpose,
function RSA_Encrypt(str) {
const publicKey = fs.readFileSync(path.resolve("./my/public.pem"), "utf8");
const buffer = Buffer.from(str);
const encrypted = crypto.publicEncrypt({ key: publicKey, padding:
crypto.constants.RSA_PKCS1_PADDING }, buffer);
return encrypted.toString("base64");
}
function RSA_Decrypt(str) {
const privateKey = fs.readFileSync(path.resolve("./my/private.key"), "utf8");
const buffer = Buffer.from(str, "base64");
const decrypted = crypto.privateDecrypt({ key: privateKey, padding:
crypto.constants.RSA_PKCS1_PADDING }, buffer);
return decrypted.toString("utf8");
}
I think this is some padding or mode issue, but unable to figure it out on my own. Can I specify ECB mode in privateDecrypt function? The encryption on their side is most probably happening in JAVA, so is there anything I am missing out in NodeJs?

Related

Problem decrypting a Java RSA/ECB/PKCS1Padding encrypted string in Swift

My iOS app needs to decrypt a string that was encrypted on a Java backend. The backend encrypts the string using these parameters: Cipher.getInstance("RSA/ECB/PKCS1Padding").
As a simple proof of concept, using OpenSSL from the Terminal I extracted the private key from the pfx certificate that they sent me:
$ openssl pkcs12 -in certificate.pfx -nocerts -out key.pem -nodes
$ openssl rsa -in key.pem -out server.key
Then, I hardcoded the private key as a String in my code and tried to decrypt the encrypted message using the SwiftyRSA lib:
import UIKit
import SwiftyRSA
class ViewController: UIViewController {
private let rsaPemPrivateKey = """
-----BEGIN RSA PRIVATE KEY-----
THE ACTUAL PRIVATE KEY...
-----END RSA PRIVATE KEY-----
"""
private let encryptedString = "ciSvTLKRAMqz8d8SEX8epvkyyrfAoiX28Sd1RhYbsz7oOg3/taXZxZkTeCbVeFXB8Mf8eWn2SGMcVIrmFGuxqyRoC5SluJDgrup0wcG/XRg7lrt7oD6esVBAGZT5nmS79mUoAC4CKW5cHclCh66JUysATSIvI9qrrXNUnBApGJc="
override func viewDidLoad() {
super.viewDidLoad()
do {
try decryptWithPrivateKey()
} catch {
print("Exception:")
print(error)
}
}
private func decryptWithPrivateKey() throws {
let privateKey = try PrivateKey(pemEncoded: rsaPemPrivateKey)
let encrypted = try EncryptedMessage(base64Encoded: encryptedString)
let clear = try encrypted.decrypted(with: privateKey, padding: SecPadding.PKCS1)
print("Decrypted string is: \(try clear.string(encoding: .utf8))")
}
}
This results in the following error, when the method encrypted.decrypted is called:
Exception:
chunkDecryptFailed(index: 0)
I also tried with other libraries, but similar errors occurred.
Here's the result with the SwiftRSA lib:
Exception:
decryptionFailed(error: Optional(Error Domain=NSOSStatusErrorDomain Code=-50 "RSAdecrypt wrong input (err -1)" UserInfo={NSDescription=RSAdecrypt wrong input (err -1)}))
And here's the result with the SwCrypt lib:
Exception:
decodeError
I'm not sure what could be wrong here. At first I thought the problem might be with the private key, but none of the libraries throw an exception when I instantiate their corresponding PrivateKey objects with it. Perhaps the problem is with the encrypted string I'm trying to decrypt, but it seems that no base64 decoding is required, as the EncryptedMessage object expects a base 64 encoded string anyway.
Is there anything else I can try to decrypt this RSA/ECB/PKCS1Padding encrypted string in Swift? (although in Java they pass ECB as the mode of operation, it looks like it don't actually use ECB under the hood, so I don't think I need to implement any custom management of the block size of the encrypted string as they say here).
-- Edit --
Here's a few other details as requested by zaitsman:
Dummy certificate's private key:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAt+733lAMe2uhg3pLdhv///wuwYtlJNiRbC3xfz6y/9qkXoDn
3yKleJtw1JI7e1uZIN1h/bY4DUezSDuqio7VpHZ8VpnQ3pm9U87Fozpaqi/62hqM
MzQ7B7lWsrSUrcjgEDZAWehWLdMEZiRlgQL3IeUT8bh4YMMXL9a+3gf+dUHLge5J
FIVJ/3VjyPbRn/05KxPlUwxtkP5TIAMWsJkNbO38i+ymQ9GFhBuaV2rOC2WOV3eo
VMd7LqfIBhPdA/CDpYuo/ZxVPdY2OOdXTPX1JY2qKzoE4sqUrlwzuYaqLbGR6nU+
ZSMUVtivZN+fTCWS6HcmNvEmB14I5DTWJ5BjaQIDAQABAoIBABKS5SDkAH6uHb9D
KD+jEDTvaGFPDEWuQPElqo9o63Z+w75vUfrsar1FJR2yLqOEWnBBKtYOApcEuIwA
ynX3eoeDF4c/PSJdcAr4hGi5SdKJydEggSviiFt9Uc32AlWSRV4dvF3X4mv6NSWD
Y2SlwgMAOQVd1Xof+UVxcBDHyeBZOmFIVkvs+JKDKLAA+Gw6uCmQ+m2kUUtEAh7G
yPQdbqwYdi6vnyVOTRfoyC6J/fmRh0P3ZJtWEctGmlkGVVDt8qYHDwKrtACsbsXh
2dRkVOIUM4JclCK9vh/1xFpn9m+yaAI8c8XXLuN9ZyglDM9nmF6iPrf1n7DRDVRU
lRqO6okCgYEAv0+lTquO1S0RbCSzGCkKJ59JP12PknJQHC9zM6Psa0gd1LN3LYQ3
byEjCmJJhhAnBw95PuTF/B0ESa7nhSvAUazipc123gdTx/7V8raE0WlKZM89X6Vy
H4ZjvNikr/2S2WaNZWwPYNC5b2raOGZP5sEUpGBQz6M9UbZz42SZsN8CgYEA9iCy
DkhX1tzsKQhjRdXU9V/N5daXH65fto+qlpQMOcwIKDAe6DH36lugSR4gaQh+KtmR
nWX/58TM0iQWDIUMs3sbyBeESfCh3LEH42EN1M62BNC6F7A8pnLw8qfGeu+kfu5X
fAvqv0HgBJP4Duqdl30Ux9h4RqtGTyWiQaEnjLcCgYBDB3LR14YZ5sp963XcdzSZ
oVOWof9VvjuXRIDGjR6ekAvzpFWTWwnZI/EVCE7ea/ZgoOlIQfukU75W2rM/k460
jzByC0UkkcinAACSI6TxgkSQgZPtWRhdN0bmubkp+FxlU2sjJ/NTZo4yHWwL6r0A
CpJi7WQi+/zWDmkYOP0oXQKBgQDdC1weK6aH131ZFKljjLlnbZVTN7gdwdM/+CLy
fR/FwJIK1bzMOCQ5G/UF9cKR0gjNnvyB6Zs8oah5ieMrd0qC3quCtmweo7gapfs5
oG51kvgxtuuKXsL5kf0HUNqWiianwJJdW66F+jmgouuDKf5CkRlaqfTLMwNADcJ9
QqhsXQKBgQCJ4hUy3ptM6KUDG0oAT9s+cojVAmsCm5zITHkaFfkjP0W8srGXwrWx
63wLQCy84GOCFH3jecHSKHapq9V0laL+ARnepj6naO8ivMQFSucyAOeX7CfJAc45
BoezzHAqR1cnGn52G57ycqrX7eCHSULfIcYU7VwcqKJzxf1w+A+AVw==
-----END RSA PRIVATE KEY-----
Java code used to encrypt (the minimal reproducible example):
I asked the backend team for an example of the code they use to encript, and they sent me this Kotlin function:
fun String.encryptWithPublicKey(publicKey: String): String {
try {
val base64Decoder = Base64.getDecoder()
val decoded = base64Decoder.decode(publicKey.toByteArray(Charsets.UTF_8))
val key = RSAPublicKey.getInstance(decoded)
val keySpec = RSAPublicKeySpec(key.modulus, key.publicExponent)
val rsaKey = KeyFactory.getInstance("RSA").generatePublic(keySpec)
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, rsaKey)
val encrypted = cipher.doFinal(this.toByteArray(Charsets.UTF_8))
return ByteString.of(encrypted, 0, encrypted.size).base64()
} catch (e: Exception) {
throw e
}
}
Sample encrypted string, b64 encoded:
Here's the base 64 encoded, encrypted string I get from the backend response:
ciSvTLKRAMqz8d8SEX8epvkyyrfAoiX28Sd1RhYbsz7oOg3/taXZxZkTeCbVeFXB8Mf8eWn2SGMcVIrmFGuxqyRoC5SluJDgrup0wcG/XRg7lrt7oD6esVBAGZT5nmS79mUoAC4CKW5cHclCh66JUysATSIvI9qrrXNUnBApGJc=
Sample raw value:
I don't have the exact value right now, but it should be a numeric value similar to this:
1234 5678 9012 3456

create java PrivateKey and PublicKey from a String of file

Good day,
There is another third party that need my web application to send them some data in encrypt format. Thus they send me some guide to do so, however, I am not familiar with it, I am trying to google around but looks like I am google wrong way.
The guide is something as follow:
Run openssl command to generate a privatekey:
openssl ecparam -name prime256v1 -genkey -out myprivate.pem
After run this command, I output a priv.pem file, and I saw inside got some key end with '==', which is as follow:
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILefWfeuZOgnbDlxpwo3uQ2xQXfhXHUPTS+vKzvVZdCToAoGCCqGSM49
AwEHoUQDQgAE4MeQspGRJ1qdpweBfiaT5P84alZdga1f7mSpa5HqXTH58u0ZWJUQ
J7ToU/bUOPITh4FX07AV6wrgFCmwtUenDQ==
-----END EC PRIVATE KEY-----
Second one is run openssl command to generate the public key, and then send them:
openssl ec -in myprivate.pem -pubout -out mypublic.pem
Convert the private key to pkcs8 format:
openssl pkcs8 -topk8 -nocrypt -in myprivate.pem -out mypkcs8.pem
The third party will give me a public key in string format, then ask me to generate a secret key, and provide me some java code as follow:
first is to generate secret key and second one is encrypt:
public static SecretKey generateSharedSecret(PrivateKey privateKey,
PublicKey publicKey) {
try {
KeyAgreement keyAgreement = KeyAgreement.getInstance( "ECDH" );
keyAgreement.init( privateKey );
keyAgreement.doPhase( publicKey, true );
SecretKeySpec key = new SecretKeySpec(
keyAgreement.generateSecret( ), "AES" );
return key;
} catch ( Exception e ) {
// TODO Auto-generated catch block
e.printStackTrace( );
return null;
}
}
public static String encryptString(SecretKey key, String plainText) {
try {
String myIv = "Testing # IV!";
byte[] iv = myIv.getBytes( "UTF-8" );
IvParameterSpec ivSpec = new IvParameterSpec( iv );
Cipher cipher = Cipher.getInstance( "AES / CBC / PKCS5Padding" );
byte[] plainTextBytes = plainText.getBytes( "UTF-8" );
byte[] cipherText;
cipher.init( Cipher.ENCRYPT_MODE, key, ivSpec );
cipherText = new byte[cipher.getOutputSize( plainTextBytes.length )];
int encryptLength = cipher.update( plainTextBytes, 0,
plainTextBytes.length, cipherText, 0 );
encryptLength += cipher.doFinal( cipherText, encryptLength );
return bytesToHex( cipherText );
} catch ( Exception e ) {
e.printStackTrace( );
return null;
}
}
and also the bytes to hex string method:
public static String bytesToHex(byte[] byteArray) {
StringBuffer hexStringBuffer = new StringBuffer( );
for ( int i = 0; i < byteArray.length; i++ ) {
hexStringBuffer.append( String.format( "%02X", byteArray[ i ] ) );
}
return hexStringBuffer.toString( );
}
I have self gen a private key and also a public key by using openssl command, but the 4th step telling me that they will give me a public key as well, thus I am not understand, which public key should I use.
And also, how can I convert a String into java PrivateKey and PublicKey object?
* add on *
I try to convert the der file to java PublicKey object, it looks work. Before this, I convert the pem to der using openssl command:
openssl pkey -pubin -in ecpubkey.pem -outform der -out ecpubkey.der
Here is the java code:
File f = new File("/home/my/Desktop/key/ecpubkey.der");
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
KeyFactory fact = KeyFactory.getInstance("EC");
PublicKey theirpub = fact.generatePublic(new X509EncodedKeySpec(keyBytes));
However, I am hitting java.security.spec.InvalidKeySpecException: java.io.IOException: insufficient data when I try to convert der file to java PrivateKey object, the following is what I did:
openssl ecparam -name prime256v1 -genkey -out priv.pem
openssl pkcs8 -topk8 -nocrypt -in priv.pem -outform der -out priv.der
And the following is my java code:
File f2 = new File("/home/my/Desktop/key/priv.der");
FileInputStream fis2 = new FileInputStream(f2);
DataInputStream dis2 = new DataInputStream(fis2);
byte[] keyBytes2 = new byte[(int) f.length()];
dis2.readFully(keyBytes2);
dis2.close();
KeyFactory fact2 = KeyFactory.getInstance("EC");
PrivateKey pKey = fact2.generatePrivate( new PKCS8EncodedKeySpec(keyBytes2) ); // this line hit insufficient data
Diffie-Hellman is well-explained in wikipedia -- and probably some of the hundreds of Qs here, and crypto.SX and security.SX, about it, but I can't easily find which. In brief:
you generate a keypair, keep your privatekey, and provide your publickey to the other party
the other party does the same thing (or its reflection): generate a keypair, keep their privatekey, and provide their publickey to you
you use your privatekey and their publickey to compute the 'agreement' value
they similarly use their privatekey and your publickey to compute the same 'agreement' value. This is also called a shared secret, because you and the other party know it, but anyone eavesdropping on your traffic does not.
The 'provide' in that synopsis omits a lot of very important details. It is vital that when you provide your publickey to the other party they actually get your publickey and not a value altered or replaced by an adversary, and similarly when they provide their publickey to you it is vital you get the real one and not a modified or fake one. This is where actual DH systems mostly break down, and the fact you mention none of the protections or complications needed here suggests your scheme will be insecure and easily broken -- if used for anything worth stealing.
Note you should NEVER disclose or 'send' your privatekey to anyone, and they should similarly not disclose theirs. That's the main basis for public-key (or 'asymmetric') cryptography to be of any value or use at all.
There are numerous ways that keys can be represented, but only some are relevant to you.
Public keys are often represented either in
the ASN.1 structure SubjectPublicKeyInfo defined in X.509 and more conveniently in PKIX, primarily in rfc5280 #4.1 and #4.1.2.7 and rfc3279 2.3, encoded in DER, which has the limitation that many of the bytes used in this encoding are not valid characters and cannot be correctly displayed or otherwise manipulated and sometimes not transmitted or even stored; or
that same ASN.1 DER structure 'wrapped' in 'PEM' format, which converts the troublesome binary data to all displayable characters in an easily manipulable form. PEM format was originally created for a secure-email scheme call Privacy Enhanced Mail which has fallen by the wayside, replaced by other schemes and technologies, but the format it defined is still used. The publickey PEM format was recently re-standardized by rfc7468 #13 (which as you see referenced rfc5280).
OpenSSL supports both of these, but the commandline utility which you are using mostly defaults to PEM -- and since you need to convey your key to 'them', and they need to convey their key to you, PEM may well be the most reliable and/or convenient way of doing so. (Although other formats are possible, if you and they agree -- and if they require something else you'll have to agree for this scheme to work at all.)
Java directly supports only DER, thus assuming you receive their publickey in SPKI PEM, to use it in Java you need to convert it to DER. You can either do this in OpenSSL
openssl pkey -pubin -in theirpub.pem -outform der -out theirpub.der
and then read the DER into a Java crypto KeyFactory:
byte[] theirpubder = Files.readAllBytes(Paths.get(whatever));
KeyFactory fact = KeyFactory.getInstance("EC");
PublicKey theirpub = fact.generatePublic(new X509EncodedKeySpec(theirpubder));
// can downcast to ECPublicKey if you want to be more specific
Alternatively you can have Java convert the PEM which isn't too hard; there are several variations but I like:
String theirpubpem = new String(Files.readAllBytes(Paths.get(whatever)));
// IN GENERAL letting new String(byte[]) default the charset is dangerous, but PEM is OK
byte[] theirpubder = Base64.getMIMEDecoder().decode(theirpubpem.replaceAll("-----[^\\n]*\\n","") );
// continue as for DER
For private keys
there are significantly more representations, but only one (or two-ish) that Java shares with OpenSSL. Since you only need to store the private key locally and not 'send' it, PEM may not be needed; if so you can just add -outform der to your pkcs8 -topk8 -nocrypt command, adjusting the name appropriately, and read the result directly in a Java KeyFactory in the same fashion as above except with PKCS8EncodedKeySpec and generatePrivate and [EC]PrivateKey. If you do want to store it in (PKCS8-clear) PEM, you can also combine the above.
Using the DH agreement value directly as a symmetric cipher (e.g. AES) key is nonstandard and generally not considered good practice, although for ECDH with prime256v1 (aka secp256r1 or P-256) it is technically possible. AFAIK all good standards use a key-derivation step (aka Key Derivation Function or KDF) in between. Since you haven't shown us their 'guide' I can't say if this is correct -- for at least small values of correct.
To be sure you know, using CBC with a fixed IV more than once for the same key (which in this case is the same DH result) is insecure. I assume 'Testing' means you plan to replace it with something better.
Also FYI you don't need to use the full complication of the Cipher.init,update,doFinal API. When the data is small enough to fit in memory, as here, you can just do:
cipher.init(ENCRYPT_MODE, key, parms);
byte[] encrypted = cipher.doFinal (plainbytes);
// or since you want to hexify it
... bytesToHex (cipher.doFinal (plainbytes)) ...
Finally because Java byte is signed, your bytesToHex will output almost exactly half of all bytes with FFFFFF prefixed. This is very unusual, and phenomenally ugly, but again I don't know if it is 'correct' for you.
Base on dave_thompson_085 explanation and code, I manage to create my java PublicKey and Privatekey with following:
public static PublicKey getPublicKey(String filename) throws IOException, GeneralSecurityException {
String publicKeyPEM = getKey(filename);
return getPublicKeyFromString(publicKeyPEM);
}
private static String getKey(String filename) throws IOException {
// Read key from file
String strKeyPEM = "";
BufferedReader br = new BufferedReader(new FileReader(filename));
String line;
while ((line = br.readLine()) != null) {
strKeyPEM += line + "\n";
}
br.close();
return strKeyPEM;
}
public static PublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
String publicKeyPEM = key;
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
BASE64Decoder b = new BASE64Decoder();
byte[] encoded = b.decodeBuffer(publicKeyPEM);
KeyFactory kf = KeyFactory.getInstance("EC");
PublicKey pubKey = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
return pubKey;
}
and this is for private key
public static PrivateKey getPrivateKey(String filename) throws IOException, GeneralSecurityException {
String privateKeyPEM = getKey(filename);
return getPrivateKeyFromString(privateKeyPEM);
}
public static PrivateKey getPrivateKeyFromString(String key) throws IOException, GeneralSecurityException {
String privateKeyPEM = key;
privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", "");
privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", "");
BASE64Decoder b = new BASE64Decoder();
byte[] encoded = b.decodeBuffer(privateKeyPEM);
KeyFactory kf = KeyFactory.getInstance("EC");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
PrivateKey privKey = (PrivateKey) kf.generatePrivate(keySpec);
return privKey;
}
Many thanks to #dave_thompson_085 explanation.

Difference between RSA Sign with Java and openssl rsautl -sign

I'm trying to write matching code in Java, for this openssl operation:
openssl rsautl -sign
So far, I tried this:
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey, SecureRandom.getInstanceStrong());
ByteArrayInputStream bais = new ByteArrayInputStream(inputData);
byte[] sBuffer = new byte[1024];
int sBufferLen;
while ((sBufferLen = bais.read(sBuffer)) >= 0) {
sig.update(sBuffer, 0, sBufferLen);
}
bais.close();
byte[] signature = sig.sign();
Looks like the Java code calculates the SHA-256 hash for the inputData, then signs the hash and returns the signature only.
openssl, on the other hand seems to return the inputData along with the signature.
I am inferring this using the openssl rsautl -verify operation. Running this operation on the Java signed data returns the ASN1 encoded data with a sha256 object in it. Running this operation on the openssl signed data returns the actual input data.
Is there any way to mimic what openssl is doing - including the original data with the signature (detached signature?) using Java APIs?
According to the answer here, while signing:
Java does:
[hash data -> ASN.1 encode -> Pad -> modexp]
openssl only does:
[Pad -> modexp]
So I had to skip the first two steps in Java, so that it matches openssl rsautl -sign
To do that I looked at the code in the RSASignature class.
byte[] toBePadded = inputData.getBytes();
RSAPadding padding = RSAPadding.getInstance(1, 512, SecureRandom.getInstanceStrong());
byte[] toBeSigned = padding.pad(toBePadded);
byte[] opensslSignature = RSACore.rsa(toBeSigned, (RSAPrivateKey) privateKey, true);
Edit: Easier to just use "NONEwithRSA" signature type:
Signature sig = Signature.getInstance("NONEwithRSA");

Encrypt data using private key in C and decrypting data using public key in Java

I have use case where i want to to encrypt some data using private key in C and decrypt it using public key in java.
I generated the the public/private key using openssl. I am able to run a c code for encrypting the data using private key.
Something like following code :
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/err.h>
int padding = RSA_PKCS1_PADDING;
char * data;
char *encrypted;
FILE * fp = fopen(<private_key_file>,"rb");
RSA *rsa= RSA_new() ;
rsa = PEM_read_RSAPrivateKey(fp, &rsa,NULL, NULL);
RSA_private_encrypt(data_len,data,encrypted,rsa,padding);
This works fine and i am also able to decrypt it using public key in C. I am not able to decrypt the same using public key in Java. For this, I converted the public key to DER format :
$ openssl rsa -in private_key.pem -pubout -outform DER -out public_key.der
I am using following code
public String decrypt(byte[] encrypted) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA");
KeyFactory kf = KeyFactory.getInstance("RSA");
byte[] encKey = readFromFile(PUBLIC_KEY_FILE, false);
X509EncodedKeySpec ks = new X509EncodedKeySpec(encKey);
PublicKey pk = kf.generatePublic(ks);
cipher.init(Cipher.DECRYPT_MODE, pk);
byte[] plainText = cipher.doFinal(encrypted);
return new String(plainText,"UTF-8");
}
I get the following exception with my java code.
Exception in thread "main" javax.crypto.BadPaddingException: Decryption error
at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:380)
at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:291)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:356)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2121)
at AssyncKeyEncryptTest.decrypt(AssyncKeyEncryptTest.java:198)
at AssyncKeyEncryptTest.test(AssyncKeyEncryptTest.java:45)
at AssyncKeyEncryptTest.main(AssyncKeyEncryptTest.java:32)
Can someone please help me in fixing the error in decrypting the data using public key in java ?
The stacktrace suggests different padding between encryption and decryption.
Try "PKCS1Padding" as instance for decryption as you used it while encrypting.
Try "RSA/None/PKCS1Padding" instead of "RSA".
If you are unable to find a Cipher instance with "RSA/None/PKCS1Padding" then try adding a security provider like Bouncy Castle Provider.
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Adding the bouncy castle dependency should be straightforward enough.
http://www.bouncycastle.org/latest_releases.html

SHA256withRSA sign from PHP verify from JAVA

For my current project I have to send a signature from PHP to Java application. I am using Crypt/RSA right now for signing my data.
For test I am signing just "abc" with following code :
$rsa = new Crypt_RSA();
$plaintext = 'abc';
$rsa->loadKey("MIICXgIBAAKBgQDjh+hNsqJe566JO0Sg7Iq5H1AdkauACdd8QMLp9YNY0HPslVH0
rXaOFo0zgH0Ktu/Ku3lS1lfxbFQAY8b6ywZKvu4eoxlnEwuBwy09CG+3ZiVLBjCj
TZHA/KOkpVLa+tA6KsoP6zv/xI/ACkSCxPGR0q3SiRuhXV/6tacoKxUYnwIDAQAB
AoGBAIC00GOjONYWmFRoglnFdHNjkx4m2KyE5LAUsi1GBBapU+nwTXvq47VcbGNF
u3XkJaC4i9igBv86GApgZp5XWia86On/Lz9NR4fB2EFP6Ydy84GfCDNNvkism4BR
aA+eYdNiQ3Wfyi98ZpUi+rPsoI6Cid4eSkCC4poTUaqzMkiBAkEA9Gn1oIlUEoVI
q/u5Y9vflXRDt95AA9AokJkQj7XTNjsz8ypU8TO6D6ZykpcbK6zjU0UJsQiC3dKj
AgmAR2VzYwJBAO5RETMAyDnR+5g+MtHpwGqGdY4dq0j4y4CsdtOYKWwSTh3VQy+C
eghJoyPRfIpulw2Mk/l+occEI0ohJl0+UJUCQQDSZtjVLwMZwnUx4EvSw/ewL9sP
0Jpo7evNtoaEQDEncUWiYeGnljDowg/FU6FHMtiq2TajmMEXdflvioBMdfAjAkEA
3TB60SbJr/i4Fo6sJm5ZO8W+eAALiTf50VzBERTqZTb8L+5PZFoqn2SROV5mxClu
o5G1idzBlHC/vD7WV7bNnQJAd0FrxaMBurJ4Uv/B8TDP+eeBdB7d9rKw0+TVlcel
cbpIz6BIP6+nmsgy6dbDRnx0eC/MgF2EU0wrCu1DK0PyWA==");
$rsa->setHash("sha256");
$signature = $rsa->sign($plaintext);
$signature_encoding = mb_convert_encoding($signature, "UTF-8");
error_log("signature encoded in UTF-8 :" . $signature_encoding);
$encoded_sign = base64_encode($signature_encoding);
error_log("encoded sign for abc: " . $encoded_sign);
I can verify the signature from php code. But when it comes to verifying from JAVA, i was not successfull. Here is the java code that does the verify operation :
public boolean verify(String signed, String data, PubKey pubKey) throws Exception{
PublicKey publicKey = jceProvider.generateRSAPublicKeyFromX509(
base64.decode(pubKey.getEncodedKey())
);
byte[] signature = base64.decode(signed);
byte[] verifier = data.getBytes(Charset.forName("UTF-8"));
return jceProvider.verify(signature, verifier, publicKey);
}
public class JCEProvider {
public boolean verify (byte[] signature, byte[] verifier, PublicKey publicKey) throws Exception{
Signature rsaSignature = Signature.getInstance("SHA256withRSA");
rsaSignature.initVerify(publicKey);
rsaSignature.update(verifier);
return rsaSignature.verify(signature);
}
I dont think it is because of keys, I can already verify it from PHP as I told before. There is something that I miss about PHP encoding or byte streams but I am lost for the moment.
Any help would be appreciated.
I'm using openssl like Whity already mentioned. Here is my striped down example. Be aware of any character encoding, line ending, etc. This results in changed binary representation of your text data.
PHP-RSA_SHA256-Sign:
<?php
$data = "For my current project I have to send a signature from PHP to Java application. I am using Crypt/RSA right now for signing my data.";
$private_key = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBANDiE2+Xi/WnO+s120NiiJhNyIButVu6zxqlVzz0wy2j4kQVUC4Z
RZD80IY+4wIiX2YxKBZKGnd2TtPkcJ/ljkUCAwEAAQJAL151ZeMKHEU2c1qdRKS9
sTxCcc2pVwoAGVzRccNX16tfmCf8FjxuM3WmLdsPxYoHrwb1LFNxiNk1MXrxjH3R
6QIhAPB7edmcjH4bhMaJBztcbNE1VRCEi/bisAwiPPMq9/2nAiEA3lyc5+f6DEIJ
h1y6BWkdVULDSM+jpi1XiV/DevxuijMCIQCAEPGqHsF+4v7Jj+3HAgh9PU6otj2n
Y79nJtCYmvhoHwIgNDePaS4inApN7omp7WdXyhPZhBmulnGDYvEoGJN66d0CIHra
I2SvDkQ5CmrzkW5qPaE2oO7BSqAhRZxiYpZFb5CI
-----END RSA PRIVATE KEY-----
EOD;
$binary_signature = "";
$algo = "SHA256";
openssl_sign($data, $binary_signature, $private_key, $algo);
print(base64_encode($binary_signature) ."\n");
?>
The output of base64 encoded binary signature is:
OnqiWnFQ2nAjOa1S57Du9jDpVr4Wp2nLdMk2FX+/qX1+SAHpVsW1JvQYqQUDlxvbTOE9vg6dlU6i3omR7KipLw==
JAVA-RSA_SHA256-Verify:
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import org.apache.commons.codec.binary.Base64;
public class RsaVerify {
public static void main(String args[]){
String publicKey =
// "-----BEGIN PUBLIC KEY-----"+
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANDiE2+Xi/WnO+s120NiiJhNyIButVu6"+
"zxqlVzz0wy2j4kQVUC4ZRZD80IY+4wIiX2YxKBZKGnd2TtPkcJ/ljkUCAwEAAQ==";
// "-----END PUBLIC KEY-----";
byte[] data = "For my current project I have to send a signature from PHP to Java application. I am using Crypt/RSA right now for signing my data.".getBytes();
byte[] signature = Base64.decodeBase64("OnqiWnFQ2nAjOa1S57Du9jDpVr4Wp2nLdMk2FX+/qX1+SAHpVsW1JvQYqQUDlxvbTOE9vg6dlU6i3omR7KipLw==");
try {
System.out.println(verify(data, signature, publicKey));
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
}
private static boolean verify(byte[] data, byte[] signature, String publicKey) throws GeneralSecurityException{
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubKey);
sig.update(data);
return sig.verify(signature);
}
}
phpseclib uses the more secure PSS padding by default. Java is probably using PKCS#1 padding. So if you were to go the phpseclib route (which I'd recommend doing)... do this:
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
I think u need to improve your PHP solution.
According to http://php.net/manual/en/function.openssl-get-md-methods.php you can use directly [47] => sha256WithRSAEncryption from PHP, probably call openssl from commandline also be possible:
openssl dgst -sha256 -sign my.key -out in.txt.sha256 in.txt

Categories