SHA1 signature created in Java not being verified in PHP - java

I am creating an RSA signature in Java and sending it in the Auth header to a PHP server which is then verifying it. The problem is that although the signature is being verified in Java, it is failing in PHP. How do I fix this?
private String getSignature(JsonObject body) {
try {
InputStream is = getClass().getClassLoader().getResourceAsStream("private.key");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
try (InputStreamReader keyReader = new InputStreamReader(is);
PemReader pemReader = new PemReader(keyReader)) {
PemObject pemObject = pemReader.readPemObject();
byte[] content = pemObject.getContent();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
PrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privKeySpec);
System.out.println(privKey.getAlgorithm());
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initSign(privKey);
sign.update(body.toString().getBytes());
verifySignature(body, sign.sign());
return new String(sign.sign());
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String verifySignature(JsonObject body, byte[] bs) {
try {
InputStream is = getClass().getClassLoader().getResourceAsStream("public.pem");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
try (InputStreamReader keyReader = new InputStreamReader(is);
PemReader pemReader = new PemReader(keyReader)) {
PemObject pemObject = pemReader.readPemObject();
byte[] content = pemObject.getContent();
KeySpec privKeySpec = new X509EncodedKeySpec(content);
PublicKey privKey = (RSAPublicKey) keyFactory.generatePublic(privKeySpec);
System.out.println(privKey.getAlgorithm());
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initVerify(privKey);
sign.update(body.toString().getBytes());
boolean res = sign.verify(bs);
System.out.println(res);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
My PHP code where I'm verifying the sign:
$fp = fopen("public.pem", "r");
$pub_key = fread($fp, 8192);
fclose($fp);
$pubkeyid = openssl_pkey_get_public($pub_key);
$dd = ["email"=>"kkamran#gmail.com","password"=>"!Pass1234","platform"=>"con"];
$ss = base64_decode("dHIG77+9fu+/ve+/ve+/vVTvv73vv70677+9QiHGnmjHu++/vQYm77+977+977+9bXM+Pe+/ve+/vRtlJcyYY++/vdiu77+9fu+/vUkM77+9RQI2BO+/ve+/vX4YFRwt77+9GO+/vWrvv71F77+9C++/ve+/ve+/ve+/vWTvv73vv71zB++/vXFaQ86277+9LHXvv71+Q3Dvv73vv73vv71TEzMo77+9SgLvv73vv73vv70377+9IO+/vVFW0rYc77+9QX8a77+977+977+977+9Iu+/ve+/ve+/vUYkIU5heO+/vc6e77+977+977+9Rmnvv73vv71+PRxy77+9zLTvv73vv71aDe+/vQjvv71UY++/ve+/vUTvv704FyZjBkB/77+977+9fe+/ve+/ve+/ve+/vXJgUAYUVO+/ve+/ve+/vXNE77+9fmAc77+977+9Ye+/vUAKF8izTO+/ve+/vSPvv700Ru+/ve+/ve+/vWHvv70bDtyoNmxpKd2UKe+/ve+/ve+/vXgCLe+/ve+/vQPvv70p77+9S2Xvv73vv71bX++/vUhw77+9Oe+/vV0+77+9De+/vQs=");
$ok = openssl_verify( $data, $ss, $pubkeyid, OPENSSL_ALGO_SHA1);
Update:
I am now signing like this:
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initSign(privKey);
sign.update(body.toString().getBytes());
String signStr = Base64.getEncoder().encodeToString(sign.sign());
verifySignature(body, signStr.getBytes());
return signStr;

The line:
return new String(sign.sign());
in the getSignature() method performs an decoding of the signature with the default charset. From the posted signature it can be concluded that this is the UTF-8 charset. This UTF-8 decoding corrupts the signature!
Binary data like signatures, ciphertexts, hash values, random binary data etc. generally do not contain UTF-8 compliant byte sequences. When decoding with UTF-8, these non-compliant sequences are replaced by the 0xEFBFBD replacement character, which irreversibly corrupts the data. The 0xEFBFBD byte sequence occurs with high frequency in the posted signature (after Base64 decoding), which is a clear indication of corruption resulting from UTF-8 decoding.
In general, charset encodings such as UTF-8 are not suitable for converting binary data to a string (unless, of course, the binary data was generated using that encoding). Instead, a binary-to-text encoding should be used, e.g. Base64, see also here.
A second problem is the double sign.sign() call in getSignature(). A sign() call resets the state of the signature object to the state immediately after the last initSign(), see here. I.e. in the present case the second call is made without the data from the update() call, so the signature is ultimately created for an empty message. This signature is returned and therefore of course does not correspond to the actual message, so that a later verification will fail.
A possible solution would be to additionally execute the corresponding update() call before the second sign() call.
Of course, in this particular case it is more efficient to execute the sign.sign() call only once and store the result to be able to use it later (as often as needed).
Both problems are successfully fixed as follows:
...
byte[] signature = sign.sign();
verifySignature(body, signature);
return Base64.getEncoder().encodeToString(signature);
...

Related

JWS Signing with RSA 256 privatekey with Algorithm RSASSA-PKCS1-v1.5 SHA-256

I need some help with JWS Signing with RSA 256 privatekey -RSASSA-PKCS1-v1.5 SHA-256
I m working on SAP PI/PO.
I am unable to retrieve the RSA privatekey saved in server's OS folder, so I am trying to pass the pem(base64 encoded) key as a string.
My requirement is to generate Header & payload & signed it.
Sample input Header:
{"alg": "RS256","kid": "asff1233dd"}
sample Json Payload:
{"CompInvoiceId": "0009699521","IssueDtm": "20220623"}<br />
Error: I am able to generate Header.payload in base64 url encode but the signature part is getting corrupted when I convert the privatekey to PKCS8encoding.
The generated JWS looks like:
eyJhbGciOiJSUzI1NiIsImtpZCI6Imh5d3kzaHIifQ.eyJDb21waW52b2ljZSI6IjAwOTk5MzMzIiwic3VibWl0SWQiOiIxMjM0NSJ9.[B#42ace6ba
This is signature part which is getting corrupted - [B#42ace6ba
Kindly help with below code:
This is because of this declaration byte[] signed = null, when I remove
that it just throwserror as cannot find variable for signed.
Please help me with passing privatekey & signature.
The Java code I am working on:
I am passing :
Json data= data,
header = header
Privatekey in base64 = key
String jwsToken(String key, String data, String header, Container container) throws
StreamTransformationException{
String tok = null;
byte[] signed = null;
try {
StringBuffer token = new StringBuffer();
//Encode the JWT Header and add it to our string to sign
token.append(Base64.getUrlEncoder().withoutPadding().encodeToString(header.getBytes("UTF-
8")));
token.append(".");
//Encode the Json payload
token.append(Base64.getUrlEncoder().withoutPadding().encodeToString(data.getBytes("UTF-8")));
//Separate with a period
token.append(".");
//String signedPayload =
Base64.getUrlEncoder().withoutPadding().encodeToString(signature.sign());
PrivateKey privatekey = null;
String privateKeyPEM = key;
//String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());
byte[] decodePrivateKey = Base64.getDecoder().decode(key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodePrivateKey);
privatekey = (PrivateKey) keyFactory.generatePrivate(keySpec);
Signature sig = Signature.getInstance( "SHA256withRSA" );
sig.initSign( ( PrivateKey ) privatekey );
sig.update(token.toString().getBytes("UTF-8"));
signed=sig.sign();
tok = (token.toString());
}
catch (Exception e) {
e.printStackTrace();
}
return tok;
}
Instead of appending byte array, encode it in base64 then append it
signed = sig.sign();
token.append(Base64.getUrlEncoder().withoutPadding().encodeToString(signed));

Private Key sign data in Java to PHP

I have to write PHP program to do the same function with the java sign function as follow
public static String sign(byte[] data, String privateKey) throws Exception {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(data);
byte[] hashData = messageDigest.digest();
StringBuffer hexString = new StringBuffer();
byte[] keyBytes = Base64Utils.decode(privateKey.getBytes());
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance("NONEWithRSA");
signature.initSign(privateK);
signature.update(hashData);
byte[] sign = signature.sign();
return Base64.getEncoder().encodeToString(sign);
}
I have done some research on google and try to write the PHP code as follow
public function sign($data, $privateKeyString){
$privateKey = openssl_pkey_get_private($privateKeyString);
$hashData = hash("sha256",$data);
openssl_sign($hashData, $signature, $privateKey);
openssl_free_key($privateKey);
return base64_encode($signature);
}
I try to pass the same key with the data let's say "Hello" to both function and testing
the hash data are map but the outcome signature are different
Is there anyone can spot what cause the return base64 signature are different between the java and php?
public function sign($data, $privateKeyString){
$privateKey = openssl_pkey_get_private($privateKeyString);
$hashData = openssl_digest("sha256",$data);
openssl_private_encrypt($hashData, $signature, $privateKey);
openssl_free_key($privateKey);
return base64_encode($signature);
}
Finally fixing the problem by using openssl_digest "sha256" instead of passing sha256 hash value into the for private key encrytion, due to the hashing need to be convert into the hex string instead of the original value.

Java Exceptions: Inappropriate key specification and short read of DER length

Using Base64 functions from the Apache Commons API and working with DSA I am trying to load a base 64 encoded public key from a file here is the method being used
/**
* Load a base-64 encoded public key in X.509 format
* #param pkfile the name of the file containing the public key
* #return an instance of PublicKey on success or null on failure
*/
public PublicKey loadBase64PublicKey(String pkfile) {
PublicKey pub;
Base64InputStream bis;
byte[] buffer;
// load the contents of the pkfile into the buffer
try {
bis = new Base64InputStream(new FileInputStream(pkfile));
buffer = new byte[bis.available()];
bis.read(buffer);
bis.close();
} catch (Exception e) {
System.err.println(e.toString());
return null;
}
// use a KeyFactory to parse the data
try {
KeyFactory kf = KeyFactory.getInstance("DSA");
pub = kf.generatePublic(new X509EncodedKeySpec(buffer));
} catch (Exception e) {
e.printStackTrace();
return null;
}
return pub;
}
Main Method:
public static void main(String args[]) {
DigitalSignatureA DSA = new DigitalSignatureA();
// load public key
PublicKey pubKey;
pubKey = DSA.loadBase64PublicKey("sign\\pubkey-1.dat");
}
However when calling the method from main the following error comes up:
java.security.spec.InvalidKeySpecException: Inappropriate key specification: IOException: Short read of DER length
at sun.security.provider.DSAKeyFactory.engineGeneratePublic(Unknown Source)
at java.security.KeyFactory.generatePublic(Unknown Source)
at DigitalSignatureAssignment.loadBase64PublicKey(DigitalSignatureAssignment.java:147)
at DigitalSignatureAssignment.main(DigitalSignatureAssignment.java:224)
Line 147 would be pub = kf.generatePublic(new X509EncodedKeySpec(buffer));
The public key from the file is encoded in X509 but saved under base64 encoding and the Base64InputStream decodes any input.
Java InputStream.available() is never guaranteed to tell you how much (more) data exists
Note that while some implementations of InputStream will return the total number of bytes in the stream, many will not. It is never correct to use the return value of this method to allocate a buffer intended to hold all data in this stream.
and in this commons-codec case it doesn't even try
Returns:
0 if the InputStream has reached EOF, 1 otherwise
Either pick a sufficiently large buffer size to start with, or keep expanding it and reading more until EOF, or a combination of both. Or read the file into memory as text (e.g. Files.readAllLines or Files.readAllBytes in j8+) and then decode that in-memory copy (FWIW j8+ now has java.util.Base64 and you don't need commons-codec for this)

Public Key Unknown Encoding

I have this public key:
MIGJAoGBAKv4OKlpY2oq9QZPMzAjbQfiqDqTnisSvdLP+mTswZJdbtk1J+4+qAySJuZjSQljzcUu0ANg+QG0VsvoU72zu5pErZKWubfe9HB/tq69bhP60qgP6/W2VebWlqUNGtsMedxuVaFBL3SoqU7e5RELIsuArCJJIgz86BQDX0x63VpXAgMBAAE=
I am trying to use it to decode this:
Zm/qR/FrkzawabBZYk7WfQJNMVZoZrwWTvfQwIhPMzAuqEO+y+sb/x9+TZwTbqmu45/GV4yhKv0bbDL8F6rif7RJap7iQUFQBDEIAraY42IGZ8pB6A0Q0RSnJWW+tLTLJg5cTrgZQ8sLoO+U03T6DE1wy73FU5h6XhXxZERo0tQ=
In which I know the unencrypted value is this:
2ABB43E83F7EC33D0D33F64BA5782E42
I have been trying several different things including Bouncy Castle (Java implementation) but I am unable to get the public key to work, mostly ending in invalid encoding errors.
This is my current implementation:
byte[] keyBytes = Base64.decodeBase64(PUB_KEY);
try {
AlgorithmIdentifier rsaIdent = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption);
SubjectPublicKeyInfo kInfo = new SubjectPublicKeyInfo(rsaIdent, keyBytes);
ASN1Primitive primKey = kInfo.parsePublicKey();
byte[] encoded = primKey.getEncoded();
byte[] sessionBytes = Base64.decodeBase64("Zm/qR/FrkzawabBZYk7WfQJNMVZoZrwWTvfQwIhPMzAuqEO+y+sb/x9+TZwTbqmu45/GV4yhKv0bbDL8F6rif7RJap7iQUFQBDEIAraY42IGZ8pB6A0Q0RSnJWW+tLTLJg5cTrgZQ8sLoO+U03T6DE1wy73FU5h6XhXxZERo0tQ=");
Security.addProvider(new BouncyCastleProvider());
X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
KeyFactory factory = KeyFactory.getInstance(spec.getFormat());
Cipher cipher = Cipher.getInstance("RSA", "BC");
cipher.init(Cipher.DECRYPT_MODE, factory.generatePublic(spec));
// ----- THIS IS WHERE IT BREAKS -----
byte[] decrypted = cipher.doFinal(sessionBytes);
String tada = new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) { ... }
When I get to generate the public key from the factory I get
java.lang.IllegalArgumentException: unknown object in getInstance: org.bouncycastle.asn1.ASN1Integer
I have tried several other things but all result in the same error above.
Is there something wrong with my public key? What is the correct way to do this?
First of all, your key is PKCS#1 encoded. It's not a SubjectPublicKeyInfo structure required by Java. You can see how to decode it here.
Second, you cannot decrypt with a public key, you need a private key for that.

problem reading a publickey file and use the key to encrypt another file

I've been strugling with reading a publickey file which I want to get the key sting in the file and use it to encrypt another file. I'm using RSA PKCS1 v1.5 in encrypting and signing the file with SH1 hashing algorythim but thats not the problem, the problem is that I've been supplied with the publickey file to use when encrypting and I cant seem to win with reading the file and generating a publicKey object.
Here's the code:
void setPublicKey(String file)
{
try
{
FileInputStream keyfis = new FileInputStream(file);
byte[] encKey = new byte[keyfis.available()]; keyfis.read(encKey);
keyfis.close();
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// I get an exception on the below line
publicKey = keyFactory.generatePublic(pubKeySpec);
} catch (Exception e)
{
e.printStackTrace();
}
}
Can someone please help!!
AFAIK X509 encoded keys are binary files encoded using ASN.1. Therefore the question on new-lines at the end does not make any sense.
If you have a text file you have a PEM encoded file and I am currently not sure which KeySpec you have to use in this case.
You may convert the PEM encoded key to a DER encoded key (e.g. using OpenSSL) or you can use BouncyCastle which as support for loading PEM encoded keys.
BTW: Using keyfis.read(encKey); is dangerous as the read method only reads up encKey bytes but don't have to. Better create a DataInputStream from the InputStream and use readFully(encKey):
new DataInputStream(keyfis).readFully(encKey);
Found the solution but not sure yet if its the right solution coz I still have to get the PrivateKey and decrypt the file but for now I was able to encrypt it using the supplied PublicKey as the modulus but I don’t have the exponent and I just used some common number “65537” as the exponent
Which I read that it is not a critical part of the encryption.
I had to change the logic to use the RSAPublicKeySpec (which uses BigInteger and Base64Decoder)instead of X509EncodedKeySpec to set the KeySpec
And continue to use the KeyFactory object to generate the public key.
Now this logic NEEDS the modulus and exponent.
byte[] buffer = new byte[(int) new File(file).length()];
BufferedInputStream f = new BufferedInputStream(new FileInputStream(file));
f.read(buffer);
String modulusBase64 = new String(buffer);
BASE64Decoder b64dec = new BASE64Decoder();
String exponentBase64 = "65537";
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(new BigInteger (1, b64dec.decodeBuffer(modulusBase64)), new BigInteger(1, b64dec.decodeBuffer(exponentBase64)));
KeyFactory publicKeyFactory = KeyFactory.getInstance("RSA");
publicKey = publicKeyFactory.generatePublic(publicKeySpec);
//This is the PublicKey in the file. "J45t4SWGbFzeNuunHliNDZcLVeFU7lOpyNkX1xX+sVNaVJK8Cr0rSjUkDC8h9n+Zg7m0MVYk0byafPycmzWNDynpvj2go9mXwmUpmcQprX1vexxT5j1XmAaBZFYaJRcPWSVU92pdNh1Sd3USdFjgH0LQ5B3s8F95xdyc/5I5LDKhRobx6c1gUs/rnJfJjAgynrE4AsNsNem+STaZWjeb4J+f5Egy9xTSEl6UWxCClgCwhXopy10cBlH8CucpP0cyckOCIOloJ7mEMRCIpp6HPpYexVmXXSikTXh7aQ7tSlTMwUziIERc/zRpyj1Nk96Y7V8AorLFrn1R4Of66mpAdQ=="

Categories