I need to append an user id to a public url, I'm not a security expert and I need to know the best way to secure it.
I thougt that cypher the id and the append it to the url, and decypher it on the function called by the url, but I need to be the only one thant can decypher it.
Any suggestions?
If you want a one-time-only or limited-time-only unique ID to give to the user (I'm guessing this is maybe in some kind of email link or something?) then you could potentially create a GUID or UUID and associate that with the user (via a database table perhaps), and have a field to mark when it's been used or expired.
A GUID/UUID is near-enough guaranteed to be unique and isn't easy to randomly guess.
The best way to do that is by implementing a SSO mecanism (Single Sign On).
The user will enter his password on the first app and use a secure link to access the second app.
The first app generates a public key encrypted with a private key known by the two apps.
The second app generates the public key to and if it matches the key appended to the URL then its safe to log the user.
Here an example :
The first app will create a public key :
$privateKey = `123azerty456`;
$publicKey = hash('sha512', 'user_id' . $privateKey);
$url = 'second_app.com/example.php?user_id=foo&key='.$publicKey;
The script on the second app will compute the key on its own based on the user_id passed as an argument and using the same private key :
$privateKey = `123azerty456`;
$checkPublicKey = hash('sha512', $_GET['user_id'] . $privateKey);
if($checkPublicKey == $_GET['key']) {
echo 'OK';
} else {
echo 'UNAUTHORIZED';
}
Related
I have this url:
https://www.example.com/?p=user#provider.it
What is the best way to encrypt the email inside my URL in order to don't let user knows there is an email in the URL, something like:
https://www.example.com/?p=skckqbsBDoia27d
Obviously I will be able to decrypt skckqbsBDoia27d to get user#provider.it and skckqbsBDoia27d MUST be unique based on email.
The solution MUST works with PHP 5.5.
Use base64_encode() and base64_decode() accordingly.
some#email.com will get encoded as c29tZUBlbWFpbC5jb20=
$email = "some#email.com";
$url = "http://www.example.com?param=" . strrev(base64_encode($email));
Then on the receiving side:
echo base64_decode(strrev($_GET['param']));
If you're concerned about disclosing email then the best way would be to just not include the email in any form. Rather, use some other unique identifier that's associated with the user record. If it's not important that it be unguessable, you can just use the database row's pkey field user.id or whatever. If it needs to be unguessable, I'd create a new field like user.public_id with a unique constraint and then populate it with a hash or uuid.
I'm trying to build a platform for users who will store confidential data about their clients. The context is quite simple: french laws prohibit me from having access to the data that my users will store (for example medical records of patients).
So when a user submits data which will be stored in the database, he should be the only one having access to that information. For example by encrypting it with his password. That way, if I log into mysql, I would only see encrypted nonsense and no readable data.
My philosophy, which might be wrong, is to learn by doing it. Hope you guys are ok with my approach.
Problem is: I have no idea where to start, how to do that... and actually not even what to search for on google. I even tried to find something suitable on codecanyon.net for example and couldn't fond any relevant scripts.
Thanks in advance :) !
PS: I will actually have the same problem with files (jpg, word, pdf, xls... that should be enough for the users). But that's another story.
Although I'm not familiar with the French data protection laws, I do have some experience with the general EU legislation. Probably you have to encrypt personal data in your system such way, that the person cannot be identified. This means, that technical data, such as a system specific id in a table can stay unencrypted. This is the only reason I believe you can actually make this work. Sort of. You have to work with lawyers to determine what data can stay unencrypted to comply with the laws and still to be able to provide a valuable service to your clients.
However, I believe that you are missing an important point: if you cannot have access to certain data, then that data MUST NOT arrive to your organisation in a plain format. Period. If it does, then you already have access to it. So, php (or any kind of server based encryption) or mysql based encryption solutions are out of the picture.
The only solution I can think of is it to team up with a 3rd party PKI provider, who will provide your clients with certificates ( perhaps on chip cards), and the client side of your application encrypts the sensitive personal data items on the client BEFORE they are sent to your server and also decrypt these data items on the client. This also means that you will have to use some plugins on the client side if you want this system to be web based. Probably you will need some signed java app to manage the card reader and the certificate.
The setup has 2 drawbacks for your clients:
It's not a single provider they deal with, since the PKI provider must be an independent third party. You must not manage the certificates.
In case the stored data gets corrupted, the encrypted data will be lost. So, you will have to implement some crazy backup and restore solution.
So assuming the problem is as follows:
You need to encrypt the data before you store it.
You shouldn't have the keys to decrypt it, only encrypt it.
There's actually a tool for this: It's called a sealing API, and it can be accomplished through OpenSSL or Libsodium.
Sealing/Unsealing Data in PHP with Libsodium
$store_me = \Sodium\crypto_box_seal(
$plaintext,
$recipient_public_key
);
$visible = \Sodium\crypto_box_seal_open(
$store_me,
$recipient_keypair
);
Sealing/Unsealing Data in PHP with OpenSSL
/**
* A human-usable variant of openssl_seal()
*
* #param string $plaintext Your message
* #param string $publickey_string PEM-encoded RSA public key
* #param boolean $encode Hex-encode the output?
*
* #return string
*/
function easy_seal($plaintext, $publickey_string, $encode = false)
{
$pubkey = openssl_get_publickey($publickey_string);
if ($pubkey === false) {
throw new Exception('Could not load public key');
}
$sealed = '';
$ekeys = [];
$result = openssl_seal($plaintext, $sealed, $ekeys, [$pubkey]);
if ($result === false) {
throw new Exception('openssl_seal failed!');
}
if ($encode) {
return json_encode([
bin2hex($sealed),
bin2hex($ekeys[0])
]);
}
return json_encode([$sealed, $ekeys[0]]);
}
/**
* Inverse operation of easy_seal()
*
* #param string $ciphertext (the output of easy_seal())
* #param string $privatekey_string PEM-encoded RSA private key
* #param boolean $encoded Do we need to decode from hex?
*
* #return string
*/
function easy_unseal($ciphertext, $privatekey_string, $encoded = false)
{
list($sealed, $ekey) = json_decode($ciphertext, true);
if ($encoded) {
$sealed = hex2bin($sealed);
$ekey = hex2bin($ekey);
}
$open_data = '';
$privkey = openssl_get_privatekey($privatekey_string);
if ($privkey === false) {
throw new Exception('Could not load public key');
}
$result = openssl_open($sealed, $open_data, $ekey, $privkey);
if ($result === false) {
throw new Exception('openssl_open failed!');
}
return $open_data;
}
Usage Example
$public_key = file_get_contents('/path/to/publickey.pem');
$plaintext = 'Something something dark side';
$store_me = easy_seal($plaintext, $public_key);
// Elsewhere:
$secret_key = file_get_contents('/path/to/secretkey.pem');
$visible = easy_unseal($store_me, $secret_key);
Demo: https://3v4l.org/BNavp
Actually, I'm also on a similar kind of project where I'm trying to build a secure database in MySQL Server that is also useful to run all the valid SQL Queries. It's still in the progress and too many difficulties are there; I accept.
But, for your problem , it seems you only need to encrypt and decrypt the values.and you don't want to store the key also there in the database. For me, there are two ways that comes in my mind:
First way is, that you decide a fixed secret key to encrypt and decrypt the values and use it for every data, that is being stored on the database.
But, that's not practical I guess, since the security gets weak with this approach and a person can identify your key with brute force approach.
Second way is, that you generate a random key for every different user, like, at the time of registration. And data of different user can be viewed by only the user who has the key to decrypt it, which here only the user has. And then you apply the first approach after this. i.e., you decide a key that will be used to encrypt these keys of different users. and then store this encrypted key in the database in a separate table. So that, next time user will try to access his/ her data, his entered key (could be his password), will be encrypted with your decided static key, if this encrypted key is found in the table of your database your will fetch the data of that user, decrypt it with his/ her key and display to him/ her.
All you need is a,
(i) programming platform to select, JAVA is best.
(ii) learn how to use database with this programming language, MySQL Server is a nice choice to work with.
(iii) And a good encryption algorithm to implement.
Hope, I didn't make you angry with this answer :) Cheers.
I'm using laravel's Auth password reset method and not sure i fully understand what part plays the token in all of this.
I'm sending the user an email with Password::remind('email#email.com') , which generates a token in my password_reminders table. The token is fully visible in the url.
The user goes to a url that looks something like: mywebsite.com/remindpass/xxxxxx[token] .
Then he fills out a form with his email and a new password , sending it trough post to a controller - which uses Password::reset('email','password','xxxxxx') .
The question is how is this secure? What does the generated token do to prevent someone just going to mywebsite.com/remindpass/xxxxxx[token] and change the email & password as he likes?
Can someone please clarify the proccess?
I'm sure someone could answer this question better than I could.
Short answer:
The token makes it more difficult for someone to guess the credentials needed to reset the password while making the reset link in the email available.
Long answer:
In the file vendor/laravel/framework/src/Illuminate/Auth/Guard.php, you'll see the method createRememberTokenIfDoesntExist. This method actually references another method right above it called refreshRememberToken to set your token.
It uses the laravel helper function str_random. If you trace this function back to it's source, you'll find it uses the vendor/laravel/framework/src/Illuminate/Support/Str.php class' random method.
public static function random($length = 16)
{
if (function_exists('openssl_random_pseudo_bytes'))
{
$bytes = openssl_random_pseudo_bytes($length * 2);
if ($bytes === false)
{
throw new \RuntimeException('Unable to generate random string.');
}
return substr(str_replace(array('/', '+', '='), '', base64_encode($bytes)), 0, $length);
}
return static::quickRandom($length);
}
Now we finally get down to where the token is built. This method uses the function openssl_random_pseudo_bytesto generate the token. You can read about that function in the PHP manual page for openssl_random_pseudo_bytes, but basically it generates a cryptographically strong random string.
Laravel then takes this string (still in the random method), base 64 encodes it, replaces some characters, and takes a slice of that string based on either the default setting of 16 (seen in the parameter definition $length = 16) or whatever length is passed into the method by the caller.
So, you get a string that is cryptographically strong and then manipulated as your token.
If you look at the file vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php and find the method retrieveByToken, you'll see that laravel uses both the user record ID and the token to find the user who's password needs to change.
For someone to guess that string AND the id of you user record that has that token would be incredibly difficult and would require knowledge of your application's business logic.
What does the generated token do to prevent someone just going to mywebsite.com/remindpass/xxxxxx[token] and change the email & password as he likes?
Because only you and the person you sent the email to (i.e. the account holder) know what the token is.
A strong implementation will takes steps to make it hard to guess tokens:
Long (harder to guess) tokens
Time limited tokens
IP based rate limiting for access to /remindpass/*
I am trying to develop an application on android. The application is basically send the user's location information with the users it specified for a finite amount of time. It will be like Glympse.
I am doing this for a school project so don't have good hosts for the web. I am trying to use free ones. And i can not use their database systems to my own wishes. and inserting and fetching from the database continously will be a burden.
One other option coming to my mind, is to open up php page when the user wants to share hislocation. This page will continously communicate with the gps information. But as this pages should be specific to some people, they must have unique, unpredictable url. like the ones when have password recovery mails. Also i wonder how to make this pages exists for some time and disappears.
The page links will be sent to the shared users lately.
So what about storing any of the URI data is a session array and when a specific key is requested by the user use the received key and replace it by your actual URI stored in the $_SESSION array?
when generating a uri you could have a method store it for you and return a replacing URI
like so (note that you should have session already started that should not be part of this objects scope):
class URIStore{
private $URIstoreID;
private $SUPERSECRETSALT;
private $storeName;
const STORE_EXCEPTION_EXPIRED = 0;
const STORE_EXCEPTION_CSRF = 1;
public function __construct($storename){
//retreive existing store ID so we can built on previous
$this->URIstoreID = isset($_SESSION[$storename]['URIStoreID']) ? $_SESSION[$storename]['URIStoreID'] : 0;
$this->SUPERSECRETSALT = 'jsf098u32rojwe092';//salt could be made random here
$this->storename = $storename;
}
/**
* stored the $uri data in a session for later retrieval through the returned key
* #param mixed $uri - can be anything that you want, like a key value pair array
* #return string
*/
public function store($uri){
$r = md5($this->URIStoreID.$this->SUPERSECRETSALT);
$_SESSION[$this->storename][$r] = $uri;
$this->URIStoreID++;
$_SESSION[$this->storename]['URIStoreID'] = $this->URIStoreID;
return $r;
}
/**
* returns a previously stored URI item
* #param string $key - the key you want to get the URI data for
* #return mixed
* #Throws Exception - when Store or Key doesnt exist
*/
public function retrieve($key){
if(!isset($_SESSION[$this->storename]){
//the initial session has expired
throw new Exception("URIStore [$this->storename] does not exists",self::STORE_EXCEPTION_EXPIRED);
}
elseif(!isset($_SESSION[$this->storename][$key]){
//the requested key does not exist
//could be CSRF attempt?
throw new Exception("URIStore [$this->storename] does not contain this key",self::STORE_EXCEPTION_CSRF);
}
return $_SESSION[$this->storename][$key];
}
}
the use the above you could do the following when building a URL
$store = new URIStore('URIStore');
$URL = SERVER_URL . 'yourscriptthathandlestherequest.php?key=' . $store->store(array('a'=>1,'b'=>23));
and when you retrieve a request you first get the actual data instead of get data
like
$store = new URIStore('URIStore');
try{
$data = $store->retrieve(#$_GET['key']);
}
catch(Exception $e){
switch($e->getCode()){
case URIStore::STORE_EXCEPTION_EXPIRED:
//handle accordingly
break;
case URIStore::STORE_EXCEPTION_CSRF:
//handle accordingly
break;
}
}
note that in your case GET is fine, for CRUD actions I would highly suggest using POST
the Session will expire when no requests are made for a while (depends on server settings)
if you want different expirations for different items (still limited to the session expiration time as a maximum though) you would add another layer to the array that stores the uri and an additional timestamp that you have the URIStore::retrieve method compair against. and finally you could use multiple stores for different things if you wanted. so that you only use the one you expect on a certain page.
You could give the users a link like: script.php?key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx where the x's are an MD5. That MD5 could be the username and a "salt", like sort of a password. In other words, if $_GET['key'] matches md5($username . $super_secret_site_password) then you'll know that person is the real deal.
You need to have some kind of storage anyway, so I'm not goint into that.
The simplest solution is probably to have a unique number (Random unique ID or sequence number), suffix it with something like "read" and "write" and then secure it by building a hash over it and a secret key:
$secret = 'changeme';
$user_id = uniqid();
$public_url = 'page.php?user_id='.$user_id.
'&access=r&check='.sha1($user_id.'r'.$secret);
$owner_url = 'page.php?user_id='.$user_id.
'&access=w&check='.sha1($user_id.'w'.$secret);
Then you can check for them like this:
if ($_GET['check'] == sha1($_GET['user_id'].'r'.$secret))
{
// show location
}
if ($_GET['check'] == sha1($_GET['user_id'].'w'.$secret))
{
// do location update
}
If you don't want the user_id and access type to be visible in the URL you can use phpseclib to encrypt it instead of just securing it by sha1().
I'm toying with the idea of creating automatic electronic certificates. It's pretty easy to create custom certificates using the fpdf PHP class. The way I have it set up is that given a URL
http://www.example.com/makepdf.php?name=myname&class=classname
you get a PDF certificate with the student name and the class they took taken from the $_GET variable. Of course, this means that anyone could manipulate the URL to very easily create a custom certificate. (They could do this in Photoshop anyway, but the idea is to make manipulating the certificate not totally trivial.) Once a class is over, I want to send a mail merge to everyone with a unique URL for their certificate.
How would you approach this problem? Should I just create a set of random numbers and associate these with the student/workshop pairs in a database? Are there standard ways of approaching this problem?
Couple solutions stand out:
Store the names & classes in a database, and reference them with a numeric ID instead of passing the data in the request
Keep the information in the request, but add a secure hash that will prevent tampering with the data
The hash mechanism would be something like this:
When generating the link for the certificate, you have $name and $class. You'll create a third GET variable that is a hash of $name, $class, and a secret string that only your program knows. Something like this:
$salt = "this is my secret";
$hash = md5($name . $class . $salt);
$url = "http://www.mysite.com/certificate.php?name=" . urlencode($name) . "&class=" . urlencode($class) . "&hash=" . $hash;
Now when a user hits your certificate generation page, you must verify the hash:
$salt = "this is my secret";
$expected = md5($_GET['name'] . $_GET['class'] . $salt);
if ($expected != $_GET['hash']) {
die("You are not authorized");
} else {
// User is OK; generate the certificate
}
Yes, if you want to limit your inputs to a fixed pool, then creating a database full of random keys is the way I would go.
If you want a quicker and dirtier way to do it, just generate the keys into a text file, use a script to pull the file apart to send them to the recipients, and have your PHP certificate generator read from a copy of the file on the server.
Assuming you are generating these URLs yourself on the server, you could join all your parameter values together into a string:
hash_string = "myname:classname";
Then append a final parameter that's a hash of that string along with some secret seed:
query_string .= "&h=" . md5("my_secret_key:" . hash_string)
Then, when you get the query back, just check to make sure that the hash matches:
hash_string = params['name'] . ':' . params['class'];
if (params['h'] == md5("my_secret_key:" . hash_string)) ...
I don't really know PHP syntax, but you get the idea.
Your best bet would be to have a list of students/classes (some kind of database) and only allow generation of allowed certificates. That way you don't need to obfuscate the name of the student or class, because only valid certificates can be generated.
If that's too much to ask - you could generate a MD5 hash based on the combination and some salt, then add that hash to the URL. That way the salt would need to be know to forge a URL.
http://www.example.com/makepdf.php?name=Tim&class=PHP&hash=c2c455ce438112b44499561131321126
Then the generation script just does this:
$hash = md5($_GET['name'] . $_GET['class'] . $salt);
if($hash != $_GET['hash']){
//invalid request
}
Of course you'll need to generate the URL's with the same salt.
Should I just create a set of random numbers and associate these with the student/workshop pairs in a database?
Yes.