Based on nimbus-jose-jwt Java library, I tried to create the following JSON Web Token (JWT) and signed it using a JSON Web Signature (JWS) using a string "secret" hashed to SHA256.
But after generating serialized string and test it at jwt.io, i always get the error "Invalid Signature".
When I try to decode at server side using Python decoder, I also get signature error. What could be wrong?
byte[] bytes = new byte[32];
String message = "secret";
MessageDigest md = MessageDigest.getInstance("SHA-256");
bytes = md.digest(message.getBytes("UTF-8"));
JWSSigner signer = new MACSigner(bytes);
// Prepare JWT with claims set
JWTClaimsSet claimsSet = new JWTClaimsSet();
claimsSet.setSubject("alice");
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
// Apply the HMAC
signedJWT.sign(signer);
// To serialize to compact form, produces something like
String s = signedJWT.serialize();
It looks like you are using the SHA-256 digest of "secret" as the key to create the MAC, and plain old "secret" for validating. Replace:
byte[] bytes = new byte[32];
String message = "secret";
MessageDigest md = MessageDigest.getInstance("SHA-256");
bytes = md.digest(message.getBytes("UTF-8"));
JWSSigner signer = new MACSigner(bytes);
with:
JWSSigner signer = new MACSigner("secret");
Related
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));
I need to integrate with some service provider API, where each request needs to be signed with private key of our certificate and service provider will decrypt it using our public key and similarly for response service provider will encrypt it using their private key and we will decrypt it using their public key (Means we will share our Public Key between each other)
Service Provider did not have sample code in php
Instructions which they provided are:
Get contents in Bytes to Sign (Using UTF-8 Encoding)
Get the X509 Certificate (Private Key Certificate)
Compute the Message Digest
Sign the Message and generate attached Signature
(use Detached/attached property of Signing Library)
Encode your Signed Message into Base64 Encoding
Send the Encoded Message on HTTP
Sample Code in Java provided by service provider:
string data = "SAMPLE REQUEST XML";
priv = (PrivateKey)keystore.getKey(name, passw);
CMSSignedDataGenerator sgen = new CMSSignedDataGenerator();
sgen.addSigner(priv, (X509Certificate)cert, CMSSignedDataGenerator.DIGEST_SHA1);
sgen.addCertificatesAndCRLs(certs);
byte[] utf8 = data.getBytes("UTF8");
// The 2nd parameter need to be true (dettached form) we need to attach original message to signed message
CMSSignedData csd = sgen.generate(new CMSProcessableByteArray(utf8), true, "BC");
byte[] signedData = csd.getEncoded();
char[] signedDataB64 = Base64Coder.encode(signedData);
String str = new String(signedDataB64);
I have written following code in php:
$utf8_data = utf8_encode($xmlString);
$byte_array = unpack('C*', $utf8_data);
$bytesStr = implode("", $byte_array);
$data = file_get_contents('/path/to/abc.pfx');
$certPassword = 'certpassword';
openssl_pkcs12_read($data, $certs, $certPassword);
$private_key = $certs['pkey'];
$binary_signature = "";
openssl_sign($bytesStr, $binary_signature, $private_key, OPENSSL_ALGO_SHA1);
Now my question is my above code correct ? Also how openssl_sign give me signature + sample request data,
As i am not able to generate same encoded string which service provider has provided into their sample,
This is the code am using on the Java side
private String encodedHexString(String secretKey, String payload)
throws NoSuchAlgorithmException, InvalidKeyException {
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(),"HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(keySpec);
byte[] payloadDigest = mac.doFinal(payload.getBytes());
String encodedDigest = DatatypeConverter.printHexBinary(payloadDigest);
return encodedDigest;
}
where secretKey is the Secret token entered on GitHub side as well on my side and the payload is what I am getting in the request.getParameter("payload") for application/x-www-form-urlencoded Content-type.
matching this with request.getHeader("X-Hub-Signature") don't match unfortunately, even after appending "sha1=" to it on Java side.
I had success with simply encoding the whole request body, without any kind of parameter parsing, unescaping, etc:
String secret = "73fcce1ecaeeb4136f27854eaaacb785929bb9a3";
String payload = "payload=%7B%22zen%22%3A%22It%27s+not+fully+shipped+until+it%27s+fast.%22%2C%22hook_id%2...";
System.out.println(encodedHexString(secret, payload));
The result was:
0F026F9E5107F4BF377CA032C1431BFED687B6E9
This matched the X-Hub-Signature header very well:
X-Hub-Signature: sha1=0f026f9e5107f4bf377ca032c1431bfed687b6e9
An Android client (4.2.1) application sends a public key via a HttpPost request to a PHP (5.6) API. This API encrypts the data with AES compliant RIJNDAEL_128, then encrypts the key for the AES encryption with the client public key with OpenSSL public encryption and RSA_PKCS1_OAEP_PADDING. It sends this data base64 encoded via XML back to the client android application which shall encrypt the data. I've setup a basic PHP test script which tests the whole process, this works as expected.
Currently I'm working on implementing the decryption in the client Android application but already decrypting the AES-key fails. I have other questions besides this current problem (see at the end).
Here is a text graphical synopsis of what is happening:
client -> public key -> API -> data -> AESencrypt(data), RSAencrypt(AES-key) -> base64encode[AES(data)], base64encode[RSA(AES-key)] -> <xml>base64[AES(data)], base64[RSA(AES-key)]</xml> -> client -> base64[AES(data)], base64[RSA(AES-key)] -> base64decode[AES(data)], base64decode[RSA(AES-key)] -> AESdecrypt(data), RSAdecrypt(AES-key) -> data
I'm encrypting the data with MCRYPT_RIJNDAEL_128 which I read is AES compatible (see PHP doc for mycrypt).
Here is the code:
<?php
$randomBytes = openssl_random_pseudo_bytes(32, $safe);
$randomKey = bin2hex($randomBytes);
$randomKeyPacked = pack('H*', $randomKey);
// test with fixed key:
// $randomKeyPacked = "12345678901234567890123456789012";
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$dataCrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $randomKeyPacked, $data, MCRYPT_MODE_CBC, $iv);
The AES-key coming out of this is encoded with openssl_public_encrypt and the padding setting OPENSSL_PKCS1_OAEP_PADDING. Reading the source code (source of PHP OpenSSL implementation) this is equivalent to RSA_PKCS1_OAEP_PADDING described as
EME-OAEP as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter.
in the OpenSSL documentation found here. Afterwards I base64_encode the data to be able to transfer it via an XML string to the client. The code looks like this:
openssl_public_encrypt($randomKeyPacked, $cryptionKeyCrypted, $clientPublicKey, OPENSSL_PKCS1_OAEP_PADDING);
$content = array(
'cryptionKeyCryptedBase64' => base64_encode($cryptionKeyCrypted),
'cryptionIVBase64' => base64_encode($iv),
'dataCryptedBase64' => base64_encode($dataCrypted)
);
// $content gets parsed to a valid xml element here
The client Android application gets the return data via HttpPost request via a BasicResponseHandler. This returned XML string is valid and parsed via Simple to respective java objects. In the the class holding the actual content of the transferred data I currently try to decrypt the data. I decrypt the AES-key with the transformation RSA/ECB/OAEPWithSHA-1AndMGF1Padding which due to this site (only I could find) is a valid string and seems to be the equivalent of the padding I used in PHP. I included the way I generated the private key as it is the same way I generate the public key that was send to the PHP API. Here is that class:
public class Content {
#Element
private String cryptionKeyCryptedBase64;
#Element
private String cryptionIVBase64;
#Element
private String dataCryptedBase64;
#SuppressLint("TrulyRandom")
public String getData() {
String dataDecrypted = null;
try {
PRNGFixes.apply(); // fix TrulyRandom
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
keygen.initialize(2048);
KeyPair keypair = keygen.generateKeyPair();
PrivateKey privateKey = keypair.getPrivate();
byte[] cryptionKeyCrypted = Base64.decode(cryptionKeyCryptedBase64, Base64.DEFAULT);
//byte[] cryptionIV = Base64.decode(cryptionIVBase64, Base64.DEFAULT);
Cipher cipherRSA = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipherRSA.init(Cipher.DECRYPT_MODE, privateKey);
byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);
byte[] dataCrytped = Base64.decode(dataCryptedBase64, Base64.DEFAULT);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipherAES = Cipher.getInstance("AES");
cipherAES.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decryptedAESBytes = cipherAES.doFinal(dataCrytped);
dataDecrypted = new String(decryptedAESBytes, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return dataDecrypted;
}
}
Doing this I currently fail at line
byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);
with Bad padding exceptions for nearly all PHP openssl_public_encrypt padding parameter - Android Cipher transformation string combinations I tried. Using the standard PHP padding parameter by omitting the padding parameter in the openssl_public_encrypt which defaults to OPENSSL_PKCS1_PADDING and a Cipher transformation string of just Cipher.getInstance("RSA") I do not get a bad padding exception. But the encrypted key seems not to be valid as AES decryption fails with
java.security.InvalidKeyException: Key length not 128/192/256 bits.
I tried validating this with a fixed key (see code comment in PHP code above) and I don't get the same key back after decrypting it and transforming it to a string. It seems it is just garbled data although it is 256 bits long if I read the Eclipse ADT debugger correctly.
What might be the correct Cipher transformation string to use as an equivalent for PHP's OPENSSL_PKCS1_OAEP_PADDING. Reading this documentation I need the transformation string in the form "algorithm/mode/padding", I guessed that algorithm = RSA but I couldn't find out how to translate what the OpenSSL (above) documentation states about the padding into a valid cipher transformation string. I.e. what is mode for example?
Unfortunately this Android RSA decryption (fails) / server-side encryption (openssl_public_encrypt) accepted answer did not solve my problem.
Anyway might this solve my problem or does my problem originate elsewhere?
How would I further debug this? What is the correct way to transform the base64 decoded, decrypted key into a human readable form so I can compare it with the key used to encrypt?
I tried with:
String keyString = new String(keyBytes, "UTF-8");
But this doesn't give any human readable text back so I assume either the key is wrong or my method of transforming it.
Also decrypting the AES encrypted data in PHP the IV is needed in the decryption function mcrypt_decrypt. As you can see in the code I send it but it seems in Android this is not needed? Why so?
PS: I hope I provided all needed information, I can add further in the comments.
PPS: For completeness here is the Android client code making the HttpPost request:
#SuppressLint("TrulyRandom")
protected String doInBackground(URI... urls) {
try {
System.setProperty("jsse.enableSNIExtension", "false");
HttpClient httpClient = createHttpClient();
HttpPost httpPost = new HttpPost(urls[0]);
PRNGFixes.apply(); // fix TrulyRandom
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
keygen.initialize(2048);
KeyPair keypair = keygen.generateKeyPair();
PublicKey publickey = keypair.getPublic();
byte[] publicKeyBytes = publickey.getEncoded();
String pubkeystr = "-----BEGIN PUBLIC KEY-----\n"+Base64.encodeToString(publicKeyBytes,
Base64.DEFAULT)+"-----END PUBLIC KEY-----";
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("publickey", pubkeystr));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
// Execute HTTP Post Request
HttpResponse response = httpClient.execute(httpPost);
return new BasicResponseHandler().handleResponse(response);
} catch (Exception e) {
Toast toast = Toast.makeText(asyncResult.getContext(),
"unknown exception occured: " + e.getMessage(),
Toast.LENGTH_SHORT);
toast.show();
return "error";
}
}
You are generating one RSA keypair in doInBackground and telling the host to use the public half of that keypair to encrypt the DEK (data encryption key). You are then generating a completely different RSA keypair in getData and attempting to use the private half of that keypair to decrypt the encrypted DEK. The way public-key encryption works is you encrypt with the public half of a keypair and decrypt with the private half of the same keypair; the public and private halves are mathematically related. You need to save and use at least the private half of the keypair (optionally the keypair with both halves) whose public half you send.
Once you've got the DEK correctly, in order to decrypt CBC-mode data, yes you do need to use the same IV for decryption as was used for encryption. Your receiver needs to put it in an IvParameterSpec and pass that on the Cipher.init(direction,key[,params]) call. Alternatively if you can change the PHP, since you are using a new DEK for each message it is safe to use a fixed IV; easiest is to encrypt with '\0'x16 and allow the Java decrypt to default to all-zero.
Additionally you need to set Base64.decode with the parameter Base64.NO_WRAPas PHP will just put out the base64 delimited by \0. And to that you will also need to use the "AES/CBC/ZeroBytePadding" transformation cipher to decrypt the AES data as the PHP function mycrypt_encrypt will pad the data with zeros.
Here is what the getData function will have to look like:
public String getData() {
String dataDecrypted = null;
try {
byte[] cryptionKeyCrypted = Base64.decode(cryptionKeyCryptedBase64, Base64.NO_WRAP);
byte[] cryptionIV = Base64.decode(cryptionIVBase64, Base64.NO_WRAP);
Cipher cipherRSA = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
// get private key from the pair used to grab the public key to send to the api
cipherRSA.init(Cipher.DECRYPT_MODE, rsaKeyPair.getPrivateKey());
byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);
byte[] dataCrytped = Base64.decode(dataCryptedBase64, Base64.NO_WRAP);
IvParameterSpec ivSpec = new IvParameterSpec(cryptionIV);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipherAES = Cipher.getInstance("AES/CBC/ZeroBytePadding");
cipherAES.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
byte[] decryptedAESBytes = cipherAES.doFinal(dataCrytped);
dataDecrypted = new String(decryptedAESBytes, "UTF-8");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return dataDecrypted;
}
I'm trying to implement digital signature in php as in java sample code below:
Signature rsaSig = Signature.getInstance("MD5withRSA");
RSAPrivateKey clientPrivateKey = readPrivateKeyFromFile(fileName);
rsaSig.initSign(clientPrivateKey);
String source = msg;
byte temp[] = source.getBytes();
rsaSig.update(temp);
byte sig[] = rsaSig.sign();
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(sig);
My php code :
$rsa = new Crypt_RSA();
$rsa->loadKey('...'); // in xml format
$plaintext = '...';
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$signature = $rsa->sign($plaintext);
But looks like some thing is missing. We should get same signature as java code returns.Can anybody guide me in this?
By default phpseclib uses sha1 as the hash. You probably need to do $rsa->setHash('md5').