PHP HMAC - Will this implementation of a hmac validator be sufficient? - php

The following function is part of a Request class. The class basically just holds the information parsed from the headers and body, and I want to implement a secure HMAC validation scheme. I have not done this before, but I have read A LOT on the subject, both here on SO and other places. I chose the sha256 algo as a middle way between performance and security.
The class holds all the variables except the API_KEY which is a defined constant that changes for each version and the shared secret which I store in a DB after an initial three-way exchange with public-key encryption securing the shared secret during device-registration. validNonce() just looks up the nonce in the DB to see if it is valid.
My questions boils down to: Am I on the right track? Am I missing something bleeding obvious?
public function isValidRequest($secret)
{
if(!validNonce($this->nonce))
{
return false;
}
$data = API_KEY . $this->device_key . $this->user_key .
$this->cnonce . $this->nonce . $this->body;
$hmac_hash = hash_hmac("sha256",$data,$secret);
return $this->hash === $hmac_hash;
}

Am I on the right track? Am I missing something bleeding obvious?
Timing attacks! Okay, so it's not really obvious, but that's the missing piece of the puzzle here.
The general solution (in diff format) is:
- return $this->hash === $hmac_hash;
+ return hash_equals($this->hash, $hmac_hash);
Furthermore, how you feed your data into HMAC needs to be carefully considered when working with multipart messages. Otherwise, even though you're using HMAC securely, you'll introduce the risk of creating two identical strings from different combinations of its constituent parts, which might be security-affecting.
$data = API_KEY . $this->device_key . $this->user_key .
$this->cnonce . $this->nonce . $this->body;
This was a concern of mine when I designed PASETO, so I defined a scheme I call PAE (pre-authentication encoding) that makes these messages distinct and unambiguous. You can find an implementation here (reproduced below):
<?php
class Util
{
// ~8<~8<~8<~8<~8<~ SNIP SNIP ~8<~8<~8<~8<~8<~
/**
* Format the Additional Associated Data.
*
* Prefix with the length (64-bit unsigned little-endian integer)
* followed by each message. This provides a more explicit domain
* separation between each piece of the message.
*
* Each length is masked with PHP_INT_MAX using bitwise AND (&) to
* clear out the MSB of the total string length.
*
* #param string ...$pieces
* #return string
*/
public static function preAuthEncode(string ...$pieces): string
{
$accumulator = \ParagonIE_Sodium_Core_Util::store64_le(\count($pieces) & PHP_INT_MAX);
foreach ($pieces as $piece) {
$len = Binary::safeStrlen($piece);
$accumulator .= \ParagonIE_Sodium_Core_Util::store64_le($len & PHP_INT_MAX);
$accumulator .= $piece;
}
return $accumulator;
}
// ~8<~8<~8<~8<~8<~ SNIP SNIP ~8<~8<~8<~8<~8<~
}
And a compatible JavaScript implementation:
function LE64(n) {
var str = '';
for (var i = 0; i < 8; ++i) {
if (i === 7) {
// Clear the MSB for interoperability
n &= 127;
}
str += String.fromCharCode(n & 255);
n = n >>> 8;
}
return str;
}
function PAE(pieces) {
if (!Array.isArray(pieces)) {
throw TypeError('Expected an array.');
}
var count = pieces.length;
var output = LE64(count);
for (var i = 0; i < count; i++) {
output += LE64(pieces[i].length);
output += pieces[i];
}
return output;
}
What this means is that you'd want your final result to use something like PAE to encode all of the pieces into the $data function when creating AND verifying your HMAC tags. Don't forget to use hash_equals() to compare the two strings.

Related

PHP: Verifying that requests originate from Office for the web by using proof keys

I'm trying to Verifying that requests originate from Office for the web by using proof keys
I have read this resource many times but still do not know how to implement it in PHP.
I tried the code below it didn't seem to solve the problem, I do not know what to do next.
Can someone give me a suggestion?
$rsa = new Crypt_RSA();
$modulus = new Math_BigInteger(base64_decode($modulus), 256);
$exponent = new Math_BigInteger(base64_decode($exponent), 256);
$rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
$rsa->setPublicKey();
$publicKey = $rsa->getPublicKey();
I understand that verifying WOPI proof key is quite complicated to implement while extracting programming logic from their explanation in the document is challenging.
Unfortunately, I'm not a PHP developer. However, I'd like to share my production C# code in case it could help.
private async Task<bool> ValidateWopiProof(HttpContext context)
{
// Make sure the request has the correct headers
if (!context.Request.Headers.ContainsKey(WopiRequestHeader.PROOF) ||
!context.Request.Headers.ContainsKey(WopiRequestHeader.TIME_STAMP))
return false;
// TimestampOlderThan20Min
var timeStamp = long.Parse(context.Request.Headers[WopiRequestHeader.TIME_STAMP].ToString());
var timeStampDateTime = new DateTime(timeStamp, DateTimeKind.Utc);
if ((DateTime.UtcNow - timeStampDateTime).TotalMinutes > 20)
return false;
// Set the requested proof values
var requestProof = context.Request.Headers[WopiRequestHeader.PROOF];
var requestProofOld = String.Empty;
if (context.Request.Headers.ContainsKey(WopiRequestHeader.PROOF_OLD))
requestProofOld = context.Request.Headers[WopiRequestHeader.PROOF_OLD];
// Get the WOPI proof info from Wopi discovery
var wopiProofPublicKey = await _wopiDiscovery.GetWopiProof();
// Encode the values into bytes
var accessTokenBytes = Encoding.UTF8.GetBytes(context.Request.Query["access_token"].ToString());
var hostUrl = GetAbsolouteUrl(context);
var hostUrlBytes = Encoding.UTF8.GetBytes(hostUrl.ToUpperInvariant());
var timeStampBytes = BitConverter.GetBytes(Convert.ToInt64(context.Request.Headers[WopiRequestHeader.TIME_STAMP])).Reverse().ToArray();
// Build expected proof
List<byte> expected = new List<byte>(
4 + accessTokenBytes.Length +
4 + hostUrlBytes.Length +
4 + timeStampBytes.Length);
// Add the values to the expected variable
expected.AddRange(BitConverter.GetBytes(accessTokenBytes.Length).Reverse().ToArray());
expected.AddRange(accessTokenBytes);
expected.AddRange(BitConverter.GetBytes(hostUrlBytes.Length).Reverse().ToArray());
expected.AddRange(hostUrlBytes);
expected.AddRange(BitConverter.GetBytes(timeStampBytes.Length).Reverse().ToArray());
expected.AddRange(timeStampBytes);
byte[] expectedBytes = expected.ToArray();
return (VerifyProofKeys(expectedBytes, requestProof, wopiProofPublicKey.Value) ||
VerifyProofKeys(expectedBytes, requestProofOld, wopiProofPublicKey.Value) ||
VerifyProofKeys(expectedBytes, requestProof, wopiProofPublicKey.OldValue));
}
private string GetAbsolouteUrl(HttpContext context)
{
var url = $"{_wopiUrlService.ApiAddress.TrimEnd('/')}{context.Request.Path}{context.Request.QueryString}";
return url.Replace(":44300", "").Replace(":443", "");
}
private bool VerifyProofKeys(byte[] expectedProof, string proofFromRequest, string discoPublicKey)
{
using (RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider())
{
try
{
rsaProvider.ImportCspBlob(Convert.FromBase64String(discoPublicKey));
return rsaProvider.VerifyData(expectedProof, "SHA256", Convert.FromBase64String(proofFromRequest));
}
catch (FormatException)
{
return false;
}
catch (CryptographicException)
{
return false;
}
}
}
This implementation follows the document spec. For the method _wopiDiscovery.GetWopiProof(), I just get proof-key section from Wopi Discovery.
.
#Rachanee is your solution working? I have the same code but validation is failing.
You can find a WOPI Proof Validator in PHP, in this package: https://github.com/Champs-Libres/wopi-lib/
This package is a helper for integrating WOPI protocol in PHP applications.
It contains a WOPI Proof Validator service that you can use out of the box.

how to generate Trx wallet without using API

base on TRX documents and some search in GitHub I tried to generate wallet offline and I can't use API for some reasons.
based on Trx documents I should do these steps :
Generate a key pair and extract the public key (a 64-byte byte array representing its x,y coordinates).
Hash the public key using sha3-256 function and extract the last 20 bytes of the result.
Add 0x41 to the beginning of the byte array. The length of the initial address should be 21 bytes.
Hash the address twice using sha256 function and take the first 4 bytes as verification code.
Add the verification code to the end of the initial address and get an address in base58check format through base58 encoding.
An encoded Mainnet address begins with T and is 34 bytes in length.
Please note: the sha3 protocol adopted is KECCAK-256.
I find mattvb91/tron-trx-php in GitHub and this repository there is a wallet generator method /src/Wallet.php but the generated key validation return an Error Exception and validation get failed.
I try to recode mattvb91/tron-trx-php Wallet generator method and create my wallet generator
class walletGenerator
{
private $addressPrefix = "41";
private $addressPrefixByte = 0x41;
private $addressSize = 34;
public function __construct()
{
}
public function generateWallet()
{
$key = new Key();
$odd = false;
while (!$odd)
{
$keyPair = $key->GenerateKeypair();
if(strlen($keyPair['public_key']) % 2 == 0) $odd = true;
}
$privateKeyHex = $keyPair['private_key_hex'];
$pubKeyHex = $keyPair['public_key'];
$pubKeyBin = hex2bin($pubKeyHex);
$addressHex = $this->getAddressHex($pubKeyBin);
$addressBin = hex2bin($addressHex);
$addressBase58 = $this->getBase58CheckAddress($addressBin);
$validAddress = $this->validateAddress($addressBase58);
}
private function getAddressHex($pubKeyBin)
{
if (strlen($pubKeyBin) == 65) {
$pubKeyBin = substr($pubKeyBin, 1);
}
$hash = Keccak::hash($pubKeyBin , 256);
$hash = substr($hash, 24);
return $this->addressPrefix . $hash;
}
private function getBase58CheckAddress($addressBin){
$hash0 = Hash::SHA256($addressBin);
$hash1 = Hash::SHA256($hash0);
$checksum = substr($hash1, 0, 4);
$checksum = $addressBin . $checksum;
$result = Base58::encode(Crypto::bin2bc($checksum));
return $result;
}
private function validateAddress($walletAddress){
if(strlen($walletAddress) !== $this->addressSize) return false;
$address = Base58Check::decode($walletAddress , false , 0 , false);
$utf8 = hex2bin($address);
if(strlen($utf8) !== 25) return false;
if(strpos($utf8 , $this->addressPrefixByte) !== 0) return false; // strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior
$checkSum = substr($utf8, 21);
$address = substr($utf8, 0, 21);
$hash0 = Hash::SHA256($address);
$hash1 = Hash::SHA256($hash0);
$checkSum1 = substr($hash1, 0, 4);
if ($checkSum === $checkSum1) {
return true;
}
}
Here is my problems :
validateAddress method has an Error Exception
when I add Private Key in Trx Wallets manually the detected wallet address is different from the generated Address.
if(strpos($utf8 , $this->addressPrefixByte) !== 0) return false; // strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior
additional information
I used ionux/phactor to generating keyPair and iexbase/tron-api Support classes for Hash and ...
I can debug and solved the problem and share the solutions with you
nowhere is the solutions
this line of code has an Error Exception
if(strpos($utf8 , $this->addressPrefixByte) !== 0) return false; // strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior
this problem because of PHP 7.3 bugs and fix with PHP 7.2 ( Exception: stripos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior #770 )
and addresses differences
in the Key Pair array, I remove 0x from first of Private Key Hex and I can access to the wallet in Tron Link Extention don't remember to have a transition for wallet activation

Can We Restrict PHP Variables to accept only certain type of values

i am wondering is it possible to restrict php variable to accept only certain type of variable it is defined to accept.
e-g if we see in C#
public int variable = 20;
public string variable2 = "Hello World";
so what would it be like in php if i want to restrict type for variable.
public int $variable = 20;
so that if i try to assign string to integer variable i get the error.
public int $varaible = "Hello World"; //As type is integer, it should throw error.
is there such thing defining types in PHP for variables before assigning values to it??
TL;DR Not directly, no. PHP is not strictly-typed. There are, however, a few workarounds that may work for you in the context of function parameters or properties of classes.
Long answer: PHP is not a strictly-typed language, but loosely-typed. You can give a variable any value you want, regardless of how it was initialized. So, you can't simply type something like int $myVar and expect $myVar = "foo"; to throw an error. But PHP does offer a few handy features to get you to the same end when dealing with function parameters or properties of a class.
Option 1: Type hints
You can use a "type hint" for function parameters:
class SomeClass
{
/* code here */
}
function foo(SomeClass $data)
{
/* code here */
}
foo() will only accept parameters of type SomeClass. Passing it, say, an int will throw a fatal error. This doesn't work in PHP < 7 if the parameters are intended to be base types, like int, string, etc., so you can't do function foo(int $data) {...}. That said, there are a few libraries out there that attempt to force it to work at the expense of a little speed. Also, PHP 7 adds a lot of support for this kind of thing, as does the Hack language based on PHP.
Pros:
Easy
Intuitive
Cons:
Only works for program-defined classes
Unavailable for base types
Option 2: Getters and Setters
You can also use getters and setters, like so:
class SomeClass
{
private $foo = 0;
function setFoo($val = 0)
{
// force it to be an int
if (is_integer($val) {
$this->foo = $val;
} else {
// throw an error, raise an exception, or otherwise respond
}
}
}
Pros:
Relatively easy
Relatively intuitive
Cons:
Only works in program-defined classes
Unavailable for base types
Requires lots of code
Option 3: Magic Methods
This method is my favorite, but also the most complicated. Use the __set() magic method to deal with class properties.
class MyClass {
private $type = 0; // we will force this to be an int
private $string = ''; // we will force this to be a string
private $arr = array(); // we will force this to be an array
private $percent = 0; // we will force this to be a float in the range 0..100
function __set($name, $value) {
switch ($name) {
case "type":
$valid = is_integer($value);
break;
case "string":
$valid = is_string($value);
break;
case "arr":
$valid = is_array($value);
break;
case "percent":
$valid = is_float($value) && $value >= 0 && $value <= 100;
break;
default:
$valid = true; // allow all other attempts to set values (or make this false to deny them)
}
if ($valid) {
$this->{$name} = $value;
// just for demonstration
echo "pass: Set \$this->$name = ";
var_dump($value);
} else {
// throw an error, raise an exception, or otherwise respond
// just for demonstration
echo "FAIL: Cannot set \$this->$name = ";
var_dump($value);
}
}
}
$myObject = new MyClass();
$myObject->type = 1; // okay
$myObject->type = "123"; // fail
$myObject->string = 1; // fail
$myObject->string = "123"; // okay
$myObject->arr = 1; // fail
$myObject->arr = "123"; // fail
$myObject->arr = array("123"); // okay
$myObject->percent = 25.6; // okay
$myObject->percent = "123"; // fail
$myObject->percent = array("123"); // fail
$myObject->percent = 123456; // fail
Pros:
Relatively easy
Intuitive
Extremely powerful: one setter to rule them all
Cons:
Only works in program-defined classes
Unavailable for base types
Requires lots of switching or if/else logic
Can cause problems with IDEs not auto-completing property types correctly
Here's a demo of this approach.
Closing Thoughts
Finally, if you're using an IDE like PHPStorm, don't forget about PHPDoc type hints:
/* #var integer */
$foo = 0; // will result in warnings if the IDE is configured properly and you try to do something like substr($foo, 1, 4);
And if you really want to go hard core, you can do strong typing using Hack, at the expense of making your code less portable and less compatible (for now) with major IDEs.
Of course, none of these is a substitute for explicitly validating user input and thoroughly testing the application's response to unexpected input types.
No. PHP is not a strictly typed language. You can however use type hints in functions and methods.
If class or interface is specified as type hint then all its children or implementations are allowed too.
Type hints can not be used with scalar types such as int or string. Resources and Traits are not allowed either.
The Scalar types being:
string
bool
int
float
Examples:
function (array $theArr) {
// body
}
class X {
public function __construct(SomeOtherClass $arg) {
// body
}
public function setFoo(Foo $foo) {
}
}
See the manual for more specifics: http://php.net/manual/en/language.oop5.typehinting.php
You have to made it by your own hands, example :
function setInt(&$var, $value) {
if(!is_integer($value) {
throw new Exception("Integer wanted " . gettype($value) . " received");
}
$var = $value;
}

Two takes on PHP two way encryption - which one is preferable?

I need to encrypt some data and have it decrypted on a later point in time. The data is tied to specific users. I've gathered two possible solutions...
1: The first one is derived from the official docs (example #1 # http://php.net/manual/en/function.mcrypt-encrypt.php):
function encrypt($toEncrypt)
{
global $key;
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
return base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $toEncrypt, MCRYPT_MODE_CBC, $iv));
}
function decrypt($toDecrypt)
{
global $key;
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$toDecrypt = base64_decode($toDecrypt);
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, substr($toDecrypt, $iv_size), MCRYPT_MODE_CBC, substr($toDecrypt, 0, $iv_size)));
}
The key is generated once using:
echo bin2hex(openssl_random_pseudo_bytes(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC)))
And then later referred to as this:
$key = pack('H*', [result of above]);
1.1: I've noticed that the encrypted result always ends in two equal signs ('=='). Why? - Using bin2hex() and hex2bin() in encrypt() and decrypt() instead of base64_encode()/base64_decode() respectively does not yield these results.
1.2: Will using bin2hex()/hex2bin() have any consequence on the outcome (other than length)?
1.3: There seems to be some discussion whether or not to call a trim-function on the return result when decrypting (this applies to the solution below as well). Why would this be necessary?
2: Second solution comes from here, Stackoverflow (Simplest two-way encryption using PHP):
function encrypt($key, $toEncrypt)
{
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $toEncrypt, MCRYPT_MODE_CBC, md5(md5($key))));
}
function decrypt($key, $toDecrypt)
{
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode($toDecrypt), MCRYPT_MODE_CBC, md5(md5($key))), "\0");
}
I'm aware that both approaches to the key handling is interchangeable, I purposely made them different in that respect in order to highlight possible solutions, please feel free to mix and match.
Personally I feel that the first one offers tighter security since both key and initialization vector is properly randomized. The second solution however, does offer some form of non-predictability since the key is unique for each piece of encrypted data (even though it suffers under the weak randomization of md5()).
The key could for example be the user's name.
3: So, which one is preferable? I'm slightly in the dark since the Stackoverflow answer got a whopping 105 votes. Other thoughts, tips?
4: Bonus question!: I'm not incredibly brainy on server security aspects, but obviously gaining access to the PHP files would expose the key, which as a direct result, would render the encryption useless, assuming the attacker also has access to the DB. Is there any way to obscure the key?
Thank you for reading and have a nice day!
EDIT: All things considered, this seems to be my best bet:
function encrypt($toEncrypt)
{
global $key;
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
return base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $toEncrypt, MCRYPT_MODE_CBC, $iv));
}
function decrypt($toDecrypt)
{
global $key;
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$toDecrypt = base64_decode($toDecrypt);
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, substr($toDecrypt, $iv_size), MCRYPT_MODE_CBC, substr($toDecrypt, 0, $iv_size)));
}
Using a key created once using the following:
bin2hex(openssl_random_pseudo_bytes(32)));
The main difference between the two code samples is that the first one generates a random initialization vector (IV) for each message, while the second one always uses a fixed IV derived from the key.
If you never encrypt more than one message with the same key, both methods are OK. However, encrypting multiple messages with the same key and IV is dangerous, so you should never use the second code sample to encrypt more than one message with the same key.
Another difference is that the first code sample passes the key directly to the block cipher (Rijndael), whereas the second one first runs it through md5(), apparently in a weak attempt to use it as a key derivation function.
If the key is already a random bitstring (of suitable length), like your sample key generation code would produce, there's no need to run it through md5(). If, instead, it's something like a user-provided password, there might be some advantage to hashing it — but in that case, you really ought to use a proper key derivation function like PBKDF2 instead, e.g. like this:
$cipher = MCRYPT_RIJNDAEL_128; // = AES-256
$mode = MCRYPT_MODE_CBC;
$keylen = mcrypt_get_key_size( $cipher, $mode );
$salt = mcrypt_create_iv( $keylen, MCRYPT_DEV_URANDOM );
$iterations = 10000; // higher = slower; make this as high as you can tolerate
$key = hash_pbkdf2( 'sha256', $password, $salt, $iterations, $keylen, true );
Note that the correct $salt and $iterations values will be needed to reconstruct the key from the password for decryption, so remember to store them somewhere, e.g. by prepending them to the ciphertext. The length of the salt doesn't matter much, as long as it's not very short; making it equal to the key length is a safe enough choice.
(Incidentally, this is also a pretty good way to hash a password to verify its correctness. Obviously, you shouldn't use the same $key value for both encryption and password verification, but you could safely store, say, hash( 'sha256', $key, true ) alongside the ciphertext to let you verify that the password / key is correct.)
A few other issues I see with the two code snippets:
Both snippets use MCRYPT_RIJNDAEL_256, which is, apparently, not AES-256, but rather the non-standard Rijndael-256/256 variant, with a 256-bit block size (and key size). It's probably secure, but the 256-bit-block-size variants of Rijndael have receive much less cryptanalytic scrutiny than the 128-bit-block-size ones (which were standardized as AES), so you're taking a slightly higher risk by using them.
Thus, if you want to play it safe, need to interoperate with other software using standard AES, or just need to be able to tell your boss that, yes, you're using a standard NIST-approved cipher, the you should go with MCRYPT_RIJNDAEL_128 (which, apparently, is what mcrypt calls AES-256) instead.
In your key generation code, pack( 'H*', bin2hex( ... ) ) is a no-op: bin2hex() converts the key from binary to hexadecimal, and pack( 'H*', ... ) then does the reverse. Just get rid of both functions.
Also, you're generating a key, not an IV, so you should use mcrypt_get_key_size(), not mcrypt_get_iv_size(). As it happens, for MCRYPT_RIJNDAEL_256 there's no difference (since both the IV size and the key size are 32 bytes = 256 bits), but for MCRYPT_RIJNDAEL_128 (and many other ciphers) there is.
As owlstead notes, mcrypt's implementation of CBC mode apparently uses a non-standard zero-padding scheme. You second code sample correctly removes the padding with rtrim( $msg, "\0" ); the first one just calls rtrim( $msg ), which will also trim any whitespace off the end of the message.
Also, obviously, this zero-padding scheme won't work properly if your data can legitimately contain zero bytes at the end. You could instead switch to some other cipher mode, like MCRYPT_MODE_CFB or MCRYPT_MODE_OFB, which do not require any padding. (Out of those two, I would generally recommend CFB, since accidental IV reuse is very bad for OFB. It's not good for CFB or CBC either, but their failure mode is much less catastrophic.)
Q1: Choose this!
Disclosure: I (re-)wrote the mcrypt_encrypt code sample. So I opt for 1.
Personally I would not recommend to use MCRYPT_RIJNDAEL_256. You use AES-256 by using a key with a key size of 32 bytes (256 bit) for the MCRYPT_RIJNDAEL_128 algorithm, not by selecting a Rijndael with a block size of 256. I explicitly rewrote the sample to remove MCRYPT_RIJNDAEL_256 – among other mistakes – and put in comments why you should use MCRYPT_RIJNDAEL_128 instead.
Q 1.1: Padding byte for base64
= is a padding character for base 64 encoding. Base64 encodes 3 bytes into 4 characters. To have a number of characters that is an exact multiple of 4 they use these padding bytes, if required.
Q1.2: Will using bin2hex()/hex2bin() have any consequence on the outcome (other than length)?
No, as both hex and base64 are deterministic and fully reversible.
Q1.3: On rtrim
The same goes for the rtrim. This is required as PHP's mcrypt uses the non-standard zero padding, up to the block size (it fills the plaintext with 00 valued bytes at the right). This is fine for ASCII & UTF-8 strings where the 00 byte is not in the range of printable characters, but you may want to look further if you want to encrypt binary data. There are examples of PKCS#7 padding in the comments section of mcrypt_encrypt. Minor note: rtrim may only work for some languages such as PHP, other implementations may leave trailing 00 characters as 00 is not considered white space.
Q2: Disqualification
The other SO answer uses MD5 for password derivation and MD5 over the password for IV calculation. This fully disqualifies it as a good answer. If you have a password instead of a key, please check this Q/A.
And it doesn't use AES either, choosing to opt for MCRYPT_RIJNDAEL_256.
Q3: On the votes
As long as SO community keeps voting on answers that seem to work for a certain language/configuration instead of voting on answers that are cryptographically secure, you will find absolute trap like the answer in Q2. Unfortunately, most people that come here are not cryptographers; the other answer would be absolutely smitten on crypto.stackexchange.com.
Note that just yesterday I had to explain to somebody on SO why it is not possibly to decrypt MCRYPT_RIJNDAEL_256 using CCCrypt on iOS because only AES is available.
Q4: Obfuscation
You can obfuscate the key, but not much else if you store an AES key in software or configuration file.
Either you need to use a public key (e.g. RSA) and hybrid cryptography, or you need to store the key somewhere safe such as a HSM or smart card. Key management is a complex part of crypto, possibly the most complex part.
First of all I apologize for the length of this answer.
I just came across this thread and I hope that this class may be of help to anyone reading this thread looking for an answer and source code they can use.
Description:
This class will first take the supplied encryption key and run it through the PBKDF2 implementation using the SHA-512 algorithm at 1000 iterations.
When encrypting data this class will compress the data and compute an md5 digest of the compressed data before encryption. It will also calculate the length of the data after compression. These calculated values are then encrypted with with the compressed data and the IV is prepended to the encrypted output.
A new IV is generated using dev/urandom before each encryption operation. If the script is running on a Windows machine and the PHP version is less than 5.3, the class will use MCRYPT_RAND to generate an IV.
Depending on if parameter $raw_output is true or false, the encryption method will return lowercase hexit by default or raw binary of the encrypted data.
Decryption will reverse the encryption process and check that the computed md5 digest is equal to the stored md5 digest that was encrypted with the data. If the hashes are not the same, the decryption method will return false. It will also use the stored length of the compressed data to ensure all padding is removed before decompression.
This class uses Rijndael 128 in CBC mode.
This class will work cross platform and has been tested on PHP 5.2, 5.3, 5.4, 5.5 and 5.6
File: AesEncryption.php
<?php
/**
* This file contains the class AesEncryption
*
* AesEncryption can safely encrypt and decrypt plain or binary data and
* uses verification to ensure decryption was successful.
*
* PHP version 5
*
* LICENSE: This source file is subject to version 2.0 of the Apache license
* that is available through the world-wide-web at the following URI:
* https://www.apache.org/licenses/LICENSE-2.0.html.
*
* #author Michael Bush <michael(.)bush(#)hotmail(.)co(.)uk>
* #license https://www.apache.org/licenses/LICENSE-2.0.html Apache 2.0
* #copyright 2015 Michael Bush
* #version 1.0.0
*/
/**
* #version 1.0.0
*/
final class AesEncryption
{
/**
* #var string
*/
private $key;
/**
* #var string
*/
private $iv;
/**
* #var resource
*/
private $mcrypt;
/**
* Construct the call optionally providing an encryption key
*
* #param string $key
* #return Encryption
* #throws RuntimeException if the PHP installation is missing critical requirements
*/
public function __construct($key = null) {
if (!extension_loaded ('mcrypt')) {
throw new RuntimeException('MCrypt library is not availble');
}
if (!extension_loaded ('hash')) {
throw new RuntimeException('Hash library is not availble');
}
if (!in_array('rijndael-128', mcrypt_list_algorithms(), true)) {
throw new RuntimeException('MCrypt library does not contain an implementation of rijndael-128');
}
if (!in_array('cbc', mcrypt_list_modes(), true)) {
throw new RuntimeException('MCrypt library does not support CBC encryption mode');
}
$this->mcrypt = mcrypt_module_open('rijndael-128', '', 'cbc', '');
if(isset($key)) {
$this->SetKey($key);
}
}
/**
* #return void
*/
public function __destruct() {
if (extension_loaded ('mcrypt')) {
if (isset($this->mcrypt)) {
mcrypt_module_close($this->mcrypt);
}
}
}
/**
* Set the key to be used for encryption and decryption operations.
*
* #param string $key
* #return void
*/
public function SetKey($key){
$this->key = $this->pbkdf2('sha512', $key, hash('sha512', $key, true), 1000, mcrypt_enc_get_key_size($this->mcrypt), true);
}
/**
* Encrypts data
*
* #param string $data
* #param bool $raw_output if false this method will return lowercase hexit, if true this method will return raw binary
* #return string
*/
public function Encrypt($data, $raw_output = false) {
$data = gzcompress($data, 9);
$hash = md5($data, true);
$datalen = strlen($data);
$datalen = pack('N', $datalen);
$data = $datalen . $hash . $data;
if (version_compare(PHP_VERSION, '5.3.0', '<=')) {
if (strtolower (substr (PHP_OS, 0, 3)) == 'win') {
$this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($this->mcrypt), MCRYPT_RAND);
} else {
$this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($this->mcrypt), MCRYPT_DEV_URANDOM);
}
} else {
$this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($this->mcrypt), MCRYPT_DEV_URANDOM);
}
$this->initialize();
$data = mcrypt_generic($this->mcrypt, $data);
$this->deinitialize();
$data = $this->iv . $data;
$this->iv = null;
if ($raw_output) {
return $data;
}
$data = unpack('H*',$data);
$data = end($data);
return $data;
}
/**
* Decrypts data
*
* #param string $data
* #return string This method will return false if an error occurs
*/
public function Decrypt($data) {
if (ctype_xdigit($data)) {
$data = pack ('H*',$data);
}
$this->iv = substr ($data, 0, mcrypt_enc_get_iv_size($this->mcrypt));
$data = substr ($data, mcrypt_enc_get_iv_size($this->mcrypt));
$this->initialize();
$data = mdecrypt_generic($this->mcrypt, $data);
$this->deinitialize();
$datalen = substr($data, 0, 4);
$len = unpack('N', $datalen);
$len = end($len);
$hash = substr($data, 4, 16);
$data = substr($data, 20, $len);
$datahash = md5($data, true);
if ($this->compare($hash,$datahash)) {
$data = #gzuncompress($data);
return $data;
}
return false;
}
/**
* Initializes the mcrypt module
*
* #return void
*/
private function initialize() {
mcrypt_generic_init($this->mcrypt, $this->key, $this->iv);
}
/**
* Deinitializes the mcrypt module and releases memory.
*
* #return void
*/
private function deinitialize() {
mcrypt_generic_deinit($this->mcrypt);
}
/**
* Implementation of a timing-attack safe string comparison algorithm, it will use hash_equals if it is available
*
* #param string $safe
* #param string $supplied
* #return bool
*/
private function compare($safe, $supplied) {
if (function_exists('hash_equals')) {
return hash_equals($safe, $supplied);
}
$safe .= chr(0x00);
$supplied .= chr(0x00);
$safeLen = strlen($safe);
$suppliedLen = strlen($supplied);
$result = $safeLen - $suppliedLen;
for ($i = 0; $i < $suppliedLen; $i++) {
$result |= (ord($safe[$i % $safeLen]) ^ ord($supplied[$i]));
}
return $result === 0;
}
/**
* Implementation of the keyed-hash message authentication code algorithm, it will use hash_hmac if it is available
*
* #param string $algo
* #param string $data
* #param string $key
* #param bool $raw_output
* #return string
*
* #bug method returning wrong result for joaat algorithm
* #id 101275
* #affects PHP installations without the hash_hmac function but they do have the joaat algorithm
* #action wont fix
*/
private function hmac($algo, $data, $key, $raw_output = false) {
$algo = strtolower ($algo);
if (function_exists('hash_hmac')) {
return hash_hmac($algo, $data, $key, $raw_output);
}
switch ( $algo ) {
case 'joaat':
case 'crc32':
case 'crc32b':
case 'adler32':
case 'fnv132':
case 'fnv164':
case 'fnv1a32':
case 'fnv1a64':
$block_size = 4;
break;
case 'md2':
$block_size = 16;
break;
case 'gost':
case 'gost-crypto':
case 'snefru':
case 'snefru256':
$block_size = 32;
break;
case 'sha384':
case 'sha512':
case 'haval256,5':
case 'haval224,5':
case 'haval192,5':
case 'haval160,5':
case 'haval128,5':
case 'haval256,4':
case 'haval224,4':
case 'haval192,4':
case 'haval160,4':
case 'haval128,4':
case 'haval256,3':
case 'haval224,3':
case 'haval192,3':
case 'haval160,3':
case 'haval128,3':
$block_size = 128;
break;
default:
$block_size = 64;
break;
}
if (strlen($key) > $block_size) {
$key=hash($algo, $key, true);
} else {
$key=str_pad($key, $block_size, chr(0x00));
}
$ipad=str_repeat(chr(0x36), $block_size);
$opad=str_repeat(chr(0x5c), $block_size);
$hmac = hash($algo, ($key^$opad) . hash($algo, ($key^$ipad) . $data, true), $raw_output);
return $hmac;
}
/**
* Implementation of the pbkdf2 algorithm, it will use hash_pbkdf2 if it is available
*
* #param string $algorithm
* #param string $password
* #param string $salt
* #param int $count
* #param int $key_length
* #param bool $raw_output
* #return string
* #throws RuntimeException if the algorithm is not found
*/
private function pbkdf2($algorithm, $password, $salt, $count = 1000, $key_length = 0, $raw_output = false) {
$algorithm = strtolower ($algorithm);
if (!in_array($algorithm, hash_algos(), true)) {
throw new RuntimeException('Hash library does not contain an implementation of ' . $algorithm);
}
if (function_exists('hash_pbkdf2')) {
return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
}
$hash_length = strlen(hash($algorithm, '', true));
if ($count <= 0) {
$count = 1000;
}
if($key_length <= 0) {
$key_length = $hash_length * 2;
}
$block_count = ceil($key_length / $hash_length);
$output = '';
for($i = 1; $i <= $block_count; $i++) {
$last = $salt . pack('N', $i);
$last = $xorsum = $this->hmac($algorithm, $last, $password, true);
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = $this->hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
if ($raw_output) {
return substr($output, 0, $key_length);
}
$output = unpack('H*',$output);
$output = end ($output);
return substr($output, 0, $key_length);
}
}
Example usage:
<?php
include 'AesEncryption.php';
$key = 'my secret key';
$string = 'hello world';
try
{
$aes = new AesEncryption($key); // exception can be thrown here if the class is not supported
$data = $aes->Encrypt($string, true); // expecting return of a raw byte string
$decr = $aes->Decrypt($data); // expecting the return of "hello world"
var_dump ($decr);
// encrypt something else with a different key
$aes->SetKey('my other secret key'); // exception can be thrown here if the class is not supported
$data2 = $aes->Encrypt($string); // return the return of a lowercase hexit string
$decr = $aes->Decrypt($data2); // expecting the return of "hello world"
var_dump ($decr);
// proof that the key was changed
$decr = $aes->Decrypt($data); // expecting return of Boolean False
var_dump ($decr);
// reset the key back
$aes->SetKey($key); // exception can be thrown here if the class is not supported
$decr = $aes->Decrypt($data); // expecting hello world
var_dump ($decr);
}
catch (Exception $e)
{
print 'Error running AesEncryption class; reason: ' . $e->getMessage ();
}
1.1 It's just padding. It happens with most input to base64, but not all.
1.2 No difference. Keep with base64, it's standard with encryption.
1.3 I don't see reason why it would be necessary. People sometimes solve problems at wrong places. Instead of fixing the input, they modify the output. Where's this discussion?
Definitely DO NOT use this. You change your key of some legnth to 128 bit MD5. And that is not secure.
Use asymetric encryption, if decrypting is on different machine, or different user.

Best practice for tiny code reuse in PHP

For a long time I have a problem - should I reuse small parts of code and if so, how should I do it so it would be the best practice.
What I mean about small code is for example:
if (!is_array($table)) {
$table = array($table);
}
or
$x = explode("\n", $file_content);
$lines = array();
for ($i=0, $c = count($x); $i<$c; ++$i) {
$x[$i] = trim($x[$i]);
if ($x[$i] == '') {
continue;
}
$lines[] = $x[$i];
}
Such tiny parts of code may be used in many classes in one project but some of them are used also in many projects.
There are many possible solutions I think:
create simple function file and put them all reusable piece of codes as function, include them simple in project and use them whenever I want
create traits for those piece of codes and use them in classes
reuse code by simple copy paste or creating function in specific class (??)
other ?
I think all of those solutions have their pros and cons.
Question: What method should I use (if any) to reuse such code and why is this approach the best one in your opinion?
I think that "the best way" depends on many factors including the technology your applications use (procedural, OOP), versions of PHP they run on, etc. For example, traits are interesting and useful but they are available only since php 5.4.0 so using this tool to group your code snippets you will not be able to reuse them in systems running on earlier PHP versions. On the other hand if your app uses an OOP style and you organized your resuable small code snippets in functions, their usage may seem awkward in an OOP app and conflict with the function names in a particular class. In this case I think grouping your functions in classes would seem more natural.
Putting everything together, it seems that classes provide better tool for grouping resuable code snippets in terms outline above, namely backward compatibility with earlier PHP versions, avoiding function names conflicts, etc.) Personally I code mostly in OOP, so i have a Util class where I group small functions representing resuable pieces of code snippets that do not directly relate to each other and thus could not be logically groupped in other classes.
As mentioned already traits are a good thing. But might be a bit hard to manage after a while, and it might not be supported everywhere since its new.
What I do is to create Tool classes that have a lot small static functions, like:
class ArrayTools
{
static public function CheckArray($array)
{
if (!is_array($array))
{
$array = array($array);
}
return $array;
}
}
So you can call it with ArrayTools::CheckArray($array)
Please go with traits if your code mainly involves classes and objects.. As the the concept of traits exclusively focusses on code reuse ability.
Following are the code snippets which actually I use with Plain PHP projects, these code snippets are used from various frameworks good traits and best practices.
1.
The following code is used to check the environment in which your working, based on the environment you can set the some global variables, error reporting as so on.
if(!defined('ENVIRONMENT')){
define('ENVIRONMENT','DEVELOPMENT');
}
if (defined('ENVIRONMENT'))
{
switch (ENVIRONMENT)
{
case 'DEVELOPMENT':
case 'TESTING':
$base_url = 'http://localhost/project_name/';
error_reporting(E_ALL);
break;
case 'PRODUCTION':
$base_url = 'http://hostname/project_name/';
error_reporting(0);
break;
default:
exit('The application environment is not set correctly.');
}
}
2.
/* This function is used to PRINT the ARRAY data in the pre formatted manner */
if (!function_exists('pr')) {
function pr($data) {
echo '<pre>', print_r($data), '</pre>';
}
}
3.
/* This function is used to Sanitize the user data and make data safe to insert into the database */
function sanitize($data) {
global $link;
$data = trim($data);
return htmlentities(strip_tags(mysqli_real_escape_string($link, $data)));
}
4.
/* Used to get the difference of 2 arrays
Returns the array with difference
*/
function multi_diff($arr1,$arr2){
$result = array();
foreach ($arr1 as $k=>$v){
if(!isset($arr2[$k])){
$result[$k] = $v;
} else {
if(is_array($v) && is_array($arr2[$k])){
$diff = multi_diff($v, $arr2[$k]);
if(!empty($diff))
$result[$k] = $diff;
}
}
}
return $result;
}
5.
/* This fnction is used to generate the random keys of specific length
Accepts parameter of certain length if not specified it will generate 20 bit length automatically
*/
function generate_random_key($length = 20) {
//Initializing the varialble
$keystring = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';
$random_key = '';
for ($i = 0; $i < $length; $i++) {
$random_key.=$keystring[rand(0, strlen($keystring) - 1)];
}
//Return the randomly generated key
return $random_key;
}
6.
/* This function outputs the errors in ul>li format with unstyled
* To get the bullets styling remove class='list-unstyled' in <ul> tag */
function output_errors($errors){
$output = array();
foreach ($errors as $error) {
$output[] = '<li>'.$error.'</li>';
}
return '<ul class="list-unstyled">'.implode('', $output).'</ul>';
}
7.
/* Checks whether the user is loggedin else will redirect to the protectect page */
function protected_page(){
if(is_loggedin() === false){
// header('Location: protected.php');
header('Location: logout.php');
exit();
}
}
8.
/* If user tries to access the page directly accessing through the URL,
* If already loggedin then redirect him to any of the inner page
*/
function login_redirect(){
if(is_loggedin() === true){
header('Location: home.php');
}
}
9.
/* This function is used to check whether the user exists or not */
function email_exists($email){
/* Your Code */
}
/* This function is used to check whether the user isActive or not */
function is_active($email){
/* Your Code */
}
/* This function will get the userid from the email */
function userid_from_email($email) {
/* Your Code */
}
/* This fucntion is used to login the user based on the email-id and password */
function login($email,$password){
/* Your Code */
}
/* Check whether the USER is loggedin or not */
function is_loggedin(){
return (isset($_SESSION['userid'])) ? true : false;
}
Hope this helps you. Cheers!

Categories