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);
Related
I have a private key file $formatPrivateKey that I need to use as a variable
$privateKey = file_get_contents('27660275_website.com.key');
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $privateKey, true);
I need this to generate a client-assertion JWT.
However, I'm getting the following error - {"error":"invalid_request","error_description":"The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA"}
I guess I'm not reading the file_get_contents('27660275_website.com.key') properly.
My $privateKey file starts with the following -----BEGIN RSA PRIVATE KEY----- and finishes with -----END RSA PRIVATE KEY----. I tried to remove them but I still got the same output.
I tried to concatenate the alphanumeric sequence as suggested in another answer to a question in Stackoverflow. However, it didn't work.
Therefore my assumption that I'm not reading file_get_contents('27660275_website.com.key') properly.
In this context, the secret key is a password (a string) rather than a private key file.
Check out this for example, they simply use the password 'secret'. Any string should make your code work.
https://www.php.net/manual/en/function.hash-hmac.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;
}
I'm trying to connect to a api in my python app .
so the api documentations comes with php and asp sample code but no python
I'm pretty good with php but have no experience with encryption ... I'm trying to re-write python code for the api using php sample .
They use this class for RSA
https://github.com/AlaFalaki/Pclass/blob/master/libraries/rsa.class.php
(Since its a RSA lib im guessing python RSA lib would take care of this part ) :
static function rsa_sign($message, $private_key, $modulus, $keylength) {
$padded = RSA::add_PKCS1_padding($message, false, $keylength / 8);
$number = RSA::binary_to_number($padded);
$signed = RSA::pow_mod($number, $private_key, $modulus);
$result = RSA::number_to_binary($signed, $keylength / 8);
return $result;
}
Here is the problem php sign function takes 4 arguments uses some internal functions ... But python rsa has 3 and one of them is just the hash method !
rsa.sign(message, priv_key, hash)
Parameters:
message – the message to sign. Can be an 8-bit string or a file-like object. If message has a read() method, it is assumed to be a file-like object.
priv_key – the rsa.PrivateKey to sign with
hash – the hash method used on the message. Use ‘MD5’, ‘SHA-1’, ‘SHA-256’, ‘SHA-384’ or ‘SHA-512’.
i've tried little experiment to see if i get same output
so i've singed a simple text string in php
echo base64_encode(RSA::rsa_sign(sha1("test"),$private_key,$modulus,$key_length));
i got
something like
dKt+4CocMNdIrtYCUr8aZykR8CpfmYUEEVONMuAPlM5mR70AoyzMhGjcEGB9fKLVC4rr5xt66w2ZmHqWO+p834rJmo9Fj57udRSY5wFs0VokMF2S2SMFn5WTYYmMBuWciRzZybWnfXcSIyp9Ibi28cdwl5hXJOMpXEJrNQLFy2s=
next i extracted private_key , public_key , modulus from a xml file that they gave me with api containing my keys ( using the same RSA class ) like
$xmlObj = simplexml_load_string($xmlRsakey);
$this->modulus = RSA::binary_to_number(base64_decode($xmlObj->Modulus));
$this->public_key = RSA::binary_to_number(base64_decode($xmlObj->Exponent));
$this->private_key = RSA::binary_to_number(base64_decode($xmlObj->D));
$this->key_length = strlen(base64_decode($xmlObj->Modulus))*8;
i made a python dictionary with them
def keys():
obj = {
'modulus' : "14417185111734127374105962730273......." ,
'public_key' : "61111" ,
'private_key' : "3739752306322843055980611965983321761993....." ,
'key_length' : 1024 ,
}
return obj
and i've tried to sign a string in python
def sign(request):
api = keys()
message = 'test'
crypto = rsa.sign(message.encode('utf-8'), api['private_key'] , 'SHA-1')
b64 = base64.b64encode(crypto)
return HttpResponse(b64)
but i get :
'str' object has no attribute 'n'
and that was my failed experiment
As i said i dont have any experience with encryption or rsa .... i want some advice from someone who worked with this stuff .
Should i give up and use php to encrypt/decrypt ?
They use this class for RSA
https://github.com/AlaFalaki/Pclass/blob/master/libraries/rsa.class.php
My advice: Run away screaming.
RSA is a mine field of security issues. There are a lot of things that you can screw up. So when someone implements the primitives in PHP using the BC extension, that's the security equivalent of standing naked in front of a firing squad and expecting to have no holes.
Encrypting with PKCS1 padding allows near-trivial message decryption
Screwing up your parameters can completely remove all security from your crypto
Home-grown RSA is ripe with side-channel attacks
Recommendation: Use Libsodium Instead
There are both PHP and Python bindings available for libsodium.
If RSA is Unavoidable...
If you really want RSA and not modern cryptography, check out phpseclib.
<?php
use
$rsa = new RSA();
// HIGHLY RECOMMENDED FOR SECURITY:
$rsa->setEncryptionMode(RSA::ENCRYPTION_OAEP);
$rsa->setMGFHash('sha256');
$rsa->loadKey($yourPEMencodedRSAPublicKey);
$ciphertext = $rsa->encrypt($plaintext);
If you're going to encrypt with RSA, you must follow these cryptography rules. If your library doesn't let you follow these rules, it's time to switch to libsodium.
Also note that encrypting large messages with RSA is both slow and dangerous: There's usually nothing preventing messages from being reordered, which can be really bad.
The solution here is: Use symmetric-key authenticated encryption and use RSA to encrypt the public key. That's what EasyRSA does, although I'm not aware of any Python equivalents, so I can't recommend that as a solution.
Of course, if you use libsodium's crypto_box API you don't have to worry about that!
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.
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.