Generating one-time tokens via mcrypt in php? - php

i will be providing api keys to my partner sites and they will be using code that i give them to to generate "tokens".
these tokens will be automatically present on forms which the partner sites' users will click on and reach my site. when they reach my site, i will need to validate that they indeed came from a partner site.
how do i validate this? the apikey will be secret, but what is presented in the form will NOT be, so it must not be possible for smart users to reverse engineer my algorithm.
EDITA
Option1: I get teh client page to send across md5($apikey.$time) AND $time (in plaintext). When i get it, i use time and my copy of apikey to generate md5($apikey.$time). if it matches and is within 1 hour (or whatever), i let the request proceed.
Option2: I already have $userid, $requestcommandoption coming in as well. I can do the following:
$input = $userid.'-'.$requestcommandoption.'-'.$time;
$encrypted_data = mcrypt_ecb (MCRYPT_3DES, $apikey, $input, MCRYPT_ENCRYPT);
when i get it at my end, i can do:
$decrypted_data = mcrypt_ecb (MCRYPT_3DES, $apikey, $encrypted_data, MCRYPT_DECRYPT);
and then check the 2 inputs if they are the same, and the 3rd if its within 1 hour?
EDITB
How secure does this sound? (code borrowed from http://onlamp.com/pub/a/php/2001/07/26/encrypt.html?page=3)
// on client
$apikey="test123";
$userid = '577';
$requestcommandoption = 'delete-all';
$time = mktime();
echo "time = $time<p>";
$input = $userid.'-'.$requestcommandoption.'-'.$time;
// Encryption Algorithm
$cipher_alg = MCRYPT_RIJNDAEL_128;
// Create the initialization vector for added security.
$iv = mcrypt_create_iv(mcrypt_get_iv_size($cipher_alg, MCRYPT_MODE_ECB), MCRYPT_RAND);
// Encrypt $string
$encrypted_string = mcrypt_encrypt($cipher_alg, $apikey, $input, MCRYPT_MODE_CBC, $iv);
$transmitted = bin2hex($encrypted_string);
// sent from client to server
print "Encrypted string: ".$transmitted."<p>";
// received on server
$encrypted_string = pack("H*" , $transmitted);
$decrypted_string = mcrypt_decrypt($cipher_alg, $apikey, $encrypted_string, MCRYPT_MODE_CBC, $iv);
print "Decrypted string: $decrypted_string";

Looks as if you're implementing something similar to open authentication - the process twitter/facebook etc use for enabling partner sites.
I'd recommend that you take a look at oAuth - http://oauth.net/ - there are plenty of libraries and php samples.
If you really want to do something simple, then assuming you've got a record of the API keys you've handed out, I would write the client script so that it makes an md5 hash of the key with another bit of information on the form - a username for example (lets call the hashed string the request key and the username the username), and I'd include an identifier for the partner (which we'll call partner_id).
So when the form submits it has the requestkey, username and partner_id.
When your server receives the request you can look up the secret key for the partner using the partner_id, then md5 the secret key you've got, with the supplied user name and see if it matches the md5 key sent with the form.
#frank...
[Adding this as a result of your comment]
In order to make the key that is sent over the wire disposable, you can get the client web page to request a temporary session key - your server generates one (using a combination of date + time + a secret word), saves it as a temp key in the partners table (alongside their permanent key) and sends it back to the client. The client app then MD5's this with the permanent key and submits this with the form. You then look up the permanent and temp keys and hash them together and compare the result with the hash you've been sent.
does that seem ok?

What about GUIDs? Of course, you would have to track the issued GUIDs.
http://php.net/manual/en/function.com-create-guid.php

You should check out OAuth and OAuth 2 standards. They're widely used for authorization and many APIs (Facebook, Twitter, etc.)

Related

Is openssl_encrypt secure enough to create a session token?

The idea is to encrypt a string containing session data (username and token expiry) and store it in a cookie. Whenever an HTTP request comes from the client, the backend checks if it can decrypt the token in the cookie and then queries the DB to check if the username exists and is allowed to access the target resource:
// create token
$token = openssl_encrypt($username . "::" . $expiry, "rc2-40-cbc", $key, 0, "00000000");
// then store in cookie
'::' is just a delimiter.
// on http request, verify token
$decryptedToken = openssl_decrypt($token, "rc2-40-cbc", $key, 0, "00000000");
if($decryptedToken != false) // token is valid
// extract data
$tokenValues = explode("::", $token);
$username = $tokenValues[0];
$tokenExpiry = $tokenValues[1];
// then validate username and expiry
Is this safe?
Your code is absolutely insecure for multiple reasons. Please do not implement crypto without a basic understanding of what you are doing.
Using your method, the session data in the db would be cracked in less than a day with hardware that you have at home.
A few problems:
rc2-40-cbc is very, very weak
it is not an authenticated encryption
the IV is not for fun, you can't just put in zeroes because that worked
the result will not fit in a cookie if there is enough session data
the same session data would be encrypted to the same ciphertext
...
Please note that the solution is not changing the cipher and adding an IV. The solution is to use a well-known and tested library that does this for you.
The concept btw could work, but it has to be correctly implemented.

Create auth token for android with php

I want to use authentication token, after various searches I found JWT token and another token generation method with md5.
Off for various reasons I do not want to use the token JWT and md5 seems to me to have a bad reputation.
So I found a method but I would like your opinion:
Generating a Byte String: https://www.php.net/manual/en/function.random-bytes.php
Then I convert that into Hexadecimal.
Finally, I concatenate that with the id of my user and salting.
I send this to my application (Android or web) than during a request to my service I decode the received token, and I will see the token contained in the id hidden in the token.
So it is harder to find the token by brute force?
Note 1: that I would like to refresh the token by the future but for the moment it should be issued for an indefinite period.
Note 2: I do not want to use a library or framework just in php.
Code :
$bytes = random_bytes(32);
$part=bin2hex($bytes));
$shortpart=substr($part,0,17);
$id = #userid
$salt=#customsalt
$token=shortpart.$id.$salt;
Example :
$shortpart =f2e4d1f2a2dfedcf5
$id=1
$salt=4d2ze121
$token=f2e4d1f2a2dfedcf514d2ze121
The user or hacker doesn't know is id is hidden in the token.
After some research I think the better method is:
$token = bin2hex(openssl_random_pseudo_bytes(32));
And custom this string.
Add one column in database table for time and calculate authtoken as:
$time = time();
and store this to database.
Encryption:
$encrypt = base64_encode($time.userid);
Decryption:
Get the time from table:
$decrypt = base64_decode($encrypt);
Check this link.

How to validate a token without a database?

I have hosted two domains on the same server, domain A and domain B.
Domain A will generate the unique access token to the content of domain B.
Domain A
<?php
//http://php.net/manual/en/function.phpversion.php
//echo 'Version of PHP: ' . phpversion();
session_start();
//$expiry_timestamp = time() + $expiry;
//https://davidwalsh.name/random_bytes //https://secure.php.net/random_bytes
//$token = bin2hex(random_bytes(64));
$token = bin2hex(openssl_random_pseudo_bytes(64));
//$time_token = 12000;
//$time_token = srand(floor(time() / $time_token));
//echo $token;
$_SESSION['token']=$token;
?>
<html>
<head>
</head>
<body>
Content 1
</body>
</html>
The process of generating a token seems to be the right one, it has been easy to generate it.
Now comes my problem, how can I validate the generated token from domain A to domain B ?. The generated token must only be valid for the content that generated the token, the token must not be valid for other content, the token must be unique so that user can not share access to another user if it is not from his or her computer, the token must be valid only for 4 hrs of access after 4 hrs the token will no longer be valid to display the content must generate a new token to access again.
Can this process be done using a cookie without using a database?
Maybe identifying both domains A and B using a key, something like that
$APP_SECRET_KEY = "key code secret";
Using a shared secret key is a good approach here.
I tend to use HMAC when I need to generate and validate a token (e.g.: E-Mail verification) and don't want to store it in a DB. Plus, HMAC is built in to PHP, so no library is needed here.
The idea is, on top of your data, you add a signature to verify that this token was created by your application on Domain A. You generate the token the same way again on Domain B to verify it.
Example:
Shared function to generate the token:
function buildVerificationToken($expires, $content)
{
// Same function on both domains
$APP_SECRET_KEY = 'key code secret'; // Maybe move that out of source code
$tokenData = [
'expires' => $expires, // Include it in signatur generation to prevent user from changing it in URL
'content' => $content, // Create different token for different content
'ip' => $_SERVER['REMOTE_ADDR'], // Identify the browser to make it not shareable. Best approach I could think of for this part.
];
$serialized = json_encode($tokenData);
return hash_hmac('sha256', $serialized, $APP_SECRET_KEY);
}
Generate the token on Domain A:
<?php
$expires = time() + (4 * 3600); // +4h
?>
Content 1
Verify it on domain B:
$providedExpires = (int) $_GET['expires'];
$providedToken = $_GET['token'];
$verificationToken = buildVerificationToken($providedExpires, 'content1'); // Build token the same way
if (!hash_equals($verificationToken, $providedToken)) { // hash_equals instead of string comparison to prevent timing attacks
// User provided forged token, token for another content, or another IP
die('Bad token'); // However you want to handle this
}
if (time() > $providedExpires) { // Check expiry time. We can trust the user did not modify it as we checked the HMAC hash
die('Token expired'); // However you want to handle this
}
// User is allowed to see content1
Json Web Token (JWT) seems to fit your requirements. Both applications use one secret key to exchange tokens with encrypted data one another.
Example use-case:
Let the secret key $secret="secret"
The raw data tells us the Unix timestamp when the token is generated (the iat field), the user id (the sub field) and the content id (the content field)
$data = [
"sub" => "1234567890",
"iat" => 1516239022,
"content" => 1
];
Application A encodes the raw data with the secret key using HS256 algorithm ($token = jwt_encode($raw, 'HS256', $secret)). The output $token will be:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJjb250ZW50IjoxfQ.idM7d2fgmJVk3WjANwG-Gt6sY0lyE3eTvpKRpwITHRs
You can parse the token to see its content in the JWT home page.
The token is send to the application B. This application decodes the token with the same algorithm and shared secret key ($raw = jwt_decode($token, 'HS256', $secret)). The raw data will be available in the application B. This data can be used to validate the token:
Read the user id from sub field and check if it is correct
Read the content id from content field and check if it is correct
Read the timestamp when the token is generated from the iat field and check if it is in the last 4 hours.
There are several PHP libraries implement JWT for you.

OpenID Access token validation in PHP

Im trying to make a SSO validation on thinktecture OpenId connect in PHP
I have written a client and get an access token. BUT i cannot find out how to validate it.
The documentation says:
3.2.2.9. Access Token Validation
To validate an Access Token issued from the Authorization Endpoint with an ID Token, the Client SHOULD do the following:
Hash the octets of the ASCII representation of the access_token with
the hash algorithm specified in JWA [JWA] for the alg Header
Parameter of the ID Token's JOSE Header. For instance, if the alg is
RS256, the hash algorithm used is SHA-256.
Take the left-most half of the hash and base64url encode it.
The value of at_hash in the ID Token MUST match the value produced
in the previous step.
I have no idea how to make step 1.
I got the ALg as RS256 and i have the at_hash from the Id token, i just cant find an exampel in PHP on how to do the validation.
Here is an example in PHP to calculate the at_hash value that should be easy to adapt to your environment:
public function setAccessTokenHash($accessTokenString)
{
// bit : 256/384/512
if(isset($this->_header['alg']) && $this->_header['alg'] != 'none'){
$bit = substr($this->_header['alg'], 2, 3);
}else{
// TODO: Error case. throw exception???
$bit = '256';
}
$len = ((int)$bit)/16;
$this->_payload['at_hash'] = Akita_OpenIDConnect_Util_Base64::urlEncode(substr(hash('sha'.$bit, $accessTokenString, true), 0, $len));
}

CI REST Server API keys

I am a newbie to API development, but have successfully managed to implement CI REST Server by Phil Sturgeon and Chris Kacerguis by reading all articles I could find, but there is one answer that eludes me, from the following question: CodeIgniter REST API Library Ajax PUT throwing 403 Forbidden and How can I generate an API Key in My own Controller in Codeigniter.
I have added the "boguskey" to the database as suggested in the first question's accepted answer, but I am confused about security here. If I need to have a hard-coded API key to generate new keys, and someone can view the header to see this bogus API key, how do I secure my API then from someone who then use this API key to generate tons of API keys for us within my API? If I do not add the boguskey, then I get "Invalid API key" regardless of which function I call.
My apologies if this is a stupid question, but if someone has an example of how I can generate keys securely (or at least inform me if I am misinterpreting the situation) I will greatly appreciate it.
To ensure the max security you should encrypt all the sent data, then if the API could decrypt it correctly you should be fine, you can use RSA encryption, so if any one intercept the request he cant decrypt or clone it, But RSA is not designed to be used on long blocks of plain text, so you can use hybrid encryption. Namely, this involves using RSA to asymmetrically encrypt a symmetric key.
Randomly generate a symmetric encryption (say AES) key and encrypt the plain text message with it. Then, encrypt the symmetric key with RSA. Transmit both the symmetrically encrypted text as well as the asymmetrically encrypted symmetric key.
The API can then decrypt the RSA block, which will yield the symmetric key, allowing the symmetrically encrypted text to be decrypted.
To implement RSA on CodeIgniter you can use this class, call the file on your controller require_once("RSA.php");.
On the API consumer controller make an array which will contain the data and the the asymmetrically encrypted symmetric key
$request_data = array();
$request_data["username"] = "taghouti";
$request_data["project"] = "Secured_API";
$serialized_request_data = serialize($request_data);
$enc = new RSAEnc($serialized_request_data,'public_key');
$encrypted = $enc->result();
$request_data = array(
"data" => base64_encode($encrypted->result),
"key" => base64_encode($encrypted->key)
);
And on the API controller you should try to decrypt the symmetric key using your private key, if the decryption done successfully you should be fine
if ($_POST["key"]) {
$key = base64_decode($_POST["key"]);
$_POST["key"] = null;
if (isset($_POST["data"])) {
$data = base64_decode($_POST["data"]);
$dec = new RSADec($data, 'private_key', $key);
$decrypted = $dec->result();
if($decrypted->success !== true) die("Decryption failed");
$decrypted = #unserialize($decrypted->result);
$_POST = is_array($decrypted) ? $decrypted : array();
$this->_post_args = $_POST;
}
}
if($this->input->post('project') && $this->input->post('username')) {
//Enjoy
} else {
die('data parsing error');
}

Categories