Creating a JWT With ECDSA P-256 SHA-256 With PHP - php

I'm wondering if it's possible to properly create a signature using the P256 curve and PHP. OpenSSL in PHP has support for creating the key and getting the proper things in order.
According to this documentation - http://self-issued.info/docs/draft-jones-json-web-token-01.html#DefiningECDSA - Section 8.3 states:
A JWT is signed with an ECDSA P-256 SHA-256 signature as follows:
Generate a digital signature of the UTF-8 representation of the JWT Signing Input using ECDSA P-256 SHA-256 with the desired private key. The output will be the EC point (R, S), where R and S are unsigned integers.
Turn R and S into byte arrays in big endian order. Each array will be 32 bytes long.
Concatenate the two byte arrays in the order R and then S.
Base64url encode the 64 byte array as defined in this specification.
Herein the problem lies with getting the R and S byte arrays.
Here is an example of what I'm trying to do.
//Create Array Configuration
$config = array(
"curve_name" => "prime256v1",
"private_key_type" => OPENSSL_KEYTYPE_EC,
);
$ourkey = openssl_pkey_new($config);
/* We would get the key details to help extract out other information such as x/y coord
of curve and private key but it's not necessary to show for this part*/
// Extract the private key from $res to $privKey
openssl_pkey_export($ourkey, $privKey);
$data = "Example data we will be using to sign";
$data = hash("sha256", $data);
$signature = "";
openssl_sign($data, $signature, $privKey); // Should I include the digest algo in this call as well?
The problem here is that this signature is not R and S that I can use to concatenate together to make the real signature I need... I think.
So ultimately, is there any way I can get the R and S values from a openssl function in php?
Any help is greatly appreciated!

If you need something like JWT but not JWT in particular, consider PASETO.
Assuming you opt for v2, PASETO doesn't use ECDSA, it uses the more secure EdDSA over Ed25519. Furthermore, the standard goes out of its way to be boring so that implementations will be obviously secure.
(If you opt for v1, PASETO uses RSASSA-PSS.)
If you do need JWT in particular, look no further than lcobucci/jwt, which in turn uses PHPECC for ECDSA.
Once upon a time, PHPECC was riddled with side-channel vulnerabilities which made it a bad choice to use in production systems. These days, it's about as secure as a PHP implementation of ECDSA could be. I wrote a usability wrapper called easy-ecc if anyone wants to use PHPECC in only the safest configuration.
Luís Cobucci's JWT library is the only PHP implementation of JWT that has, to my knowledge, been formally audited by a security company. The report is public and lives here.

these functions have helped me to encode and decode signatures generated with curve, i hope this help.
to encode:
function encode_signature() {
$data = "HOLA";
$private_key = <<<EOD
-----BEGIN PRIVATE KEY-----
MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDKuYJPma+sA2svl02CPCJECESuBrW2nExuUR1vtHgozw==
-----END PRIVATE KEY-----
EOD;
$binary_signature = "";
openssl_sign($data, $binary_signature, $private_key, OPENSSL_ALGO_SHA256);
$valid_signature = base64_encode($binary_signature);
return $valid_signature;
}
to decode:
function decode_signature($signature) {
$data = "HOLA";
$public_key = <<<EOD
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/kmRxyn5LUQdC3owkWDp6DkLnTlBN2VPd86FS5WAqQJA4y9oaowYYVSu0A7kv8tUa9FLAqb7UPfUNeh5zDnrFQ==
-----END PUBLIC KEY-----
EOD;
$publicKey = openssl_pkey_get_public($public_key);
$verify = openssl_verify($data, base64_decode($signature), $publicKey, OPENSSL_ALGO_SHA256);
return (bool) $verify;
}

Related

How to create signature using PHP

I need to use an API but first to login I need to create signature.
1. Concatenate the API key with the current timestamp in the format below:
<<APIKEY>>_<<timestamp(yyyy'-'MM'-'ddTHH:mm:ss.fffZ)>>
and this step is easy:
hash('sha256', $data);
result is:
9952375a30708b46739986482303cae30ad51fc9a362b5794d298dfc22f7ec02
and this is correct result
The next step is:
2. The combination of the created signature along with the provided API secret key will act as the
digital signature of the call.
I have API secret key like:
-----BEGIN PUBLIC KEY-----
9IGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCgBSU90PX4WyXFAZ/+M84dJNEi
/0j5OermfydTU4g2JvnpO6BOQjNpb5+mOLjVoij7DWTdDtx1WThRm04N3DVuyh+a
5cledvMbcngvyiXpQCdskT9bVmI4QLbmRny46S7MER1jhziMQRfRw9gbmlB2iCEq
n21kDr842Q+WDtLE4QIDAQA9
-----END PUBLIC KEY-----
How I can get Digital signature with a combination of created signature and provided API secret key?
There is an Python example like:
key = api_key + '_' + timestamp
print "message", key
sha_hash = hashlib.sha256(key).hexdigest()
print "sha256 hash:", sha_hash
rsa_key = RSA.importKey(pub_key)
cipher = PKCS1_v1_5.new(rsa_key)
signature = base64.encodestring(cipher.encrypt(sha_hash))
but how I can get signature using PHP?
While there are numerous ways to accomplish this, I recommend leveraging the openssl_public_encrypt method. There are other crypt functions and even pure PHP implementations of RSA but they are likely not as current and well maintained as openSSL is going to be on a linux system. Don't forget all the disruption in SSL/TLS these last years. Everything from retiring older protocols and weaker cyphers to POODLE type exploits.
If for whatever reason that is not an option I would probably look into phpseclib. https://github.com/phpseclib/phpseclib
Really might come down to what makes the most sense for your project... OS, portability, speed, etc.
Here is that Python snippet converted to equivalent PHP code.
<?php
$key = $api_key . '_' . $timestamp;
echo "message:" . $key;
$sha_hash = hash('sha256', $key);
echo "sha256 hash:" . $sha_hash;
$rsa_key = "your public key goes in here"; //see https://www.php.net/manual/en/function.openssl-pkey-get-public.php
openssl_public_encrypt($sha_hash, $encrypted, $rsa_key);
$signature = base64_encode($encrypted);

python string format pkcs8 RSA signature

I need to use pkcs8 private key in my project as example below, I can't found any library or method relate to pkcs8 signature only pkcs1 are available for the Crypto.Signature.
My python code example:
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
privatekey='''-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOuxJs5RD6s2ECEV
C38Jfj6xBBrcX6W1Qy+GLiXhh+wKj650BPSerQNQI+XPx/+uUemk/dJM3RMm75Di
mXJlp7/5LY11VyKSqS+x6ELIvWz8lo2OhHW6/wU4jiQ1MlburYbn/pRRClGGA8i4
FjHMvMu9/QCNPlNTlf7qJDO6lDVlAgMBAAECgYB0iP76/DGXIgAPm2w3v+Xf8X5q
GZRhRqKVmO6wZDbkisRIKa1ZlitNfA6DzpzA2tw9fgrSNJcKpTHGnYPpgEHUQrcw
t471yuAvzAOBffaqVjIt7vKyL3QnkNfxWenliasacX3olwBpKPrkvG75H2KBe9S0
WKpju1Cm6MebAJ3z8QJBAP8gSx3l9nNTqJcBBmP+TGDcTL9Sif2nBZrU/pWnzzop
HolaFIQTYXPIj8rMoVT/c0gLo1gcdHoSUJTbB0WO7V8CQQDsf9FAWCI3fkWgS/fW
21ApO6B07/388CpNHOTjvfsdlent4yXLL+Guh5apMH4Oq0tkGkEij5YPG1EZSYXi
48+7AkEAiX3m3ZMMMXTZe5/CyOrIUL8I4WbjFP8JJzs4hICuTmLQoScZvWAQeeyR
ibKkE4GjqCUVf6u+Hfd20/ICRjtTswJANCjEh8JoWYDZ7k6S7KoV9eIWs3OyurRl
P/idarUdyxqjKzorvbJjvdBdpBbz1lxlFkDMGMk+OTq3GjKi+rVvvQJAGhBieR2Q
69JNeoOg7J8Xk7evNBJHwtJfRDQXKJpN5YZsKclf6oRaYFEQOM6ThuNoYHISfGKw
UbDoUsPZoQFPtQ==
-----END PRIVATE KEY-----'''
signstr = testdata123
key = RSA.importKey(privatekey)
h = SHA.new(signstr)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
sign = base64.b64encode(signature)
The signature generated by code above do not match the public key in the api system I am using.
For PHP side I am able to sign correctly. The working code for PHP as below:
$private_key= openssl_get_privatekey($private_key);
openssl_sign($signStr,$sign_info,$private_key,OPENSSL_ALGO_MD5);
$sign = base64_encode($sign_info);
Any advice on how to sign with RSA pkcs8 format private key using python is much appreciated.
Hash algorithm
Of course, it must be different, because you're using different hash algorithms.
Your Python code uses SHA-1 and your PHP code uses MD5. The default hash algorithm in PHP is OPENSSL_ALGO_SHA1 as the documentation describes:
if (!openssl_sign($signStr, $sign_info, $private_key)) {
echo "Failed to sign";
}
Key encoding
There are many ways of encoding a private key. Two of the most common ways are PKCS#1 and PKCS#8 encodings. See more information on that here.
The key encoding is not a concern here, because both pycrypto and openssl use the correct one here.
Padding
There are two common padding schemes that are used for signing: PKCS#1 v1.5 padding and RSA-PSS (technically RSASSA-PKCS1-V1_5 and RSASSA-PSS). Both of them are defined in PKCS#1, but the latter was introduced in PKCS#1 v2.x.
Pycrypto supports both padding schemes, but openssl only supports PKCS#1 v1.5 in PHP.
Compatibility
If you want to check for compatibility of the same algorithm between different languages, you need to sign in one using the private key and verify the signature in the other using the public key. After you've done that, you can try the other way around if you also need the other direction.
This is only relevant for RSA-PSS, because that is randomized.
If you're using PKCS#1 v1.5 padding which is not randomized, you should be able to create a signature in both Python and PHP and compare them directly to check for compatibility.

Avoid SHA1 hashing in openssl_sign / sign given hash

I'm working on replacing a legacy system that (among other things) receives SHA1 hashes of arbitrary files and signs them using a private key with a simple PHP web service.
It should look something like that:
$providedInput = '13A0227580C5DE137C2EBB2907A3F2D7F00CA71D';
// pseudo "= sha1(somefile.txt); file not available server side!
$expectedOutput = 'DBC9CC4CB0BECEE313BB100DD1AD39AEC045714D72767211FD574E3E3546EB55E77D2EBFE33BA2974BB74CE051608BFF45A73A52612C5FC418DD3A76CAC0AE0C8FB3FC6CE4F7A516013A9743A36424DDACFE889B3D45E86E6853FD9A55B5B4F0F0D8A574A0B244C0946A99B81CCBD1A7AF7C11072745B11C06AD680BE8AC4CB4';
// pseudo: "= openssl_sign(file_get_contents(somefile.txt), signature, privateKeID);
For the sake of simplicity I'm using PHP's built in openssl extention. The problem I'm running into is that openssl_sign seems to SHA1 hash the input data again internally according to this German manual entry on openssl_sign. The English entry is missing that info for some reason.
This produces the expected output ...
$privateKeyID = openssl_get_privatekey(file_get_contents($privateKey));
openssl_sign(file_get_contents("x.txt"), $signature, $privateKeyID);
var_dump(bin2hex($signature));
... but since I don't have access to the actual input files on server side it's not very helpful.
Is there a way around the additional hashing without 3rd party libs? I already tried to simply encrypt the hash received, but from How to compute RSA-SHA1(sha1WithRSAEncryption) value I understand encrypting and signing produce different output.
Update to make things more clear:
I'm recieving an SHA1 hash as input and the service has to convert it to a valid signature (using a private key) that can simply be verified using openssl_verify. The clients are out of reach, so changing their implementation is not possible.
From How to compute RSA-SHA1(sha1WithRSAEncryption) value:
If you reproduce this EM and use RSA_private_encrypt, then you will get the correct PKCS#1 v1.5 signature encoding, the same you would get with RSA_sign or even better, using the generic EVP_PKEY_sign.
I figured I could simply implement the DER encoding myself according to this specification, but the result (EM) seems too long to be encrypted with my key
// 1. Apply the hash function to the message M to produce a hash value H
$H = hex2bin($input); // web service receives sha1 hash of an arbitrary file as input
$emLen = 128; // 1024 rsa key
// 2. Encode the algorithm ID for the hash function and the hash value into
// an ASN.1 value of type DigestInfo
$algorithmIdentifier = pack('H*', '3021300906052b0e03021a05000414');
$digest = $H;
$digestInfo = $algorithmIdentifier.$digest;
$tLen = strlen($digestInfo);
// 3. error checks omitted ...
// 4. Generate an octet string PS consisting of emLen - tLen - 3 octets
// with hexadecimal value 0xff. The length of PS will be at least 8
// octets.
$ps = str_repeat(chr(0xFF), $emLen - $tLen - 3);
//5. Concatenate PS, the DER encoding T, and other padding to form the
// encoded message EM as
$em = "\0\1$ps\0$digestInfo";
if(!openssl_private_encrypt($em, $signature, $privateKeyID)) {
echo openssl_error_string();
}
else {
echo bin2hex($signature);
}
Output:
Error:0406C06E:rsa routines:RSA_padding_add_PKCS1_type_1:data too large for key size
Any hints?
UPDATE
As you can see in code below openssl_verify return 1 for result of openssl_sign and even for openssl_private_encrypt result. I tested it on my machine. This solution will work only if sha1 digest in digital signature is used.
// Content of file
$data = 'content of file somewhere far away';
// SHA1 hash from file - input data
$digest = hash('sha1', $data);
// private and public keys used for signing
$private_key = openssl_pkey_get_private('file://mykey.pem');
$public_key = openssl_pkey_get_public('file://mykey.pub');
// Encoded ASN1 structure for encryption
$der = pack('H*', '3021300906052b0e03021a05000414') . pack('H*', $digest);
// Signature without openssl_sign()
openssl_private_encrypt($der, $signature, $private_key);
// Signature with openssl_sign (from original data)
openssl_sign($data, $opensslSignature, $private_key);
// Verifying - both should return 1
var_dump(openssl_verify($data, $signature, $public_key));
var_dump(openssl_verify($data, $opensslSignature, $public_key));
I just captured DER encoded structure by decrypting openssl_sign() result.
ORIGINAL ANSWER
openssl_sign() creates digest from data because this is how digital signature works. Digital signature is always encrypted digest from data.
You can use openssl_private_encrypt() and openssl_public_decrypt() on your sha1 digest with no fear. In general, it is the same thing but yes, there is a difference. If you encrypt something on your own, the encryption process does not care about data and just encrypts them. It is on you to know that what you will decrypt later is sha1 digest for some data. In fact, it is just data encryption with private key, not true digital signature.
openssl_sign() creates digest from data and encrypts information about kind of digest and digest itself (this is ASN.1 DER structure from your link). This is because openssl_verify() needs to know what kind of digest was used when signing.
According to the English page of openssl_sign:
bool openssl_sign ( string $data , string &$signature , mixed $priv_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ] )
I think the obvious suggestion is to use OPENSSL_ALGO_SHA256. See openssl_get_md_methods for a list of the supported algorithms.

using phpseclib's Crypt_RSA to encrypt verification code for a bank

I am required to send a query to the bank which contains a verification code $vk_mac in a specified string format. The code has to be a SHA1 hash and RSA encrypted with my public key and presented in base64 format. Unfortunately, so far, I have been unsuccessful - the bank gives me "Wrong signature" and that all the info I'm getting.
What I have is this:
$rsa = new Crypt_RSA();
$rsa->loadKey(file_get_contents("private_key.pem"));
$rsa->loadKey($rsa->getPublicKey());
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$encrypted = $rsa->encrypt(sha1($vk_mac));
$vk_mac = base64_encode($encrypted);
private_key.pem here is my private key in plain text. I tried setting the encryption mode to CRYPT_RSA_ENCRYPTION_OAEP without luck. I am 99.9% sure, that the starting $vk_mac string is formatted correctly and contains all the required details.
Does anybody have any idea what can I be doing wrong? Thank you.
Edit:
I've changed the code to this (where vk_mac is the starting formatted string that needs to be signed and private_key.pem is my the decoded private key):
$rsa = new Crypt_RSA();
$rsa->loadKey(file_get_contents("private_key.pem"));
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$hashed = $rsa->hash->hash($vk_mac);
$encrypted = $rsa->sign($hashed);
$signature = base64_encode($encrypted);
I can tell the generated signature is correct, since when I do this:
$rsa->loadKey($rsa->getPublicKey());
$verified = $rsa->verify($hashed, base64_decode($signature));
$verified returns TRUE.
The bank though, responds "Incorrect signature". Any more ideas?
Edit:
Specification
VK_MAC control code calculation
VK_MAC, for electronic signature, using in the request, for checking and confirming used version of the algorithm, indicated in the parameter VK_VERSION. In this time version 008 is used. VK_MAC is presented as a request parameter in BASE64 coding.
Version 008
The value of the MAC008 function is calculated using the public key algorithm RSA. Values of empty fields are taken into account as well – “000”.
MAC008(x1,x2,…,xn) := RSA(SHA-1(p(x1)|| x1|| p(x2 )|| x2 || … ||p(xn)||xn),d,n)
Where:
|| is an operation of adding the string
x1, x2, …, xn are the query parameters
p is a function of the parameter length. The length is a number in the form of a three-digit string
d is the RSA secret exponent
n is the RSA modulus
The signature is calculated in accordance with the PKCS1 standard (RFC 2437).
What if you try $rsa->sign()? PKCS#1 doesn't do signing by simply encrypting the hash and if your bank is using an interoperable RSA solution they're probably not doing that either.
The code was almost correct - I did not need to hash it again though (thanks #Accipitridae).
The solution was that the merchant's ID had to be uppercase, and not lowercase as provided. It does not say anywhere in the spec that it has to be uppercase as well. Nice.
As mentioned above you can do this easily with openssl. Below is how I would do so.
$hashed = sha1($vk_mac);
openssl_public_encrypt($vk_mac, $encrypted, ($pubkey));
$vk_mac = base6$_encode($encrypted);
Read the documentation on openssl_public_encrypt for more.

signing using RSA public key algorithm

I am planning to use php for generating serial number for my software which is written using MSVC++. The method i am optiong is hashing the information and sign it using Private key. In MSVC++ i am able to verify a hash signed by RSA public key algorithm using native API's. Is it possible to sign a hash using RSA public key algorithm in php.
Please advice.
Regards,
John.
Try phpseclib, a pure PHP RSA implementation:
<?php
include('Crypt/RSA.php');
$rsa = new Crypt_RSA();
extract($rsa->createKey());
$plaintext = 'terrafrost';
$rsa->loadKey($privatekey);
$signature = $rsa->sign($plaintext);
$rsa->loadKey($publickey);
echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified';
?>
The problem with the solution poelinca proposed is that it's not PKCS#1 compliant (which means, among other things, that it's very likely not going to be interoperable with MSVC++), it doesn't work with standardized key formats and it doesn't employ RSA blinding (meaning one can use timing attacks to figure out the private key).
I don't understand what you're saying with "sign a hash" , however would Encrypt and decrypt data with RSA public keys be of any help ?

Categories