Our API platform using CMAC-AES hashes as the signature for a request. We have libraries available for creating this hash in Java and .NET but need to find a solution for PHP as well. Problem is I can't find anything that seems to reliably generate a hash that matches the CMAC being generated on our server or via the Java/.NET library.
The only library I found is CryptLib, an alpha library.
https://github.com/ircmaxell/PHP-CryptLib
But it's not generating the same hash and I'm not good enough with crypto to understand why (it's forcing block sizes to 16 for AES, when what I find online says AES block size is 128).
Any other avenues I can go down?
The PHP-CryptLib library above will, in the end, work just fine. My problem was just my own mistake related to binary vs. hex data.
Using the test data provided by the library one
require_once 'lib/CryptLib/bootstrap.php';
$hasher = new CryptLib\MAC\Implementation\CMAC;
$key = '2b7e151628aed2a6abf7158809cf4f3c'; // from test/Data/Vectors/cmac-aes ...
$msg = '6bc1bee22e409f96e93d7e117393172a'; // from test/Data/Vectors/cmac-aes ...
$cmac = $hasher->generate($msg,$key);
echo $cmac;
// $cmac should be 070a16b46b4d4144f79bdd9dd04a287c
// actually getting ¢ nd{þ¯\ ¥á¼ÙWß
Except the CMAC hasher uses binary data not the ascii chars so one needs to pack it using pack():
$key = pack("H*", '2b7e151628aed2a6abf7158809cf4f3c');
$msg = pack("H*", '6bc1bee22e409f96e93d7e117393172a');
My specific, real-world case was trying to hash an arbitrary string, such as:
$msg = 'Client|Guid-023-23023-23|Guid-0230-2402-252|string|123456|2012-11-08T20:55:34Z';
And to do that I needed a function like this:
function pack_str($str) {
$out_str = "";
$len = strlen($str);
for($i=0; $i<$len; $i++) {
$out_str .= pack("c", ord(substr($str, $i, 1)));
}
return $out_str;
}
Once the data was packed with that function and run through the hasher, I got the CMAC hash I was expecting.
Related
I have the following PHP code:
require('/var/www/third_party_plugins/phpseclib/vendor/autoload.php');
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Crypt\RSA;
use phpseclib3\Math\BigInteger;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\Random;
$message = hex2bin('f5f905e8b2d8f0a72e179a169a59bc373021a75865e55c6797627bc43ddc6af0d9bd673bf94f5e8defc5af81019fd87c7d504a6aa758ba1e2f1f9858d0293b0b');
$key = hex2bin('d2ce45fd5f80c15db0a4ab26a7e27f42b507ed9469f0d63c1dbe4f89ed84c0c2');
$iv = hex2bin('db9d7e844b00282327221bb563639f96');
$cipher = new AES('cbc');
$cipher->setIV($iv);
$cipher->setKey($key);
//$cipher->disablePadding();
$Decrypted = bin2hex($cipher->decrypt($message));
print("\n" . $Decrypted . "\n");
When I run this code, I get the following result:
240dcbefc0f82fadc00ef8494488aaa81400000c2def01e79fec6c4d9a822358dd8a910cac606e8afcb607793cb442093a56b7b40b
Inside of this result, I can see the message I WANT, which is:
1400000c2def01e79fec6c4d9a822358
However, there are 16 bytes of data in the front of the message which make no sense to me, it seems like some kind of padding, but I dont want it in my result nor do I understand why it is there, because from what I understand the padding should be removed by phpseclib or openssl functions
240dcbef c0f82fad c00ef849 4488aaa8
I understand that there is a 20 byte MAC at the end, however, I notice ONE extra byte in front of the mac:
dd8a910cac606e8afcb607793cb442093a56b7b40b // THIS IS 21 BYTES, NOT 20...why?
I'm also having trouble re-encrypting the data and getting a valid response from the server, as I'm re-encrypting it with PHP, then sending the SAME data to my C# server, and it is unable to decrypt the message.
I am following along with a tutorial on encryption: https://php.watch/articles/modern-php-encryption-decryption-sodium. In working with the Sodium extension I'm just baffled by a few things. Googling is returning frustratingly little help. (Most of the results are just duplications of the php.net/manual.)
1. In various articles I'm reading, the result of sodium_crypto_*_encrypt() is something familiar:
// ex. DEx9ATXEg/eRq8GWD3NT5BatB3m31WED
Whenever I echo it out myself I get something like:
// ex. 𫦢�2(*���3�CV��Wu��R~�u���H��
which I'm certain won't store correctly on a database. Nowhere in the articles or documentation does it mention anything about charset weirdness. I can throw a header('Content-Type: text/html; charset=ISO-8859-1') in there, but I still get weird characters I'm not certain are right since I'm not finding any threads talking about this:
// ex. ÑAÁ5eŠ…n#±'ýÞÃ1è9ÜÈ̳¬"CžãÚ0ÿÛ
2. I can't find any information about the best practice for storing keys or nonces.
I just figured this obvious-to-security-folks-but-not-to-others bit of information would be a regularly discussed part of articles on keygens and nonces and such. Seeing as both my keygen and nonce functions (at least in the Sodium library) seem to return non-UTF-8 gibberish, what do I do with it? fwrite it out to a file to be referenced later? Pass it directly to my database? Copy/pasting certainly doesn't work right with it being wingdings.
Other than these things, everything else in the encryption/decryption process makes complete sense to me. I'm far from new to PHP development, I just can't figure this out.
Came across https://stackoverflow.com/a/44874239/1128978 answering "PHP random_bytes returns unreadable characters"
random_bytes generates an arbitrary length string of cryptographic random bytes...
And suggests to use bin2hex to get readable characters. So amending my usages:
// Generate $ciphertext
$message = 'This is a secret message';
$key = sodium_crypto_*_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_*BYTES);
$ciphertext = sodium_crypto_*_encrypt($message, '', $nonce, $key);
// Store hexadecimal versions of binary output
$nonce_hex = bin2hex($nonce);
$key_hex = bin2hex($key);
$ciphertext_hex = bin2hex($ciphertext);
// When ready to decrypt, convert hexadecimal values back to binary
$ciphertext_bin = hex2bin($ciphertext_hex);
$nonce_bin = hex2bin($nonce_hex);
$key_bin = hex2bin($key_hex);
$decrypted = sodium_crypto_*_decrypt($ciphertext_bin, '', $nonce_bin, $key_bin);
// "This is a secret message"
So making lots of use of bin2hex and hex2bin, but this now makes sense. Effectively solved, though not confident this is the proper way to work with it. I still have no idea why this isn't pointed out anywhere in php.net/manual nor in any of the articles/comments I've been perusing.
Hi shopify guru's I know this question has come around a thousand times, I know this because I have read every single thread I could find.
My app verificaton was working fine, but now that I switched to embedded app, I can't seem to verify the hmac.
//Remove hmac from hash comparison
$hmac = $data['hmac'];
unset($data['hmac']);
//sort the values alphabetically
ksort($data);
$data = urldecode(http_build_query($data));
$hash = hash_hmac('sha256', $data, $this->ci->get('settings')['shopify']['api_secret']);
This code will keep returning a different hash from the hmac shopify sends me, I'm guessing there is a problem with encoding or escaping, I have tried every thing I could think of (htmlspecialchars, urldecode, strreplace, doublecheck secret etc..)
the string looks like this after the urldecode line:
locale=en&protocol=https://&shop=mystorehandle.myshopify.com×tamp=1539901099
Any help would be appreciated, I wanted to get some work done on my app, but the past 3 hours have been filled with trying to get my hmac in sync with shopify's :(
I'm quiet certain I'm doing it right, but have no clue as to why it won't work then.
I'm buying a pie for whoever knows the answer first
.
Ok so... don't ask me how or why this works, but it does. After 2-3 hours on this, I took a complete stab in the dark, and it worked. I'm pretty sure it only works because there is a bug on the Shopify side.
My embedded app was being fed GET params hmac/shop/timestamp/protocol/locale in that order.
For some reason, building a query string of shop=[myshop]×tamp=[timestamp] worked.
In other words, I removed hmac, but ALSO protocol and locale.
Using this code though, BROKE the TEST version of my app, which was actually working fine with protocol and locale included.
My only conclusion here is that the hmac is derived from the GET parameters following hmac, which are in alphabetical order, stopping when the following parameter is not in that order.
So if GET params are hmac/shop/timestamp/protocol/locale - generate your hash string using shop & timestamp.
If GET params are hmac/locale/protocol/shop/timestamp - generate your hash string using locale, protocol, shop & timestamp.
So strange. Would be really interested to know if this works for you also!
Here's my code:
parse_str($_SERVER['QUERY_STRING'], $queryStringArray);
$providedHmac = $_GET['hmac'];
unset($queryStringArray['hmac']);
$amp = '';
$i = 0;
foreach($queryStringArray as $key => $value)
{
$keyFirstLetter = substr($key, 0, 1);
if($i == 0 || $keyFirstLetter > $lastKeyFirstLetter)
{
$newQueryString .= $amp . $key . '=' . $value;
$amp = '&';
}
$lastKeyFirstLetter = $keyFirstLetter;
$i++;
}
$calculatedHmac = hash_hmac('sha256', $newQueryString, SHOPIFY_APP_SHARED_SECRET);
$hmacValid = false;
if($calculatedHmac == $providedHmac)
{
$hmacValid = true;
}
I have this issue where something is encrypted in python using aes 256 cbc encryption as shown in the python codes encrypt method .
I am trying to create a Decrypt method in php to actually decrypt whats encrypted using the python class .
Here is my attempt to convert the python decryption method to php does it look right or am I missing something in my conversion as every time i use the php version to decrypt it says hmac failed ?
anyhelp in converting the python class to php i will appreciate.
public function decrypt(){
$encrypt_method ="AES-256-CBC";
$secret_key =base64_decode('samekeyusedintheencryption');
$encrypted=(string)'some encrypted text to be decrypted';
$data=json_decode(base64_decode($encrypted),true);
$secret_iv =base64_decode($data['iv']);
$output = \openssl_decrypt($data['value'],
$encrypt_method,$secret_key,0,$secret_iv);
return json_encode($output);
}
def decrypt(self, payload):
data = json_c.decode(base64.b64decode(payload))
value = base64.b64decode(data['value'])
iv = base64.b64decode(data['iv'])
crypt_object=AES.new(self.key,AES.MODE_CBC,iv)
plaintext = crypt_object.decrypt(value)
return loads(plaintext)
OK, I got it to work!
function decrypt($encryptedText, $secret_key){
$secret_key = base64_decode($secret_key);
$encrypt_method ="AES-256-CBC";
$data = json_decode(base64_decode($encryptedText),true);
$data['iv'] = base64_decode($data['iv']);
$data['value'] = base64_decode($data['value']);
return openssl_decrypt($data['value'], $encrypt_method, $secret_key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $data['iv']);
}
Some things I learned:
If the options in the openssl function are set to '0' it expects a base64_encoded input for the cipher text. Also, if the default options is set to '0' the padding default is set to PKCS#7. This, I think, is why we were getting the bad block size error.
So, the cipher text needs to be base64_decoded and we need to set both options for the padding.
I was able to decrypt your provided cipher text and see the email addresses.
You are provided the MAC in the Data array so this would allow you to check the MAC in the PHP script. This allows you to make sure the data has not been tampered with.
I recently did an encryption project and started with the open ssl, but ended up changing to the libSodium library. I highly recommend you check it out for any further projects.
Cheers!
So I am working on a PHP script that queries an API which uses HMAC authentication headers. However, I have been banging my head trying to encode the HMAC signature correctly. I have a preexisting nodejs script to work from as a template.
In the nodejs script, the HMAC signature is calculated using the following:
var crypto = require('crypto');
var hmac = [];
hmac.secret = 'ODc0YTM3YzUxODFlMWQ1YTdhMGQwY2NiZmE1N2Y1ODdjYzM5NTgyMDJhZjVkYTE4MmQxYzQ5ODk0M2QzNWQxYw==';
hmac.timestamp = 1457326475000;
hmac.path = '/account/';
hmac.message = hmac.path +'\n' + hmac.timestamp;
var sig = crypto.createHmac('sha512', new Buffer(hmac.secret, 'base64'));
hmac.signature = sig.update(hmac.message).digest('base64');
console.log(hmac);
This correctly calculates the HMAC signature as:
bWjIFFtFmWnj0+xHLW2uWVa6M6DpbIV81uyUWwRFCJUg+0Xyt40QWZWQjGvfPUB/JbjGZHUoso0Qv5JHMYEv3A==.
Meanwhile, in PHP, I am using:
<?php
$hmac['secret'] = 'ODc0YTM3YzUxODFlMWQ1YTdhMGQwY2NiZmE1N2Y1ODdjYzM5NTgyMDJhZjVkYTE4MmQxYzQ5ODk0M2QzNWQxYw==';
$hmac['nonce'] = '1457326475000';
$hmac['path'] = '/account/';
$hmac['message'] = $hmac['path']."\n".$hmac['nonce'] ;
$hmac['signature'] = base64_encode(hash_hmac('sha512',$hmac['message'],
$hmac['secret'], true));
print_r($hmac);
The above code, will calculate the HMAC signature as:
vqP49m/bk9nA4S3nMqW2r+kc2+yBfwhY/jWGUfz6dlKJUMkC2ktiPnuCcymdSWl4XezZT5VKCATYfus86Hz/Gg==
Working from the principle that "one million monkeys hacking away at a million keyboards" might one day be able to encode a valid HMAC signature, I have even tested a loop that iterates through all the permutations of the above PHP code (with/without base64 encoding the message, secret; with/without binary encoding of the HMAC, etc.)... to no avail.
Any suggestions for this here, one exhausted simian?
The problem is that you're not decoding your $hmac['secret'] first before passing it to hash_hmac().
Try:
$hmac['secret'] = base64_decode($hmac['secret']);
$hmac['signature'] = base64_encode(
hash_hmac('sha512', $hmac['message'], $hmac['secret'], true)
);