Account activator link is a use case for HMAC? - php

I'm using PHP and need to design the activator link
$data = $user->email;
$key = $user->token; // bin2hex(openssl_random_pseud_bytes(16));
$hmac = hash_hmac( 'sha256', $data, $key );
So, would send to user the follow url
hostname/account/confirm/7c7b0f24eff74902cb07e900b07a0cafc8fccfa5d2704fb92aaf3b91e9774f98
This way is good idea?
If true, sha256 is best choice for hash_hmac() on this situation? Particularly, i would appreciate one string with length <= 24

TL;DR: You don't need to use hash_hmac() for this use case. You can use the token directly.
If $user->token is generated securely (n.b. you want random_bytes(), not openssl_random_pseudo_bytes(), due to collisions in OpenSSL's random number generator), then it's a secure random value and an attacker has no way of knowing it without hacking the table.
Where you'd want to use HMAC
If you want to authenticate some cleartext data sent to your user's email address, you could use HMAC for this purpose.
For example: On registration, if you need to present users with an option (e.g. GDPR consent), you can create the following two URLs tied to the same token, like so:
$hmacYes = hash_hmac('sha256', 'yes', $user->token);
$hmacNo = hash_hmac('sha256', 'no', $user->token);
Then you can have two links for each option, and send the links like so:
* `hostname/account/confirm/yes${hmacYes}`
* `hostname/account/confirm/no${hmacNo}`
And then you can parse out the HMAC value:
if (!preg_match('/^(yes|no)(.*)$/', $urlParam, $matches) {
throw new SecurityException('bad url');
}
$choice = $matches[1];
$hmac = $matches[2];
$recalc = hash_hmac('sha256', $choice, $user->token);
if (!hash_equals($recalc, $mac)) {
throw new SecurityException('bad url');
}
// Now you know $choice is from a genuine user email
However, if you only need challenge-response authentication, then a secure random token is sufficient.

Related

Is this a good approach to "Obfuscate" User ID?

I need to provide users with a unique link that contains their USER ID (please do not suggest usage of UUID etc in this case)
It's not that important, but I still rather make sure it's very difficult to extract the user id or guess the next one etc! (even if it's achieved with security by obscurity...)
I came up with this solution:
// #var $id int|string
function obfuscate_number($id, bool $reverse=FALSE)
{
$changing = (int)substr($id, -1);
$multiplier = '45' . $changing;
$base = 25;
// Obfuscate Number
if($reverse === FALSE)
{
$new = bcmul("$id", "$multiplier", 0);
$convert = bcadd("$new", "$changing", 0);
$obf = base_convert($convert, 10, $base) . $changing;
return $obf;
}
// Reverse to Number
else
{
$deobf = base_convert(substr($id, 0, -1), $base, 10);
$convert = bcsub("$deobf", "$changing", 0);
// Simple Validation
if($convert % $multiplier !== 0) return FALSE;
$number = (int)bcdiv("$convert", "$multiplier", 0);
return $number;
}
}
// For example number 123456 => 5dnpfi6
// After reversing 5dnpfi6 => 123456
// For example number 563 => g81h3
// After reversing g81h3 => 563
If it is possible please help me improve it.
Also I think the chance of collision is 0 here, am I correct?
Possible Solutions
Slow
Hashing, hashing will allow you to send the hash out and without an extremely moderate amount of computations will not be put reversed into the user id. The server would hash, send string to client, client visits webpage, you lookup in a database of some sort matching hash with user id.
Fast (Recommended)
Using AES encryption will allow you to encrypt data that is generally guaranteed to be unbreakable if you follow AES guidelines. So an approach would be to encrypt data with AES and convert to base 64. Send the base64 to the user and when the user clicks the link, you simply just need to convert base 64 to binary, and decrypt. I would say this is considerably faster than the hashing approach.

php two-way encryption for passing variables through URL

OK... to the point...
I have a e-mailshot program that sends out thousands of emails - each with str_replace() merge fields (fields that get replaced by a row value in a recordset).
One of the important parts is my ability to track those mails on open so I include a single server-side generated pixel...
<img src="http://...trace.php?email=<<<EMAIL>>>" alt="" height="1" width="1">
The str_replace() replaces <<<EMAIL>>> with a unique real email address.
The trace.php file reads the $_GET['email'] and either logs it or sends mail confirmation.
My issue is security :)
I want to use two-way encryption so that the $_GET variable sent in the URL is an encrypted email. The trace.php file then needs to decrypt it.
As it's being sent in a URL, It has to be in ASCII format otherwise it will corrupt before decrypting.
I can't use openssl_encrypt() & openssl_decrypt() and I'm having to work with php 5.2.0 (don't hurl abuse at me!).
Any help would be greatly appreciated!!
While many of the comments you have received offer other valid ways of solving the problem e.g. a table of email addresses with primary keys, I am of the position that the best way to solve the problem is the way you originally intended: including the email address encrypted in the query URL.
I feel that this way is better because:
Computing the email address does not require database access. Database bottle-necking is generally the biggest offender for high-latency requests.
Encryption means that the same email address will produce a different IV/ciphertext pair each time you encrypt it. Thus, if you send multiple emails at different times (say, for two different marketing campaigns), the URL will be different each time. This may not have an effect, but it does provide a security advantage in that an attacker can't "pretend" that an email has been opened simply by visiting a URL.
The issue is that for this way to be better, you have to do it well. I've included an excerpt in PHP from this repository below. If you can't use openssl_* then upgrade your PHP version. Do not, ever, use the mcrypt_ functions. They are deprecated for a reason. You may need to hex encode instead of base64 encode the email addresses as is done in the example below.
<?php
define("ALGORITHM_NAME", "aes-128-gcm");
define("ALGORITHM_NONCE_SIZE", 12);
define("ALGORITHM_TAG_SIZE", 16);
define("ALGORITHM_KEY_SIZE", 16);
define("PBKDF2_NAME", "sha256");
define("PBKDF2_SALT_SIZE", 16);
define("PBKDF2_ITERATIONS", 32767);
function encryptString($plaintext, $password) {
// Generate a 128-bit salt using a CSPRNG.
$salt = random_bytes(PBKDF2_SALT_SIZE);
// Derive a key.
$key = hash_pbkdf2(PBKDF2_NAME, $password, $salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, true);
// Encrypt and prepend salt and return as base64 string.
return base64_encode($salt . encrypt($plaintext, $key));
}
function decryptString($base64CiphertextAndNonceAndSalt, $password) {
// Decode the base64.
$ciphertextAndNonceAndSalt = base64_decode($base64CiphertextAndNonceAndSalt);
// Retrieve the salt and ciphertextAndNonce.
$salt = substr($ciphertextAndNonceAndSalt, 0, PBKDF2_SALT_SIZE);
$ciphertextAndNonce = substr($ciphertextAndNonceAndSalt, PBKDF2_SALT_SIZE);
// Derive the key.
$key = hash_pbkdf2(PBKDF2_NAME, $password, $salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, true);
// Decrypt and return result.
return decrypt($ciphertextAndNonce, $key);
}
function encrypt($plaintext, $key) {
// Generate a 96-bit nonce using a CSPRNG.
$nonce = random_bytes(ALGORITHM_NONCE_SIZE);
// Encrypt and prepend nonce.
$ciphertext = openssl_encrypt($plaintext, ALGORITHM_NAME, $key, OPENSSL_RAW_DATA, $nonce, $tag);
return $nonce . $ciphertext . $tag;
}
function decrypt($ciphertextAndNonce, $key) {
// Retrieve the nonce and ciphertext.
$nonce = substr($ciphertextAndNonce, 0, ALGORITHM_NONCE_SIZE);
$ciphertext = substr($ciphertextAndNonce, ALGORITHM_NONCE_SIZE, strlen($ciphertextAndNonce) - ALGORITHM_NONCE_SIZE - ALGORITHM_TAG_SIZE);
$tag = substr($ciphertextAndNonce, strlen($ciphertextAndNonce) - ALGORITHM_TAG_SIZE);
// Decrypt and return result.
return openssl_decrypt($ciphertext, ALGORITHM_NAME, $key, OPENSSL_RAW_DATA, $nonce, $tag);
}
?>

How to encrypt data on the post with php

I read some post how to encrypt data over post HTML with PHP. But I can't really figure out how it must be done.
I explain my easy example.
I have a dashboard where users can see their installations. Think about lots of installations and each one belongs to different users. Some has one installation some has five installation.
The fact is that i develop a button to download Excel info from their installation.
I've done a contactosDownload.php that $_GET['idInstalacion'] so on the main dashboard the button looks like:
<a href="contactosDownload.php?idInstalacion=
<? echo $idInstalacionPOST ?>"
class="btnDownload btn-success btn btn-info pull-left">
<i class="fa fa-download"></i>&nbspDescargar
</a>
It is working perfectly and when the button is pressed the contactosDowload gets idInstalacion and does a mysql select and then an Excel with the information of the instalation is downladed. PHPExcel does the job perfectly.
Even because this users that has three installations can select one of this installations and info is updated with Ajax I have a jquery update:
$('#InstaSelect').change(function(e) {
e.preventDefault();
.....
$('.btnDownload').attr("href", "contactosDownload.php?idInstalacion="+idInstalacion);
....
});
That is working as well.
Like all of you are thinking send the id is not really clever idea.
Even because if someone get the link can start to retrieve data with:
http://www.aaaaa.com/contactosDownload.php?idInstalacion=1
http://www.aaaaa.com/contactosDownload.php?idInstalacion=2
and so one.
What is the best choice to uncrypt and decrypt the information and make secure the post info ?
Thanks
EDIT:
I forgot to tell that the users are authorized whith their user and password. And I store with var session all the user when they loggin.
$user_browser = $_SERVER['HTTP_USER_AGENT']; // Get the user-agent string of the user.
$dbid = preg_replace("/[^0-9]+/", "", $dbid); // XSS protection as we might print this value
$_SESSION['user_id'] = $dbid;
$_SESSION['login_string'] = hash('sha512', $tContra.$user_browser);
$_SESSION['ultimoAcceso'] = date("Y-n-j H:i:s");
$_SESSION['tTipoUsuLogged'] = $tTipo;
$_SESSION['tRolUsuLogged'] = $tRolUsuario;
$_SESSION['tEmail'] = $tUseNam;
and each php has (include 'validateSesion.php') that validates the user and tiemout.
So now I introduce this validation on the contactosDownload and if the user is not identified /index.php (login) appears.
First level got.
Now i have to encode the id on the post and check that the user form the SESSION has privileges to download the installation.
What about to encryp'/decrypt id with:
function encryptor($action, $string) {
$output = false;
$encrypt_method = "AES-256-CBC";
//pls set your unique hashing key
$secret_key = 'long logn text secret';
$secret_iv = '1a2b3c4d5e';
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$iv = substr(hash('sha256', $secret_iv), 0, 16);
//do the encyption given text/string/number
if( $action == 'encrypt' ) {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
}
else if( $action == 'decrypt' ){
//decrypt the given text/string/number
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return $output;
}
First thing is to make sure your URL is encoded, so that it's not so easily readable.
Second suggestion i'd make is to use a honeypot.
Lastly, if the data is particularly sensitive (like financial data), then i'd recommend moving away from the ID entirely, and use a hash that you can map to an ID behind the scenes.

How to secure an authentication cookie without SSL

I am in the process of creating a login system which uses both sessions (for those who disallow the use of cookies (to agree with the cookie law.. I am using the site http://www.cookielaw.org/the-cookie-law.aspx as a reference)
Now, I have this system for my cookie authentication
function GenerateString(){
$length = mt_rand(0,25);
$characters = '0123456789abcdefghijklmnopqrstuvwxyz';
$string = '';
for ($p = 0; $p < $length; $p++) {
$string .= $characters[mt_rand(5, strlen($characters) -1)];
}
return $string;
}
$RandomString = GenerateString();
$CookieAuth = $DB->prepare("INSERT INTO cookieauth (Username,RandomString) VALUES (?,?)");
$CookieAuth->bind_param('ss',$_POST['Username'],$RandomString);
$CookieAuth->execute(); // Insert the Authentication Methods into the database
$CookieAuth->close(); // Allow another query/statement
$GetInsertID = $DB->prepare("SELECT ID FROM CookieAuth WHERE RandomString=?");
$GetInsertID->bind_param('s',$Randomstring);
$GetInsertID->execute();
$GetInsertID->bind_result($RowID);
$GetInsertID->fetch();
$GetInsertID->close();
setcookie("Auth[ID]",$RowID);
setcookie("Auth[UName],$_POST['Username']);
setcookie("Auth[RandomString]",$RandomString);
Then to process the cookie:
if(isset($_COOKIE['Auth'])){
$Authenticate = $DB->prepare("SELECT Username,RandomString FROM cookieauth WHERE ID=?");
$Authenticate->bind_param('i',$_COOKIE['Auth']['ID']);
$Authenticate->execute();
$Authenticate->bind_result($RowUsername,$RowString);
$Authenticate->fetch();
$Authenticate->close();
if ($_Cookie['Auth']['UName'] == $RowUsername){
if ($_COOKIE['Auth']['RandomString'] == $RowString){
header("Location: LoggedIn.php");
}else{
die("Possible Cookie Manipulation, Autologin Cannot Continue");
}
}else{
die("Possible Cookie Manupulation, Autologin Cannot Continue!");
}
My overall objective is to provide an auto login feature by using cookies. As people should know they are essentially stored on the hard drive as plain text.. So If i include a randomly generated string which will be changed on further processing each time (then updating the cookie to match the database) is this a reasonably secure way to achieve the task? I mean, I understand that this is not 100% secure due to some users might attempt to manipulate the random string, so I could resort to a salt, random key then use hash_hmac to sha512 the salt+key and save that as the cookie...
My overall question, is the chunks I have provided a semi-secure method to process automatic logins via cookies and can minimize the possibility of some bad guys manipulating the keys to achieve the required data?
Introduction
Why do you want to authenticate cookie when that is exactly what sessions are going ? If you want to change the ID you can easily achieve that with session_regenerate_id as #MarcB has pointed.
My Assumptions
I want to assume i did not understand the question clearly and probably this is what you want to achieve
Store Values to Cookie
Know if such values have been modified
You solved it already
I could resort to a salt, random key then use hash_hmac to sha512 the salt+key and save that as the cookie...
That is exactly the the solution but you need to note that
Session Can Sill be hijacked
PHP has better ways of generating random strings
Imagine the overhead having to updated your mysql table every time for something sessions can easily do for you
using hash_hmac 512 would generate 126 in hex format you need to understand that there is Browser Cookie Limits so i suggest you reduce it to 256
Your Solution Modified
If we are going to use your solution we need some little modification
session_start();
// Strong private key stored Securly stored
// Used SESSION For demo
$privateKey = isset($_SESSION['key']) ? $_SESSION['key'] : mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
$my = new SignedCookie($privateKey);
$my->setCookie("test", "hello world", time() + 3600);
echo $my->getCookie("test");
Output
hello world
But the Data was stored like this :
This just uses hash_hmac to sign and verify your values and also uses a random variable to make sure the bad guys are not able to build table of possible values because really they don't have to break the hash .. the can just study it can also use a valid one previously used eg.
10 Cookies = AAAA
1 Cookie = BBBB
He can login with valid session and changed cookies from BBBB to AAAA so even if you re not storing to database always include a random argument
You can also still remove the cookies like this :
$my->setCookie("test", null, time() - 3600);
Simple Class Used
class SignedCookie {
private $prifix = '$x$';
private $privateKey;
function __construct($privateKey) {
$this->privateKey = $privateKey;
}
function setCookie($name, $value, $expire, $path = null, $domain = null, $secure = null, $httponly = null) {
$value = $value === null ? $value : $this->hash($value, mcrypt_create_iv(2, MCRYPT_DEV_URANDOM));
return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
}
function getCookie($name, $ignore = false) {
if (! isset($_COOKIE[$name]) || empty($_COOKIE[$name]))
return null; // does not exist
if ($ignore === false) {
if (substr($_COOKIE[$name], 0, 3) !== $this->prifix)
return - 1; // modified
$data = pack("H*", substr($_COOKIE[$name], 3)); // Unpack hex
$value = substr($data, 32, - 2); // Get Value
$rand = substr($data, - 2, 2); // Get Random prifix
if ($this->hash($value, $rand) !== $_COOKIE[$name])
return - 1; // modified
return $value;
}
return $_COOKIE[$name];
}
function hash($value, $suffix) {
// Added random suffix to help the hash keep changing
return $this->prifix . bin2hex(hash_hmac('sha256', $value . $suffix, $this->privateKey, true) . $value . $suffix);
}
}
Conclusion
You are not a security expert Just Use so just use SSL (SSL also has its issues but far better) or Look for an existing secure authentication service. #ircmaxell reminded me of Schneier's Law recently :
#Baba: "surprise" is the enemy of security. The ONLY thing that should be secret is the private key. Remember Schneier's Law: Anyone can invent an encryption scheme that they themselves can't break. My answer is based on tried and true cryptographic principles.
well i think you should take to that advice too.

PHP - Create random hash string for email closed loop verification

I am currently working on a project which requires closed loop email verification. As part of the process I need to generate a random hash string which can be appended to a link sent to the user. When they click the link they will be directed to my site at which time the app will confirm the hash and complete the registration process. For all my hashing I have been using:
hash('sha256', $string);
But for this process, I need to seed $string with a random value. I have Zend Framework available and was looking to do something like this:
$crypt = new Zend_Filter_Encrypt_Mcrypt(array());
$hash = hash('sha256', $crypt->getVector());
My question is, is this a viable algorithm for generating random hash codes?
Here is the Zend_Filter_Encrypt_Mcrypt::setVector() method (generates the value returned via getVector():
public function setVector($vector = null)
{
$cipher = $this->_openCipher();
$size = mcrypt_enc_get_iv_size($cipher);
if (empty($vector)) {
$this->_srand();
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
$method = MCRYPT_RAND;
} else {
if (file_exists('/dev/urandom') || (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')) {
$method = MCRYPT_DEV_URANDOM;
} elseif (file_exists('/dev/random')) {
$method = MCRYPT_DEV_RANDOM;
} else {
$method = MCRYPT_RAND;
}
}
$vector = mcrypt_create_iv($size, $method);
} else if (strlen($vector) != $size) {
require_once 'Zend/Filter/Exception.php';
throw new Zend_Filter_Exception('The given vector has a wrong size for the set algorithm');
}
$this->_encryption['vector'] = $vector;
$this->_closeCipher($cipher);
return $this;
}
I'm not very familiar with ZF, but something that has the word Encrypt in it just sounds like the wrong approach.
The ->getVector() sounds similar to what the Initialization Vector does in symmetric encryption; the problem is that such a vector doesn't need to be cryptographically safe, just random. For instance, it may well be just implemented as uniqid(mt_rand()) or something.
->getVector() uses mcrypt to first initialize the encryption cipher to know how big the IV should be; this is typically 8 bytes, but it largely depends on the block size of the used cipher. The thing is, you're not encrypting anything; you just want a random sequence.
The better way to get a random sequence is by using openssl_random_pseudo_bytes() with a size of 8 bytes.
In its absence, you could also read from an entropy file such as /dev/random or /dev/urandom. Afterwards you can run it through binh2hex() to generate a hexadecimal string.
Something like this is pretty rudimentary but should work on Linux'y systems:
$rnd = bin2hex(file_get_contents('/dev/urandom', false, null, 0, 8));
As a fallback for Windows, you can still use something like:
$rnd = hash('sha256', uniqid(mt_rand(), true));
You may want to look into ircmaxell's CryptLib which has a fairly comprehensive suite of random generation features. If you use the medium strength random string generator, like so:
$generator = ( new CryptLib\Random\Factory() )->getMediumStrengthGenerator();
$string = $generator->generateString(LENGTH);
The library will use multiple cryptographically secure sources and run them through a mixer to generate a string. It's worth checking into if you just want a simple solution and don't want to recompile PHP with openssl.
See the readme on secure string generation.

Categories