T am trying to use HMAC to hash a data string but for some reason my PHP and Python scripts results are not consistent... I can't figure out why.
The one that is technically working is the Python script and although I get results from PHP script, the hex returned is incorrect.
UPDATE -- one thing i'v noticed is if i am using a single line for the data then the results are identical. The mismatch seems to occur when there is multi-line data.
Python demo script here
https://repl.it/BBDH/2
Result:
f54617eadc7c037b4ed484103bb21426994110a9
import hashlib
import hmac
import base64
key = b"M0u$t4fa#1Kh-Key"
csr = """-----BEGIN NEW CERTIFICATE REQUEST-----
MIICtzCCAZ8CAQAwcjELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIw
EAYDVQQHDAlNZWxib3VybmUxDzANBgNVBAoMBk1LVEVTVDELMAkGA1UECwwCSVQx
HjAcBgNVBAMMFWRuc3Rlc3QzLnNzbHRlY2hzLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAM1Ik2SS1F4NgizKXCqpiliR/c285HtcLKFD6OcJO6d6
v8n+1B7R1ovUqRyoM4qsLAVVHshvsuqxbD7sLmcEfh/akee+CGLqgSNSw913IBWL
WRtBRhVTd6gvTQY4KqpEgRShnua179Cbb/cQLsFhHhog/IfD0JWwdRWLqn3rzQcI
xzAsOkTJSarAt/QPS5fq1Hk978iQ0QAxtEssaX+0Xcq7ZyJGkHRyW3cBoynSQAYz
RNGBFTFB+z/qiAkKWwRs61cnKub9Grz6Adw931zuYICW0EaWdBGdc15cUkvI6RF4
xqhzmHBTZLQZDcP9vYrE/biVvX2GVNKpO1hd0i7iOTcCAwEAAaAAMA0GCSqGSIb3
DQEBCwUAA4IBAQBPmct14B0f7HkMar8Ogf1wgo7jXyFytW7tfj3exTsyBC/5ShGv
1Xx//H8I5ecb5N6EflyXmaFiWM4ybQduhVyKzNxlU8i5ug/msdpxQhj3rZ7WO6Xb
O8b5oj5e/8V1RmmsjC9dDFA/A8/JgAbrOn2CtCJrgRtl1LFBtaFRonfaRbuzcVSE
e1qdKoPY2UNK7cd3Hv/pkkorUJd89YREFZatyvU/b89fjNaPzjvtljxGadeIX5WO
7sQwMyHCSknWZPY4BYaiMf6jZ8TjXOCyIHQ3bdiDSiJlUEXvgz2yhF6Uue6aTvhR
Q85mPbtxXP+JXiZgSuT0Q6n7qN1b1mbZJgwk
-----END NEW CERTIFICATE REQUEST-----""".encode('utf-8')
hashed = hmac.new(key, csr, digestmod=hashlib.sha1).hexdigest()
print("HMAC (hex) =", hashed)
PHP demo script here
https://repl.it/BBDF/2
Result:
e978d0d10b814e486592ed608b5f16d095a9affa
$key = 'M0u$t4fa#1Kh-Key';
$data = '-----BEGIN NEW CERTIFICATE REQUEST-----
MIICtzCCAZ8CAQAwcjELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIw
EAYDVQQHDAlNZWxib3VybmUxDzANBgNVBAoMBk1LVEVTVDELMAkGA1UECwwCSVQx
HjAcBgNVBAMMFWRuc3Rlc3QzLnNzbHRlY2hzLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAM1Ik2SS1F4NgizKXCqpiliR/c285HtcLKFD6OcJO6d6
v8n+1B7R1ovUqRyoM4qsLAVVHshvsuqxbD7sLmcEfh/akee+CGLqgSNSw913IBWL
WRtBRhVTd6gvTQY4KqpEgRShnua179Cbb/cQLsFhHhog/IfD0JWwdRWLqn3rzQcI
xzAsOkTJSarAt/QPS5fq1Hk978iQ0QAxtEssaX+0Xcq7ZyJGkHRyW3cBoynSQAYz
RNGBFTFB+z/qiAkKWwRs61cnKub9Grz6Adw931zuYICW0EaWdBGdc15cUkvI6RF4
xqhzmHBTZLQZDcP9vYrE/biVvX2GVNKpO1hd0i7iOTcCAwEAAaAAMA0GCSqGSIb3
DQEBCwUAA4IBAQBPmct14B0f7HkMar8Ogf1wgo7jXyFytW7tfj3exTsyBC/5ShGv
1Xx//H8I5ecb5N6EflyXmaFiWM4ybQduhVyKzNxlU8i5ug/msdpxQhj3rZ7WO6Xb
O8b5oj5e/8V1RmmsjC9dDFA/A8/JgAbrOn2CtCJrgRtl1LFBtaFRonfaRbuzcVSE
e1qdKoPY2UNK7cd3Hv/pkkorUJd89YREFZatyvU/b89fjNaPzjvtljxGadeIX5WO
7sQwMyHCSknWZPY4BYaiMf6jZ8TjXOCyIHQ3bdiDSiJlUEXvgz2yhF6Uue6aTvhR
Q85mPbtxXP+JXiZgSuT0Q6n7qN1b1mbZJgwk
-----END NEW CERTIFICATE REQUEST-----';
$hmac = hash_hmac('sha1', $data, $key);
echo "HMAC (hex) = ".$hmac;
Shevron was right: line ending are differents.
Just add: $data = str_replace("\r\n", "\n", $data); before computing the hash.
Here is the corrected rept.it file.
https://repl.it/BBDF/4
Related
Hi there StackOverflow community,
After researching for countless of hours, I'm unable to find an explanation as to why my ouputs differ between javascript and my laravel application.
I could use input type hidden to make a post from my web browser, but that would defeat the purpose of having a secure client side processing and I fear that if I don't find the reason as to why this is happening, then decryption (which I plan to do through php) would not work either.
my php code is as follows:
$payload = "this is my plaintext";
$binary_signature = "";
$private_key = openssl_pkey_get_private(file_get_contents(storage_path('privatekey.pem')), 'enc123456789');
openssl_sign($payload, $binary_signature, $private_key, OPENSSL_ALGO_SHA256);
$signature = base64_encode($binary_signature);
$new_payload = $payload."&sign=".$signature; // where my actual plaintext is also used in my javascript code
$key = "thisismykey";
$iv = "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0";
$encryption = openssl_encrypt($new_payload, 'AES-256-CBC', $key,OPENSSL_RAW_DATA, $iv);
dd(base64_encode($encyrption));
which outputs the following:
AEyHK+4DQWYjw8GeVV3mfzUJtk7ylxZINeryAdFptEbDyKOVbmNg8z32J2JgxGpFsQKpXxWaqDLf0IPNIq1jof0rKWhhDpaWzvTd0Tq/zgze7oGtZzEIqtdRDqax3ZvPkzNfuO/O14iW/YTwFkm9FLy9kGIirZDUTuAcOIjXGCgxhrhZHLn+V6SZpW5dYnH8u5rPDCeez2/HkUPI71YjD6hZ0DRjIkiXCyjPYH4fjNykz4yXo8hD+489Zxm8QPq1O1dyjR9JXSdrlYMWdixt6w0vz8EtPC8gZ+bDP/N/UEK07M52VB693zYb5uD1u7WuUUtsOjkr5ocF6QbEW7sjzI4q9yAxqvxRW/bkKqodcVqtglW6YsdJjrTR0EfA/Or/QF3e3QWVM5/2g4rT3ccE17OP6Rp/46yTpW9lOgS6Qiz2hY95GoaxbLfHB/Vb0Es+UppwDu8bd/u2Qax5erBi5ObZu3AjKNpTem45paspsKH3/vc2Jc810XrVQPjnDdZ8VrXvCgPiulywn5Mj28O7uUQ5bay3Zxy3bmHb7ESDEVMKiSEoru3LzDJ7wwPlidJzPcfWtuiMEMsPOv1Y6LaxtlizWM5/zYJFX/RA4d+KBl+Rn6BoPZDcX/6eh3oUoNhy
My JS Code (the plaintext is received through an ajax call which has the same sign method as you see from the php code, the encryption plaintext is of the same value from my php code)
function encrypt(plaintext, secretkey) {
var randomSeed = "";
randomSeed = secretkey;
var key = CryptoJS.enc.Utf8.parse(randomSeed.toUpperCase());
var iv = CryptoJS.enc.Utf8.parse("0000000000000000");
var encrypt = {};
encrypt = CryptoJS.AES.encrypt(plaintext, key, {
mode : CryptoJS.mode.CBC,
iv : iv
});
console.log("encryption is "+encrypt.toString());
return encrypt.toString(encrypt.toString());
}
Returns the correct value which is:
jLmAUr+JyCjbpctU4z6+dlF61jbHRphwTS0iAk3IRiy3jkfCtaCSWdiIO0awuX6G1jAlZroTiAuMl9OW0zj0q4HitndfGFtFUoMMCqZTzvMr6cy1TyG9EFz20T6ByrBnOvGuoVjv3Flufuk2Ghz5in2W2A3T+wF+SPXX/bIAnHtE3uW0bPl2q5tn6KyUI1uoQaYcMZKRPyzAQS7WSSwSOmAcVrRuDANgZQuO+3mh86QAdeFaYqFdZUnxD4c2kkbkGy17SUFfSK8Qjv+8tkTcYXV9QRRdWjGZiQQeyAr3PDKA4SDVzrcMNwJjTaLJiZv0Iau66HGpbf2yvRDLtIOoXQmnhs6NKTZpcSwZ07hHqVvBZmNRq+jqZOGw1s8GRH+Bz4yxSRycTS0DEddhyMoxhZcUc5wt42vDOYIEH2nuw/uu4gjrwpx0rVO1ssoZYRxvBaA6zSC4N04Wdn4JE2/LtXertDLEdLBtmk3c3n4QDU0tK5v31HMY7P7+fdQXU62niVxCNPSt9dpYa82IUrQuigNXgrbphQvZNmmcONi/4pnxJjKcKYpCn/1KhkBVUAhYm6UKJJvMNAo0M+cfsvReImrJx6IzPRdzTTFAQF5kW2NFkV4EIb0DtCF679RtdAhg5ShaP7QhqYL6EgFCs9WnJTACC26TmV20DAqUiuIYULLtjDW4qFOWi/y8D1JOWTar
I don't understand why my PHP encryption is giving the wrong output while my JavaScript encryption is giving the correct output
I'm hoping someone could give me an insight as to what I'm doing wrong from my PHP side. Wha I'm hoping to achieve is that my PHP encryption will output the same result as my JavaScript encryption.
Thank you in advance :)
I have solved my issue.
To those having a similar issue, here's a brief explanation. Crypto JS uses the following:
var key = CryptoJS.enc.Utf8.parse(randomSeed.toUpperCase());
var iv = CryptoJS.enc.Utf8.parse("0000000000000000");
Which translates to a word array if you console log the output. PHP needs to have the similar word array value that cryptoJS produces for it's AES encryption method. To solve this, you have to convert your $key and iv to be in hex format and format your code in php such as:
$key = pack("H*", "4a424d56595753555047553830334d42505a314f414256414c5a565239324659");
$iv = pack("H*", "30303030303030303030303030303030");
Then when you proceed to using openssl_encrypt
$encrypted_data = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
you will get the same output as crpytoJS.
Hope this helps.
Does anyone know what is the equivalent of this two line of PHP code in Python?
$pkeyid = openssl_get_privatekey($priv_key);
openssl_sign($data, $binary_signature, $pkeyid, OPENSSL_ALGO_SHA1);
Thanks in advance!
Edited:
$fpx_msgToken = "01";
$fpx_msgType = "BE";
$fpx_sellerExId = "ABC000012345";
$fpx_version = "6.0";
/* Generating signing String */
$data = $fpx_msgToken."|".$fpx_msgType."|".$fpx_sellerExId."|".$fpx_version;
/* Reading key */
$priv_key = file_get_contents('C:\\pki-keys\\DevExchange\\EX00002220.key');
$pkeyid = openssl_get_privatekey($priv_key);
openssl_sign($data, $binary_signature, $pkeyid, OPENSSL_ALGO_SHA1);
$fpx_checkSum = strtoupper(bin2hex( $binary_signature ) );
In python I would use the cryptography package.
Examples shown can be found here: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/
You can create a private key with the following code.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import dsa
private_key = dsa.generate_private_key(key_size=2048, backend=default_backend())
This will create the key to generate the signature of your data.
I would suggest you 2048 bits or above for the key length.
The following code is an example for signing a message.
from cryptography.hazmat.primitives import hashes
data = b"this is some test data"
signature = private_key.sign(data, hashes.SHA256())
If you now want to verify a signature you have to get the public key from the private key.
public_key = private_key.public_key()
public_key.verify(signature, data, hashes.SHA256())
This public key corresponds with your private key and is used to verify signatures that were created with your private key.
Don't focus on each line too much, every language and library will have different methods and ways of doing basically the same thing.
Now for a complete example you can just put the above examples together.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dsa
private_key = dsa.generate_private_key(key_size=2048, backend=default_backend())
data = b"this is some test data"
signature = private_key.sign(data, hashes.SHA256())
public_key = private_key.public_key()
public_key.verify(signature, data, hashes.SHA256())
public_key.verify() will raise an InvalidSignature exception if the signature happens to be invalid (Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/#verification).
I have found the solution of Python equivalent code for the above PHP code as below.
from OpenSSL import crypto
import binascii
fpx_msg_token = "01"
fpx_msg_type = "BE"
fpx_seller_exchange_id = "ABC0000123"
fpx_version = "6.0"
# Generating signing String
data = "{}|{}|{}|{}".format(fpx_msg_token, fpx_msg_type, fpx_seller_exchange_id, fpx_version)
key_id = open('C:\\pki-keys\\DevExchange\\EX00002220.key').read();
# Check is TraditionalOpenSSL or PKCS8 format
if key_id.startswith('-----BEGIN RSA PRIVATE KEY'):
# TraditionalOpenSSL format;
priv_key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_id)
else:
# PKCS8 format;
priv_key = crypto.load_pkcs12(key_id).get_privatekey()
# return signature is in binary string;
signature_bin_str = crypto.sign(priv_key, data, 'sha1')
# Convert binary string to hexidecimal
signature_hex = binascii.hexlify(signature_bin_str)
# Convert binary to string;
signature = signature_hex.decode("ascii")
# Convert signature to upper case;
fpx_checksum = str(signature).upper()
At the end I got the same value as in PHP code.
:)
I'm trying to transform a php code into python language.
the php function calculates the hmac value using sha256 and base64 encoding.
My Php function:
<?php
define('SHOPIFY_APP_SECRET', 'some_key');
function verify_webhook($data)
{
$calculated_hmac = base64_encode(hash_hmac('sha256', $data,
SHOPIFY_APP_SECRET, true));
echo $calculated_hmac;
}
$data = "some_data";
$verified = verify_webhook($data);
?>
My Python function:
import base64
import hmac
import binascii
from hashlib import sha256
API_SECRET_KEY = "some_key"
data = "some_data"
def verify_webhook():
dig = hmac.new(
API_SECRET_KEY,
msg=data,
digestmod=sha256
).digest()
calculated_hmac = base64.b64encode(bytes(binascii.hexlify(dig)))
print(calculated_hmac)
verify_webhook()
I got different outputs even I have the same key and data. I still don't know what I'm missing here. please help!
Python output:
YWM3NjlhMDZjMmViMzdmM2E3YjhiZGY4NjhkNTZhOGZhMDgzZDM4MGM1OTkyZTM4YjA5MDNkMDEwNGEwMzJjMA==
Php output:
N7JyAyKocoDx/Opx36nGqAuUKdyGH+ROX+J5AJgQ+/g=
I was able to match your php output using Python 3:
>>> dig = hmac.new( bytes(API_SECRET_KEY,'ascii'),
msg=bytes(data, 'ascii'), digestmod=sha256 )
>>> dig.digest()
b'7\xb2r\x03"\xa8r\x80\xf1\xfc\xeaq\xdf\xa9\xc6\xa8\x0b\x94)\xdc\x86\x1f\xe4N_\xe2y\x00\x98\x10\xfb\xf8'
>>> base64.b64encode(dig.digest())
b'N7JyAyKocoDx/Opx36nGqAuUKdyGH+ROX+J5AJgQ+/g='
I am working on an application which generates RSA encrypted session keys and stores them in a database. Later theses keys are transfered to a C++ application via Javascript. Therefore I want to use the OpenSSL library. I generated a 2048 bit key pair with openssl, which is used in both methods.
My PHP functions works like this:
function encrypt_with_public_key($input, $key)
{
openssl_public_encrypt($input, $crypttext, $key, OPENSSL_PKCS1_OAEP_PADDING);
return $crypttext;
}
and
$fp = fopen("public.pem","r");
$public_pem = fread($fp,8192);
fclose($fp);
$public_key = openssl_get_publickey($public_pem);
$sessionKey = ...;
$encSessionKey = encrypt_with_public_key($sessionKey, $public_key);
I tested this part successfully. The part I have trouble with, is the C++ part. I use MS Visual Studio 2013. Edit: added hex encoding (using Crypto++)
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/crypto.h>
...
string decrypted, encoded;
decrypted.clear();
char privateKey[] =
"-----BEGIN RSA PRIVATE KEY-----\n"\
...
RSA *rsa = NULL;
BIO *keybio;
keybio = BIO_new_mem_buf(privateKey, strlen(privateKey));
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
StringSource ss2(input, true,
new HexEncoder(
new StringSink(encoded)
)
);
RSA_private_decrypt(encoded.length(), (unsigned char *)encoded.data(), (unsigned char *)decrypted.data(), rsa, RSA_PKCS1_OAEP_PADDING);
FBLOG_INFO("", ERR_error_string(ERR_get_error(), NULL));
return decrypted;
Note that the private key is not read from a file.
OpenSSL returns the following error: 0406506C: lib(4): func(101): reason(108).
It means afaik that my input data is longer than the modulus length (please correct me if I'm wrong). Anyone who knows how to handle this? I thought such problems are solved through the padding parameters.
The input data is the direct output of the php encrypt function (no base64 oder anything).
I have code which first I encrypted using mcrypt_ecb and hten then i send the value to another page using file_get_contents. but when I echo receive data, it only print half of it. I have attached my code and result below:
Client
$mac="B8-AC-6F-2D-5C-23";
// encrpt the max address
$key_value = pack('H*', "bcb04b7e103a0c");
$plain_text = $mac;
$encrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $plain_text, MCRYPT_ENCRYPT);
echo ("<p><b> Text after encryption : </b>");
echo ( $encrypted_text );
// send encrypted mac address to bridge for verification
$response = file_get_contents('http://localhost/scale/check.php?mac='.$encrypted_text);
print_r( $response );
Host
if (isset($_GET['mac']) && $_GET['mac']) {
$mac = $_GET['mac'];
echo "<br/>";
echo $mac;
//decrypted recieve data
$key_value = pack('H*', "bcb04b7e103a0c");
/* #var $encrypted_text type */
$encrypted_text = $mac;
$decrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $encrypted_text, MCRYPT_DECRYPT);
echo ("<p><b> Text after decryption : </b>");
echo ( $decrypted_text );
}
Result:
B8-AC-6F-2D-5C-23
Text after encryption : 5"ÆfÛkã–]» º"÷5Ù(Ÿ©U
_5"ÆfÛk_ã–]»
Text after decryption : 9Bþ‚î10tçæÇ|¤
The output of mcrypt_ecb is binary data, its bytes can contain arbitrary values. If you want to transfer it as an url parameter you have to encode it first. The general problem is that the url parameter parsing assumes a certain structure of the url. For example if you write http://localhost/scale/check.php?mac=abc123&foo=bar then PHP will see two parameters, the first one being mac with value abc123, the second being foo with value bar. But in the way you just concatenate the ciphertext to the url you could end up with the same url even if you really meant to have a parameter mac with the value abc123&foo=bar. To prevent such confusions all values have to be encoded so that certain characters do not appear. In your example the space seems to be problematic.
You can find some background on url encoding here: https://en.wikipedia.org/wiki/Percent-encoding .
By the way: It is generally appreciated if you only post a minimal example of your problem. Given that you have issues with the transmission of your data you could remove all the encryption code. But as I've seen it now, I can't help but notice that the algorithm DES and the block cipher mode ECB is generally a very insecure choice. If you want to protect your data during transmission you can avoid all the usual issues with home grown crypto schemes by using an https connection (notice the "s" for "secure", which forces your http client to connect to the server using TLS).