I'm trying to change a users password inside an Microsoft active directory using php-ldap. The problem is when trying to change the password with ldap_mod_replace it's not changing but resetting the password and that's not what I want, because my users are not permitted to reset their own password.
The active directory is based on a Microsoft server 2016, my application is running on an IIS webserver using PHP 7.2.
// open LDAP connection
$ldap_connection = $this->connectToActiveDirectory();
if (#ldap_bind($ldap_connection, $this->ldap_username . '#' . env('LDAP_DOMAIN', 'localhost'), $request->oldPassword)) {
// LDAP connection established
$dn = $request->userdn; // distinguished name of user
/*
The DC requires that the password value be specified in a UTF-16 encoded Unicode
string containing the password surrounded by quotation marks, which has been BER-encoded
as an octet string per the Object(Replica-Link) syntax.
*/
$newPassword = "\"" .$request->password. "\"";
$utf16Password = ""; // converted password
$passwordLength = strlen($newPassword);
for ($i = 0; $i < $passwordLength; $i++) {
$utf16Password .= "{$newPassword{$i}}\000";
}
$passwordEntry = array('unicodePwd' => $utf16Password);
// Set new password
if(#ldap_mod_replace($ldap_connection, $dn, $passwordEntry)) {
// Successful
} else {
// Error, probably not enough permissions
return back(); // Redirect user to previous page
}
ldap_unbind($ldap_connection); // Close LDAP connection
return redirect('/logout'); // Redirect user to logout
}
I want to change the password and not reset it, and I can't find a solution. Maybe some of you have experienced this problem, I'm thankful for any help!
According to the documentation for the unicodePwd attribute, this is how you do it:
If the Modify request contains a delete operation containing a value Vdel for unicodePwd followed by an add operation containing a value Vadd for unicodePwd, the server considers the request to be a request to change the password. ... Vdel is the old password, while Vadd is the new password.
In short, you need to delete the value and add the value in one LDAP request. In PHP, that means using the ldap_modify_batch function. In fact, in that documentation, there is an example of how to change a password:
<?php
function adifyPw($pw)
{
return iconv("UTF-8", "UTF-16LE", '"' . $pw . '"');
}
$dn = "cn=Jack Smith-Jones,ou=Wizards,dc=ad,dc=example,dc=com";
$modifs = [
[
"attrib" => "unicodePwd",
"modtype" => LDAP_MODIFY_BATCH_REMOVE,
"values" => [adifyPw("Tr0ub4dor&3")],
],
[
"attrib" => "unicodePwd",
"modtype" => LDAP_MODIFY_BATCH_ADD,
"values" => [adifyPw("correct horse battery staple")],
],
];
ldap_modify_batch($connection, $dn, $modifs);
Related
I come to you because I have a little problem with an Active directory server, indeed I can connect to get my users ... when I add a user with the AD software, I assign him a password and this one is enabled for authentication in PHP with a form, nothing abnormal.
The problem is that when I add a user directly with PHP I use this code
$ldapconn=ldap_connect("adress");
$ldapbind=ldap_bind($ldapconn, "local", "test");
$cn = $info["cn"][0] = "test test ";
$info["sn"][0] ="test ";
$info["givenname"][0] ="test ";
$info["displayname"][0] ="test test ";
$info["name"][0] ="test test ";
$info["userprincipalname"][0] = "test #test .test ";
$info["samaccountname"][0] = "ttest ";
$info["objectClass"][0]="top";
$info["objectClass"][1]="person";
$info["objectClass"][2]="organizationalPerson";
$info["objectClass"][3]="user";
$info["objectCategory"][0] ="CN=Person,CN=Schema,CN=Configuration,DC=test
,DC=test ";
$info['userPassword'][0] = "test ";
// add entries
$r = ldap_add($ldapconn,"CN=".$cn.",OU=Utilisateurs,DC=test ,DC=test ", $info);
My user is correctly added but php authentication is not done, my user doesn't have an authentication password but a password that should be used for something else, I read on the net that $info['userPassword'] allows to create a password that is not usable for authentication. Someone would have the exact attribute in order to create this password please.
Thank you in advance for your help.
I also tried with the attribute $info['unicodePwd'] but I get an error message
" Add: Server is unwilling to perform ".
The userPassword attribute sometimes works, but the unicodePwd is the real attribute.
There are a couple caveats:
You have to be connected to AD via an encrypted connection. That means LDAP over SSL (LDAPS). So in your call to ldap_connect, you need to use ldaps://example.com. This can open a can of worms since the SSL certificate sent by the server needs to be trusted by your app. There are some comments on the documentation page that can help there.
The format of the value you assigned to unicodePwd must be:
specified in a UTF-16 encoded Unicode string containing the password surrounded by quotation marks, which has been BER-encoded as an octet string per the Object(Replica-Link) syntax.
There is also an example of this on the documentation page for ldap-mod-replace:
$newPassword = "MyPassword";
$newPassword = "\"" . $newPassword . "\"";
$len = strlen($newPassword);
for ($i = 0; $i < $len; $i++)
$newPassw .= "{$newPassword{$i}}\000";
$newPassword = $newPassw;
$userdata["unicodepwd"] = $newPassword;
$result = ldap_mod_replace($ad, $userDn, $userdata);
if ($result) echo "User modified!" ;
else echo "There was a problem!";
You may have to use ldap_mod_replace to set the password after creating the account first (rather than using $info['unicodepwd'][0] while creating the account). In my experience, I've always had to create the account first, then set the password.
But because of having no password at first, accounts are usually disabled (depending on your policies). So you will likely have to set the userAccountControl attribute to 512 (NORMAL_ACCOUNT) to enable it after you've set the password.
Thanks to you for your answer, I tried with your solution by not putting the'[unicodepwd]' and using "ldap_mod_replace" afterwards but I get an error message "server is unwilling to perform".
I think my server is configured in a specific way.
I found another solution, I use the "Adldap2" tool ( https://adldap2.github.io/Adldap2/#/?id=what-is-adldap2), which contains all the functions necessary to use LDAP with PHP.
I recommend it to anyone who has difficulties with Active directory .
For example, my problem was solved with this code:
enter code include __DIR__ . '/vendor/autoload.php';
$config = [
// Mandatory Configuration Options
'hosts' => ['testServer'],
'base_dn' => 'dc=test,dc=test',
'username' => 'test#test.test',
'password' => 'test',
'account_suffix' => '#test.test',
'port' => 636,
'use_ssl' => true,
];
$ad = new Adldap\Adldap();
$connectionName = 'my-connection';
$ad->addProvider($config, $connectionName);
try {
$provider = $ad->connect($connectionName);
echo"Connection ok <br>" ;
// Great, we're connected!
} catch (Adldap\Auth\BindException $e) {
// Failed to connect.
echo"Connection failed<br>" ;
}
$user = $provider->make()->user([
'cn' => 'test test ',
'userprincipalname' => 'test#test.test',
'accountExpires'=> 132188680854770000
]);
$user->setDn("CN=test tes ,OU=test ,DC=test,DC=test");
$dn = $user->getDn();
$user->setDisplayName('test test ');
$user->setAccountName('ttest');
$user->setFirstName('test');
$user->setLastName('test');
$attribut = $user->getAttributes();
var_dump($attribut);
$user->setEmail('test#test.test');
// Save the new user.
// Enable the new user (using user account control).
if ($user->save()) {
// Enable the new user (using user account control).
$user->setUserAccountControl(66048);
// Set new user password
$user->setPassword('test');
// Save the user.
if($user->save()) {
// The password was saved successfully.
}
}
hope this can helps :)
I'm currently working on an application that makes use of the prestashop webservice. This means that the application i'm building is an extension of an existing prestashop application. The connection between both applications is through the prestashop webservice
Currently i'm trying to create a login script for thecustomers. The email and password are obtained from the database through the webservice and i'm able to filter the inputs with the existing row's. So when filling in login#test.com. The filter will only obtain the row with that email address.
The problem i'm having is with the password. Prestashop uses a _COOKIE_KEY_ together with anmd5() to encrypt passwords. See this link for more information: link
So i've been trying some different things for a while to check the inputted password with the customers password but i haven't found the solution yet.
Take a look at the code below:
<?php
require_once('./PSWebServiceLibrary.php');
/**
* get information from PrestaShop
*/
$webService = new PrestaShopWebservice($url, $key, $debug);
define('_COOKIE_KEY_', '...');
$email = "login#test.nl";
define('password', "test");
$md5passwd = md5(_COOKIE_KEY_ . password);
$opt = array(
"resource" => "customers",
"display" => "[email , passwd]",
"filter[email]" => "$email"
);
$optPass = array(
"resource" => "customers",
"display" => "[email]",
"filter[email]" => "$email",
"filter[passwd]" => "$md5passwd"
);
$jsonPass = ($webService->get( $optPass ));
//json encode it
$jsonPasswd = json_encode($jsonPass);
echo($jsonPasswd);
if(password_verify($md5passwd, $jsonPasswd)) {
echo "password is valid";
} else {
echo "password is not valid";
}
$jsonUrl = ($webService->get( $opt ));
//json encode it
$json = json_encode($jsonUrl);
echo($json);
As you can see i've been trying out things like the password_verify and the md5() but i can't quite get it. So is there anyone who has done this or who knows how to create a correct login script on the prestashop webservice?
Update -- 12/1/2017
So after doing some research i've come up with a new way of checking the user input. First the code checks the email and if it's true it will continue with checking the password input. But the problem i'm having is with the password and the password encryption of prestashop. I'm not able to compare the two hashes together. The first hash would be the hash from the database were the second hash is the user input password. The input would need a hash() function from prestashop. But i can't quite get to the right hash sequence of prestashop.
I've searched all over the internet for this but couldn't find a decent solution for logging in through the prestashop webservice. The script i've created for logging in is shown below.
require_once('./PSWebServiceLibrary.php');
/**
* get information from PrestaShop
*/
$webService = new PrestaShopWebservice($url, $key, $debug);
$COOKIE_KEY = '_key';
$email = $_REQUEST['email'];
$password = md5('_key' . $_REQUEST['password']);
// The database hash for testing (random)
$passwordString = '$2y$10$UsYrIFQUOr5LBUZBoqSdxODuhbToEc.2QEqfAVB1r\/fhO5EfOyO96';
$opt = array(
'resource' => 'customers',
'filter[email]' => '['.$email.']',
'display' => '[email,lastname,firstname, passwd]'
);
$result = ($webService->get( $opt ));
$json = json_encode($result);
$optUser = array(
'resource' => 'customers',
'filter[email]' => '['.$email.']',
'display' => '[email,lastname,firstname,passwd]'
);
$resultUser = ($webService->get( $optUser ));
$userResult = json_encode($resultUser);
// Check the email
function hasEmail($string, $email)
{
return strpos($string, $email) !== false;
}
// Check the Password
function hasPassword($string, $password)
{
return strpos($string, $password) !== false;
}
if(hasEmail($userResult, $email) == true and hasPassword($userResult, $password) == true) {
session_start();
$_SESSION['user'] = $email;
// redirect is kut.
echo
'<html>
<head>
<meta content="text/html; charset=utf-8">
</head>
</html>';
} else {
// Here, we use single quotes for PHP and double quotes for JavaScript
echo '<script type="text/javascript">';
echo 'alert("Wrong username or password!")';
echo '</script>';
}
Small second question: How would i be able to run a -> go to url in the success statement, Currently the echo "<script></script>"; isn't working and since the header() can't be used i'm having some trouble redirecting on succes.
As always, Thanks in advance!
To generate the cookie key prestashop uses:
array('_COOKIE_KEY_', Tools::passwdGen(56)),
array('_COOKIE_IV_', Tools::passwdGen(8)),
So that cookie key is different everytime. In order to verify if the password is good you should get existing password from database and compare with your user-submitted password:
//CHECK IF THE GIVEN EMAIL MATCHES A ROW IN OUR LEGACY TABLE AND RETRIEVES THE LEGACY PASSWORD
$resultZC = Db::getInstance()->getRow('
SELECT `password`
FROM `zc_legacy_passwords`
WHERE `email` = \''.pSQL($email).'\'
AND `updated` = 0');
if (!$resultZC)
return false; //<- EMAIL NOT FOUND IN NONE OF THE TABLES, SO IT IS AN INVALID LOGIN
//ENCRYPTS THE GIVEN PASSWORD IN ZEN-CART / OSCOMMERCE FORMAT
$salt = substr($resultZC['password'], strrpos($resultZC['password'],':')+1, 2);
$ZCpassword = md5($salt . $passwd) . ':' . $salt;
if ($ZCpassword != $resultZC['password'])
return false; //<- WRONG ZEN-CART/OSCOMMERCE PASSWORD GIVEN
This is the part that you're asking for:
//ENCRYPTS THE GIVEN PASSWORD IN ZEN-CART / OSCOMMERCE FORMAT
$salt = substr($resultZC['password'], strrpos($resultZC['password'],':')+1, 2);
$ZCpassword = md5($salt . $passwd) . ':' . $salt;
where $resultZC['password'] is the password stored in the database and, $passwd is your password
if PrestaShop version is 1.6, the function to encrypt customer passwords is Tools::encrypt($passwd). This method just do this:
return md5(_COOKIE_KEY_.$passwd);
So knowing the _COOKIE_KEY_ you must be able to generate the hash.
_COOKIER_KEY_ is defined in config/settings.inc.php
If PrestaShop version is 1.7, Tools is not used and maybe md5(_COOKIE_KEY_.$passwd) will not match. It is used crypto from Symfony.
However, I guess PrestaShop webservice must have something to check users, in Customer class there is the method getByEmail(...) that is used in both 1.6 and 1.7 versions.
Regards.
I am creating a PHP application which can be installed on user's server but the administration is hosted on my server.
I need to store the password, username, db_name, host and port of database of the user. I think I should store in database (MySQL), but I don't know how to do securely.
Can you tell me please how can I store external passwords securely?
Thank you!
You need to have one secure key setup on main server (where database settings are stored) for example the password is b#auty! and on the other machine decrypt function with the same key to decrypt password. Now you can secure any string simply by
//url encrpytography
function encrypt($val, $key = 'b#auty!') {
$keySalt = $key;
$query = base64_encode(urlencode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($keySalt), $val, MCRYPT_MODE_CBC, md5(md5($keySalt))))); //this line of code encrypt the query string
return $query;
}
function decrypt($val, $key = 'b#auty!') {
$keySalt = $key; // same as used in encryptLink function
$queryString = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($keySalt), urldecode(base64_decode($val)), MCRYPT_MODE_CBC, md5(md5($keySalt))), "\0"); //this line of code decrypt the query string
//CHECK IF RETURN IS REAL LETTERS
if (preg_match('/^[a-z0-9 .\-]+$/i', $queryString)) {
} else {
$queryString = '';
}
return $queryString;
}
$encrypt = encrypt("string");
echo $encrypt;
$decrypt = decrypt($encrypt);
echo $decrypt
There is obviously more than one way to handle passwords in data stores. If you are very concerned that your password stores are never compromised, I highly recommend you look at this information:
http://www.openwall.com/articles/PHP-Users-Passwords
I always use this methodology now because it's as secure as one can get. It's free, and I have no affiliation with the author or his operation.
I'm developing an API to let my users access to files stored on another server.
Let's call my two servers, server 1 and server 2!
server 1 is the server im hosting my web site, and
server 2 is the server im storing my files!
My site is basically Javascript based one, so I will be using Javascript to post data to API when user needs to access files which are stored on server 2.
when users requests to access files, the data will be posted to API URL via Javascript! API is made of PHP. Using that PHP script(API) on server 1, I will made another request to server 2 asking for files so there will be another PHP script(API) on server 2.
I need to know how should I do this authentication between two servers as server 2 has no access to user details on server 1?
I hope to do that like this, I can use the method which is used by most payment gateways.
When API on server 2 received a request with some unique data of the user , post back those unique data through SSL to server 1 API and match them with user data in the database, then post back result through SSL to server 2 so then server 2 knows file request is a genuine request.
In this case what kind of user data/credentials server 1 API should post to server 2 and server 2 API should post back to server 1? and which user data should be matched with the data in the database? like user ID, session, cookies, ip, time stamp, ect!
Any clear and described answer would be nice! Thanks.
I would go with this:
user initiates action, javascript asks Server 1 (ajax) for request for file on Server 2
Server 1 creates URL using hash_hmac with data: file, user ID, user secret
when clicking that URL (server2.com/?file=FILE&user_id=ID&hash=SHA_1_HASH) server 2 asks server 1 for validation (sends file, user_id and hash)
server 1 does the validation, sends response to server 2
server 2 pushes file or sends 403 HTTP response
This way, server 2 only needs to consume API of server 1, server 1 has all the logic.
Pseudocode for hash and url creation:
// getHash($userId, $file) method
$user = getUser($userId);
$hash = hash_hmac('sha1', $userId . $file, $user->getSecret());
// getUrl($userId, $file) method
return sprintf('http://server2.com/get-file?file=%1&user_id=%2&hash=%3',
$userId,
$file,
$security->getHash($userId, $file)
);
Pseudocode for validation:
$hash = $security->getHash($_GET['id'], $_GET['file']);
if ($hash === $_GET['hash']) {
// All is good
}
Edit: getHash() method accepts user ID and file (ID or string, what ever suits your needs). With that data, it produces a hash, using hash_hmac method. For the secret parameter of hash_hmac function, users "secret key" is used. That key would be stored together with users data in the db table. It would be generated with mt_rand or even something stronger as reading /dev/random or using something like https://stackoverflow.com/a/16478556/691850.
A word of advice, use mod_xsendfile on server 2 (if it is Apache) to push files.
Introduction
You can use 2 simple method
Authentication Token
Signed Request
You can also combine both of them by using Token for authentication and using signature to verify integrity of the message sent
Authentication Token
If you are going to consider matching any identification in the database perhaps you can consider creating authentication token rather than user ID, session, cookies, ip, time stamp, etc! as suggested.
Create a random token and save to Database
$token = bin2hex(mcrypt_create_iv(64, MCRYPT_DEV_URANDOM));
This can be easily generated
You can guaranteed it more difficult to guess unlike password
It can easily be deleted if compromised and re generate another key
Signed Request
The concept is simple, For each file uploaded must meat a specific signature crated using a random generated key just like the token for each specific user
This can easily be implemented with HMAC with hash_hmac_file function
Combine Both Authentication & Signed Request
Here is a simple Prof of concept
Server 1
/**
* This should be stored securly
* Only known to User
* Unique to each User
* Eg : mcrypt_create_iv(32, MCRYPT_DEV_URANDOM);
*/
$key = "d767d183315656d90cce5c8a316c596c971246fbc48d70f06f94177f6b5d7174";
$token = "3380cb5229d4737ebe8e92c1c2a90542e46ce288901da80fe8d8c456bace2a9e";
$url = "http://server 2/run.php";
// Start File Upload Manager
$request = new FileManager($key, $token);
// Send Multiple Files
$responce = $request->send($url, [
"file1" => __DIR__ . "/a.png",
"file2" => __DIR__ . "/b.css"
]);
// Decode Responce
$json = json_decode($responce->data, true);
// Output Information
foreach($json as $file) {
printf("%s - %s \n", $file['name'], $file['msg']);
}
Output
temp\14-a.png - OK
temp\14-b.css - OK
Server 2
// Where to store the files
$tmpDir = __DIR__ . "/temp";
try {
$file = new FileManager($key, $token);
echo json_encode($file->recive($tmpDir), 128);
} catch (Exception $e) {
echo json_encode([
[
"name" => "Execption",
"msg" => $e->getMessage(),
"status" => 0
]
], 128);
}
Class Used
class FileManager {
private $key;
function __construct($key, $token) {
$this->key = $key;
$this->token = $token;
}
function send($url, $files) {
$post = [];
// Convert to array fromat
$files = is_array($files) ? $files : [
$files
];
// Build Post Request
foreach($files as $name => $file) {
$file = realpath($file);
if (! (is_file($file) || is_readable($file))) {
throw new InvalidArgumentException("Invalid File");
}
// Add File
$post[$name] = "#" . $file;
// Sign File
$post[$name . "-sign"] = $this->sign($file);
}
// Start Curl ;
$ch = curl_init($url);
$options = [
CURLOPT_HTTPHEADER => [
"X-TOKEN:" . $this->token
],
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_POST => count($post),
CURLOPT_POSTFIELDS => $post
];
curl_setopt_array($ch, $options);
// Get Responce
$responce = [
"data" => curl_exec($ch),
"error" => curl_error($ch),
"error" => curl_errno($ch),
"info" => curl_getinfo($ch)
];
curl_close($ch);
return (object) $responce;
}
function recive($dir) {
if (! isset($_SERVER['HTTP_X_TOKEN'])) {
throw new ErrorException("Missing Security Token");
}
if ($_SERVER['HTTP_X_TOKEN'] !== $this->token) {
throw new ErrorException("Invalid Security Token");
}
if (! isset($_FILES)) {
throw new ErrorException("File was not uploaded");
}
$responce = [];
foreach($_FILES as $name => $file) {
$responce[$name]['status'] = 0;
// check if file is uploaded
if ($file['error'] == UPLOAD_ERR_OK) {
// Check for signatire
if (isset($_POST[$name . '-sign']) && $_POST[$name . '-sign'] === $this->sign($file['tmp_name'])) {
$path = $dir . DIRECTORY_SEPARATOR . $file['name'];
$x = 0;
while(file_exists($path)) {
$x ++;
$path = $dir . DIRECTORY_SEPARATOR . $x . "-" . $file['name'];
}
// Move File to temp folder
move_uploaded_file($file['tmp_name'], $path);
$responce[$name]['name'] = $path;
$responce[$name]['sign'] = $_POST[$name . '-sign'];
$responce[$name]['status'] = 1;
$responce[$name]['msg'] = "OK";
} else {
$responce[$name]['msg'] = sprintf("Invalid File Signature");
}
} else {
$responce[$name]['msg'] = sprintf("Upload Error : %s" . $file['error']);
}
}
return $responce;
}
private function sign($file) {
return hash_hmac_file("sha256", $file, $this->key);
}
}
Other things to consider
For better security you can consider the follow
IP Lock down
File Size Limit
File Type Validation
Public-Key Cryptography
Changing Date Based token generation
Conclusion
The sample class can be extended in so many ways and rather than use URL you can consider a proper json RCP solution
A long enough, single-use, short-lived, random generated key should suffice in this case.
Client requests for a file to Server 1
Server 1 confirms login information and generates a long single-use key and sends it to the user. Server 1 keeps track of this key and matches it with an actual file on Server 2.
Client sends a request to Server 2 along with the key
Server 2 contacts Server 1 and submits the key
Server 1 returns a file path if the key is valid. The key is invalidated (destroyed).
Server 2 sends the file to the client
Server 1 invalidates the key after say 30 seconds, even if it didn't receive a confirmation request from Server 2. Your front-end should account for this case and retry the process a couple of times before returning an error.
I do not think there is a point in sending cookie/session information along, this information can be brute-forced just like the random key.
A 1024-bit long key sounds more than reasonable. This entropy can be obtained with a string of less than 200 alphanumeric characters.
For the absolute best security you would need some communication from server 2 to server 1, to double check if the request is valid. Although this communication could be minimal, its still communication and thus slows down the proces.
If you could live with a marginally less secure solution, I would suggest the following.
Server 1 requestfile.php:
<?php
//check login
if (!$loggedon) {
die('You need to be logged on');
}
$dataKey = array();
$uniqueKey = 'fgsdjk%^347JH$#^%&5ghjksc'; //choose whatever you want.
//check file
$file = isset($_GET['file']) ? $_GET['file'] : '';
if (empty($file)) {
die('Invalid request');
}
//add user data to create a reasonably unique fingerprint.
//It will mostlikely be the same for people in the same office with the same browser, thats mainly where the security drop comes from.
//I double check if all variables are set just to be sure. Most of these will never be missing.
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$dataKey[] = $_SERVER['HTTP_USER_AGENT'];
}
if (isset($_SERVER['REMOTE_ADDR'])) {
$dataKey[] = $_SERVER['REMOTE_ADDR'];
}
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
}
if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT_ENCODING'];
}
if (isset($_SERVER['HTTP_ACCEPT'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT'];
}
//also add the unique key
$dataKey[] = $uniqueKey;
//add the file
$dataKey[] = $file;
//add a timestamp. Since the request will be a different times, dont use the exact second
//make sure its added last
$dataKey[] = date('YmdHi');
//create a hash
$hash = md5(implode('-', $dataKey));
//send to server 2
header('Location: https://server2.com/download.php?file='.urlencode($file).'&key='.$hash);
?>
On server 2 you will do almost the same.
<?php
$valid = false;
$dataKey = array();
$uniqueKey = 'fgsdjk%^347JH$#^%&5ghjksc'; //same as on server one
//check file
$file = isset($_GET['file']) ? $_GET['file'] : '';
if (empty($file)) {
die('Invalid request');
}
//check key
$key = isset($_GET['key']) ? $_GET['key'] : '';
if (empty($key)) {
die('Invalid request');
}
//add user data to create a reasonably unique fingerprint.
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$dataKey[] = $_SERVER['HTTP_USER_AGENT'];
}
if (isset($_SERVER['REMOTE_ADDR'])) {
$dataKey[] = $_SERVER['REMOTE_ADDR'];
}
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
}
if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT_ENCODING'];
}
if (isset($_SERVER['HTTP_ACCEPT'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT'];
}
//also add the unique key
$dataKey[] = $uniqueKey;
//add the file
$dataKey[] = $file;
//add a timestamp. Since the request will be a different times, dont use the exact second
//keep the request time in a variable
$time = time();
$dataKey[] = date('YmdHi', $time);
//create a hash
$hash = md5(implode('-', $dataKey));
if ($hash == $key) {
$valid = true;
} else {
//perhaps the request to server one was made at 2013-06-26 14:59 and the request to server 2 come in at 2013-06-26 15:00
//It would still fail when the request to server 1 and 2 are more then one minute apart, but I think thats an acceptable margin. You could always adjust for more margin though.
//drop the current time
$requesttime = array_pop($dataKey);
//go back one minute
$time -= 60;
//add the time again
$dataKey[] = date('YmdHi', $time);
//create a hash
$hash = md5(implode('-', $dataKey));
if ($hash == $key) {
$valid = true;
}
}
if ($valid!==true) {
die('Invalid request');
}
//all is ok. Put the code to download the file here
?>
You can restrict access to server2. Only server1 will be able to send request to server2. You can do this by whitelisting ip of server1 on server side or using .htaccess file. In php you can do by checking request generated ip and validate it with server1 ip.
Also you can write a algorithm which generates a unique number. Using that algorithm generate a number on server1 and send it to server2 in request. On server2 check if that number is generated by algorithm and if yes then request is valid.
I'd go with a simple symetric encryption, where server 1 encodes the date and the authenticated user using a key known only by server 1 and server 2, sending it to the client who cant read it, but can send it to server 2 as a sort of ticket to authenticate himself. The date is important to not let any client use the same "ticket" over the time. But at least one of the servers must know which user have access to which files, so unless you use dedicated folders or access groups you must keep the user and file infos together.
I'm making a website in which I'm trying to create a form that will send the user-input to a google spreadsheet in my google docs/drive... I found a Github project that lets people code the php... It includes 2 other php files which are needed for the script. The code is as follows:
My question is, how can I hide my password from this script under $u = / $p = ??
Anyone viewing the code can see my password.. how can I prevent that?
Link to the script's source is : http://www.farinspace.com/saving-form-data-to-google-spreadsheets/
<?php
// Zend library include path
set_include_path(get_include_path() . PATH_SEPARATOR . "$_SERVER[DOCUMENT_ROOT]/ZendGdata-1.8.1/library");
include_once("Google_Spreadsheet.php");
$u = "username#gmail.com";
$p = "password";
$ss = new Google_Spreadsheet($u,$p);
$ss->useSpreadsheet("My Spreadsheet");
$ss->useWorksheet("wks2");
// important:
// adding a leading alpha char prevents errors, there are issues
// when trying to lookup an identifier in a column where the
// value starts with both alpha and numeric characters, using a
// leading alpha character causes the column and its values to be
// seen as a strictly a strings/text
$id = "z" . md5(microtime(true));
$row = array
(
"id" => $id // used for later lookups
, "name" => "John Doe"
, "email" => "john#example.com"
, "comments" => "Hello world"
);
if ($ss->addRow($row)) echo "Form data successfully stored";
else echo "Error, unable to store data";
$row = array
(
"name" => "John Q Doe"
);
if ($ss->updateRow($row,"id=".$id)) echo "Form data successfully updated";
else echo "Error, unable to update spreadsheet data";
?>
You can attempt to hide if from peering eyes using the code below. It would still be discoverable if you tried, but at least it's away from open text view. All it does is add characters to the text and then subtract them before it uses the password.
Run this script using your original password
<?php
$password = "test";
echo "Original Password In Plain Text = $password\n";
$len=strlen($password);
$NewPassword = "";
for( $i = 0; $i <= $len-1; $i++ ) {
$charcode = ord(substr( $password, $i, 1 ));
$NewChar = $charcode+5; $NewLetter = chr($NewChar);
$NewPassword = $NewPassword . $NewLetter;
} echo "Modified Password to Use in Script = $NewPassword\n";
$OrigPassword = "";
for( $i = 0; $i <= $len-1; $i++ ) {
$charcode = ord(substr( $NewPassword, $i, 1 ));
$OrigChar = $charcode-5; $OrigLetter = chr($OrigChar);
$OrigPassword = $OrigPassword . $OrigLetter;
} echo "Convert the Modified back to the Original = $OrigPassword\n";
?>
Add this part to your script with the new password from the above script
$password = "yjxy";
$OrigPassword = "";
for( $i = 0; $i <= $len-1; $i++ ) {
$charcode = ord(substr( $password, $i, 1 ));
$OrigChar = $charcode-5; $OrigLetter = chr($OrigChar);
$OrigPassword = $OrigPassword . $OrigLetter;
} $password = $OrigPassword;
echo "Script thinks this is the password = $password\n";
The best way to hide the password is to save it in external file and then include it in your php script. Your file with this password let's say 'config.php' should be above DOCUMENT_ROOT to make it unaccesible via browser. It's common aproach and for example you can see it in Zend Framework directory structure where only "public" directory is visible for user. The proper CHMOD should be set to this file as well.
Under this link you have ZF directory structure where you can check location of config files.
This question has been asked and answered lots of times here (but not specifically for Google docs). Short answer is that there is nothing you can do.
Longer answer is that you can mitigate the possibility of the credentials being compromised by:
using credentials supplied the user rather than stored in code
using tokens supplied by the user as a means of decrypting credentials stored in your code (but this gets very complicated with lots of users)
storing the credentials in an include file held outside the document root