PHP JWT Invalid signature - php

Given this code:
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
$key = 'secret';
//setting the header: 'alg' => 'HS256' indicates that this token is signed using HMAC-SHA256
$header = array(
'alg' => 'HS256',
'typ' => 'JWT'
);
// Returns the JSON representation of the header
$header = json_encode($header);
//encodes the $header with base64.
$header = base64url_encode($header);
$payload = array("a" => "b");
$payload = json_encode($payload);
$payload = base64url_encode($payload);
$signature = hash_hmac('SHA256','$header.$payload', $key, true);
$signature = base64url_encode($signature);
echo "$header.$payload.$signature";
Returns the following JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.rhCKIvkwiuNcchxDZnGak8XT1q8lmLhnm8aIxzUioWg
But the signature is not verified at https://jwt.io/
The payload is decrypted well though... What may be the problem ?

You are using single quotes around $header.payload when calculating the HMAC, rather than double quotes; the former uses the literal string and does not expand the variables:
$signature = hash_hmac('SHA256', "$header.$payload", $key, true);

Related

Epic on FHIR - PHP-JWT

I am trying to get the jwt token, but i am getting errors everytime with everything that i try. Below are th things that i have tried. I do get the jwt-token without the package but when i use the jwt.io to check the signature verification,it fails every time. With the package i get different errors like the private key cannot be coereced and sometimes invalid algorithm. Please correct me where i am messing it up.
Without php-Jwt
$header = [
'alg' => "RS384",
'typ' => "JWT"
];
$payload = [
'iss' => 'my-client-id',
'sub' => 'my-client-id',
'aud' => "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
'jti' => (string)strtotime(gmdate("Y-m-d H:i:s")),
'exp' => strtotime(gmdate("Y-m-d H:i:s")) + 270,
];
$privateKey = "my private Key"
$headers_encoded = $this->base64url_encode(json_encode($header));
$payload_encoded = $this->base64url_encode(json_encode($payload));
$signature = hash_hmac('sha384', $headers_encoded.'.'.$payload_encoded, $privateKey, true);
// Encode the signature as a base64url string
$signature_encoded = $this->base64url_encode($signature);
$jwt = $headers_encoded.'.'.$payload_encoded.'.'.$signature_encoded;
- With php-jwt
installed the package
composer require firebase/php-jwt --ignore-platform-req=ext-mongodb
Used the required files in my controller
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
Tried Encoding:
$check = JWT::encode(
$header,
$payload,
$privateKey,
'RS384'
);
i get different errors like the private key cannot be coereced and sometimes invalid algorithm. Please correct me where i am messing it up.
In simple words, this is what I am doing or trying to do. A generic form of my code:
<?php
// Load the private key from a file
$privateKey = file_get_contents('private.key');
// Set the header and payload for the JWT
$header = [
'alg' => 'RS384',
'typ' => 'JWT'
];
$payload = [
'sub' => '1234567890',
'name' => 'John Doe',
'iat' => 1516239022
];
// Encode the header and payload as JSON strings
$headerEncoded = base64_encode(json_encode($header));
$payloadEncoded = base64_encode(json_encode($payload));
// Concatenate the header, payload, and secret to create the signature
$signature = hash_hmac('sha384', "$headerEncoded.$payloadEncoded", $privateKey, true);
// Encode the signature as a base64 string
$signatureEncoded = base64_encode($signature);
// Concatenate the header, payload, and signature to create the JWT
$jwt = "$headerEncoded.$payloadEncoded.$signatureEncoded";
echo $jwt;
I do get the jwt-signature but on https://jwt.io/ it shows unverified.
Fixed
Use this instead of hash_mac():
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA384);
Make sure if the key is stored in PHP variable, format the key like this:
$privateKey = "------BEGIN PRIVATE KEY------\n".
"MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj\n".
"MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu\n".
.
.
.
"TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc\n".
"dn/RsYEONbwQSjIfMPkvxF+8HQ==\n".
"------END PRIVATE KEY------";
enclose every line in Double quotes "" and add \n at end of every line.

Generate an HS512 JWT in PHP without libraries?

I'm close but jwt.io doesn't like the signature I generate. With this code I generate the following JWT. If this ain't the way, how should I be generating a JWT in PHP if I can't use external libraries?
function gen_jwt():String{
$signing_key = "changeme";
// header always eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9
$header = [
"alg" => "HS512",
"typ" => "JWT"
];
$header = base64_url_encode(json_encode($header));
log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') json base64_url_encode: ' . $header);
// test case 0 generates (on jwt.io):
// with secret base64 encoded: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjB9.ZW0gOCJV4e1KgGEsw0bL7oCF1AI1PBL8VVgSoss4tmr7682p6DpNc1uGbBpOEfkPjKJv0JBnLvjH2XUbo8PHUg
// without secret b64 encoded: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjB9.pqzfdCTmr-eWW9sEgV-COCdS4dI7MDpCIFWss6kXnAC9eLdGX1qOOr8BtJih59o_U_AdHtBh8JwUQ4dEPTk0rg
$payload = [
// "exp" => time() + ...,
"exp" => 0,
];
$payload = base64_url_encode(json_encode($payload));
$signature = hash_hmac('sha512', "$header.$payload", $signing_key, false);
log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') signature: ' . $signature);
$signature = base64_url_encode($signature);
log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') signature: ' . $signature);
// all three parts b64 url-encoded
$jwt = "$header.$payload.$signature";
log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') jwt: ' . $jwt);
return $jwt;
}
/**
* per https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
*/
function base64_url_encode($text):String{
return str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode($text)
);
}
/**
* per https://www.uuidgenerator.net/dev-corner/php
*/
function guidv4($data = null): String {
// Generate 16 bytes (128 bits) of random data or use the data passed into the function.
$data = $data ?? random_bytes(16);
assert(strlen($data) == 16);
// Set version to 0100
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
// Set bits 6-7 to 10
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
// Output the 36 character UUID.
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
Comes out:
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjB9.ZmIxNzIyN2Q2ZjFhYjg3ZTJjMTY0NDJkNGQ4NzFlYWFmMjFhYzg1NzI5NGRkOGVhZmY4MTYzNWI1YTMyYWEyN2UxOTFmN2E5MzA1ZTZjZmI0OGVlZmMwN2U2NTc1MzNhZDg0NmMxMTZhZDZlOGVlYjJmMGVmOWUxOTMyYzE5MmE
...which jwt.io (and my own decoding efforts) say is an invalid signature. Help? Thanks!
I guess the trick was to base64-url-encode the binary output of the hmac like...
$signature = base64_url_encode(hash_hmac('sha512', "$header.$payload", $signing_key, true));
So the copy-paste-able code would be:
function gen_jwt():String{
$signing_key = "changeme";
$header = [
"alg" => "HS512",
"typ" => "JWT"
];
$header = base64_url_encode(json_encode($header));
$payload = [
"exp" => 0,
];
$payload = base64_url_encode(json_encode($payload));
$signature = base64_url_encode(hash_hmac('sha512', "$header.$payload", $signing_key, true));
$jwt = "$header.$payload.$signature";
return $jwt;
}
/**
* per https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
*/
function base64_url_encode($text):String{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($text));
}

PHP doing a SHA256 hash signature on a string

I am trying to sign a string with the secret key I have, but it keeps failing. I am new to this and are just starting to understand it a little bit (encryption and base64). But I have been stuck at this point for three days now and have pulled out all of my hairs. I just dont get it.
The bit from the api is:
MD5 Digest
MD5 algorithm is used to perform a signature operation on the final string to be signed, thereby obtaining a signature hex result string (this string is assigned to the parameter sign). In the MD5 calculation result, letters are all capitalized.
RSA Signature
Users could use their private key to perform a signature operation (Base64 coded) to a MD5 digest by implementing SHA256 algorithm through RSA, after users getting the signature result string which is signed by MD5 algorithm.
All I have tried:
$echostr="SS...AD";
$api_key="17...e0";
$secret="EB....D";
$parameters="api_key=".$api_key."&echostr=".$echostr."&signature_method=HmacSHA256&timestamp=".$timeStamp;
$preparedStr=base64_encode(strtoupper(md5($parameters)));
$sign=rsa_hash_sign($preparedStr, $secret);
echo "1 ".$sign."<\br>";
$sign=HmacSHA256_Sign($preparedStr, $secret);
echo "2 ".$sign."<\br>";
$sign=GenerateDigest($secret);
echo "3 ".$sign."<\br>";
$binaryKey = decode($secret);
$sign=encode(hash_hmac("sha256", $preparedStr, $binaryKey, true));
echo "4 ".$sign."<\br>";
$sign=hash_hmac("sha256", $preparedStr, $binaryKey, true);
echo "5 ".$sign."<\br>";
$sign=base64_encode(hash_hmac('sha256', base64_encode($preparedStr), $secret,true));
echo "6 ".$sign."<br>";
$cURLConnection = curl_init('...');
curl_setopt($cURLConnection, CURLOPT_POSTFIELDS, array(
'sign' => $sign,
'api_key' => $api_key
));
curl_setopt($cURLConnection, CURLOPT_HTTPHEADER, array(
'contentType: application/x-www-form-urlencoded',
'timestamp: '.$timeStamp,
'signature_method: HmacSHA256',//RSA
'echostr: '.$echostr
));
curl_setopt($cURLConnection, CURLOPT_RETURNTRANSFER, true);
$apiResponse = curl_exec($cURLConnection);
curl_close($cURLConnection);
var_dump($apiResponse);
function encode($data) {
return str_replace(['+', '/'], ['-', '_'], base64_encode($data));
}
function decode($data) {
return base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
}
function GenerateDigest($requestPayload)
{
$utf8EncodedString = utf8_encode($requestPayload);
$digestEncode = hash("sha256", $utf8EncodedString, true);
return base64_encode($digestEncode);
}
function rsa_hash_sign($package, $privateKey) {
$rsa = new Crypt_RSA();
$rsa->setHash("sha256");
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$rsa->loadKey($privateKey);
$hash = hash('sha256', $package, true);
$signature = $rsa->sign($hash);
$hexData = bin2hex($signature);
$base64 = base64_encode($hexData);
return $base64;
}
function HmacSHA256_Sign($preparedStr, $secret)
{
signature result string which is signed by MD5 algorithm.
$hash = "";
$rsa = new Crypt_RSA();
$rsa->setHash('sha256');
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$rsa->loadKey($secret);
return $rsa->encrypt($secret);
}
But every $sign I try is giving me "Secret key does not exist" back as error. This is my first time trying anything with encryption and I am so lost and dont know how to solve this.

How to generate JWT in PHP

How to generate JWT token in php using with the following parameters
Subject, Issuer, Expiry time and payload in the < PAYLOAD > tag.
Id can be any random number of any length.
subject is TestService
Issuer is Baguma Inc
Expiry Time will be 30 sec from current time(ideally ).
Payload is the request from Third Party
SigningKEY is fcvxcnfrhrtghkfghgwerikdf
Signature algorithm will be HS512.
Sample request from Third Party is shown below
<COMMAND><TYPE>REQUEST</TYPE><INTERFACE>TESTACCOUNT</INTERFACE> <REQUESTID>123</REQUESTID></COMMAND
Your answer got me started. Here's the working code I went with (albeit generalized from your specific case). Thanks for getting me started!
function gen_jwt():String{
$signing_key = "changeme";
$header = [
"alg" => "HS512",
"typ" => "JWT"
];
$header = base64_url_encode(json_encode($header));
$payload = [
"exp" => 0,
];
$payload = base64_url_encode(json_encode($payload));
$signature = base64_url_encode(hash_hmac('sha512', "$header.$payload", $signing_key, true));
$jwt = "$header.$payload.$signature";
return $jwt;
}
/**
* per https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
*/
function base64_url_encode($text):String{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($text));
}
With the help of an article from DZone Security, I managed to generate a JWT token by doing the following
Define the base64UrlEncode function which replaces + with -, / with _ and = with ''.
function base64UrlEncode($text)
{
return str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode($text)
);
}
Encode the headers using base64UrlEncode
$headers = [ "alg" => "HS512"];
$headers_encoded = $this->base64url_encode(json_encode($headers));
Encode the Payload using Base64 URL encode as well
$issuedAt = time();
$payload = [
"id" =>$this->gen_uuid(), // .setId(UUID.randomUUID().toString())
"sub"=> "TestService", //Subject
"exp"=> $issuedAt+30,
"iss"=> "Baguma Inc", //issuer
"iat"=> $issuedAt, //issued at
"PAYLOAD"=> "<COMMAND><TYPE>REQUEST</TYPE><INTERFACE>TESTACCOUNT</INTERFACE> <REQUESTID>123</REQUESTID></COMMAND"];
$payload_encoded = $this->base64url_encode(json_encode($payload));
Using the Key/secret build the signature
$key = "fcvxcnfrhrtghkfghgwerikdf"
$signature = hash_hmac('sha512',"$headers_encoded.$payload_encoded",$key,true);
Build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";

Google Drive Auth: Creating JWT signature from JSON version of private key

I am having trouble creating a JWT for Google Drive authorization.
The instruction is given in this link.
<?php
//helper function
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
//Google's Documentation of Creating a JWT https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests
$raw_key_data="-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkWhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCLJN7B2USp0bTH\n7aVt1K/pAJa9rgUEkebbRQUPEopManZHgJK8SMzq5nUM3OOVlTdyDyuzxUZW65Y+\nR1xajNHSidE+rJJ3MWwuevXBuUws5oiA+DXJBB6FsTtHDvUPT7sePbJx8Y80S1Jr\nB3apJNdohjSef4UihgjJ93A0PgeSVz7CeUKxrBHdGiK2Ikf7wTCzcRzz/15I6QLu\ncsNbWxo0+koXo9D0+wT3RN+riSyTa2WwYn//B8v6VmDPVrcjxETEXraAIMpqURtK\n4BuK4Qp2pSd1u2zrGc58BVGuT6+nPVpmX6wiZ21sqOulTzgIOXKIuc4zdKvZ+jz/\n1jst3ertAgMBAAECggEADRGn+IYbLGYdeD/Kb2/wG87p2aP8Jas8hzDK4lkH81h2\nho29eoDN+mwt50jh+V08CXMCVE69phFXmb7jHkAmvwMhy6Sy1w4lzpHO/mSUko0O\nmip2BszjvwPgAPMXMlp3RUZfOdOJ80v10Eaxrv5eWxtr2s04aH81WR7sA4Ql+uki\nbTiLbo4odpMVPkoJZATZKQd+L/bBM4a+b3IM87/TE7skMrSgQE6cnjjTI5Uk+WSl\nxjiZvj5XmticW3vPavL/ZXPEZqy5IxvcxdF5rGHCHVu4ah0CmDdc9A3jF0flhewK\nV6mOViqbKGInnMn4kt3l4C3+wF2+dge6t8BQ/TMtwwKBgQDBnpjiC+wRVQy36KCs\ntYdZ60ykuQeKqMyACt6FvC74xy+PjLVntxDaYArva2PVaZSm9B2FgGHPz9AfW80/\n1ZeNHcuwymni8n+4MNpQbJuhuHK8UjSo0rFq6Nrddi9RCjzASSX8s4Dlm01VM65b\nKBnM5p50I9QEY4F4gSZfLLMNowKBgQC3+Tpz23qhkxJPu9cVuvPRFfg1y0NNsp7k\n0qHYMegz1GHoMVs1kg36OtbROqpG2+rIrj0JWTGwLO14fX2TY0jWfao21CGrpkXM\nlY1KSDIMuQv6pd5yh74oqvDDpZwKxxu/nmzcQbd1lN/nFkEW5g0b7e27UoCoovwS\n7qSENbqOLwKBgHYWp8H+aX1stPQZ8p1DngiupTE2FK5yIz/Y4T0JuFBNE+nmdOGL\n2sCFoUXC5sHLwjlNXBAHbCCV66akk/th5yvPR2NNIOWk51bMnOo+Q3GQEJJhRPLO\nhhzhZlN5+IPhzYmtU3jbdjsTzEex3J6GR64b3fqRu4bttZJsmp2jopUnAoGAWW+7\nzt7/+tR4rnJu2Y2NQjQf+mbaTUddb2kWbPe2Hpw9DJgR8zURvngkPor6hIAc33p1\nCbpmwXLV7yFyjthRbJSizwzJYZzvicmaamY2jqDXBf7k6WC8PSD88t/rwAGTp8/o\ntBruiSbawoi7E9q6vL0qOUqeaVzylnGVYQCNtNkCgYBwqL1MNTR8IrXDfZyYdDRP\nWNCRqm7ymuQi7IUKVa+yaBM1ubvEe7EPrlZWlFPDdPmaScx02Qwf++xcGHWzzDX0\nQPmd95OTGafvECXuKVy2NAf2AdCYVruL+17wfPhuz7ANIpgEqsiNAZNe0GtGBjyZ\nVuiSVVML3jW4XUtf63V0/A==\n-----END PRIVATE KEY-----\n";
$private_key = openssl_get_privatekey($raw_key_data);
if ($private_key === FALSE)
{
error_log ("Unable to extract private key from raw key data: " . openssl_error_string());
};
print("$private_key\n");
//{Base64url encoded JSON header}
$jwtHeader = base64url_encode(json_encode(array(
"alg" => "RS256",
"typ" => "JWT"
)));
//{Base64url encoded JSON claim set}
$now = time();
$jwtClaim = base64url_encode(json_encode(array(
"iss" => "myService-account-id#mySite.iam.gserviceaccount.com",
"scope" => "https://www.googleapis.com/auth/drive.file",
"aud" => "https://www.googleapis.com/oauth2/v4/token",
"exp" => $now + 3600,
"iat" => $now
)));
//The base string for the signature: {Base64url encoded JSON header}. {Base64url encoded JSON claim set}
openssl_sign(
$jwtHeader.".".$jwtClaim,
$jwtSig,
$private_key,
"SHA256"
);
$jwtSign = base64url_encode($jwtSig);
//{Base64url encoded JSON header}.{Base64url encoded JSON claim set}. {Base64url encoded signature}
$jwtAssertion = $jwtHeader.".".$jwtClaim.".".$jwtSig;
print("$jwtAssertion\n");
?>
When I run this code, I get Resource id #4 followed by a nonsense signature. I even tried putting the private key in a file and reading from the file in which there is multiple lines (private key separated at \n ) but no success. I can't generate meaningful JWT signature from the private key. Can anyone help me with this issue?
Thanks,
I have managed to solve this issue. Below is the updated code that reads the JSON file downloaded from Google and creates an assertion with which one can then get access token from Google:
<?php
//helper function
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
// Read the JSON credential file my-private-key.json download from Google
$private_key_file="my-private-key.json";
$json_file = file_get_contents($private_key_file);
$info = json_decode($json_file);
$private_key = $info->{'private_key'};
//{Base64url encoded JSON header}
$jwtHeader = base64url_encode(json_encode(array(
"alg" => "RS256",
"typ" => "JWT"
)));
//{Base64url encoded JSON claim set}
$now = time();
$jwtClaim = base64url_encode(json_encode(array(
"iss" => $info->{'client_email'},
"scope" => "https://www.googleapis.com/auth/drive.file",
"aud" => "https://www.googleapis.com/oauth2/v4/token",
"exp" => $now + 3600,
"iat" => $now
)));
$data = $jwtHeader.".".$jwtClaim;
// Signature
$Sig = '';
openssl_sign($data,$Sig,$private_key,'SHA256');
$jwtSign = base64url_encode( $Sig );
//{Base64url encoded JSON header}.{Base64url encoded JSON claim set}.{Base64url encoded signature}
$jwtAssertion = $data.".".$jwtSign;
echo "$jwtAssertion\n";
You may then test your code by communicating with Google Drive for example:
curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=YOUR-JWT-ASSERTION-BUILD-ABOVE' https://www.googleapis.com/oauth2/v4/token

Categories