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.
Related
My old PHP app has a default admin user and md5 encrypted password created by the SQL that creates the database: insert into users values ( 1, 'admin', MD5('changeMe'), 2 );
Is there a simple way to include a default user and encrypted password using PHP's passowrd_hash function on creating the tables? I ask because I understand that password_hash is a native PHP function and I assume it won't be understood in SQL.
The solution to my problem came in three parts. My OP sought a simple way to create a hashed password for the admin user for insertion in the MySQL database on the installation of the application, using the native PHP password_hash() function.
(1) Based on a suggestion by #Nick and #Tadman, I decided to incorporate setting the hash in an installer script that would set not only the hash but other defined site/application variables.
Rather than inserting user values when the database table is created, it was deferred until immediately after, with the admin user entering their credentials in the form that inserts the hash and writes other definitions to a file:
$userpass = $_POST['userpass'];
echo password_hash($userpass, PASSWORD_BCRYPT);
(2) The second part of my problem was replacing all instances of md5()`` withpassword_hash()` and I achieved that by using a neat PHP script I found online to recursively search and replace the occurrences on the server.
Having replaced the md5() occurrences, I needed to change the hash comparison method and again by searching the relevant files I was able to replace instances of:
if ($p != $theUser->pwd ) {
return( false ); }
with:
if(password_verify($p, $theUser->pwd)) {
// Success!
}
else {
// Invalid credentials
echo "Uh oh!";
}
(3) The third step in resolving the problem was discovering that adding $1$ to the opening of the md5 hash could make it readable by password_hash(); so I just needed to make a couple of adjustments in the installed database to the admin user's old password.
Thanks to those who helped shine the light so I could find my way. I'm off now to invent the wheel and sliced bread.
you can do something like this in php:
$hash = password_hash('changeMe');
//echo $hash;
then use this hash in the Database.
I'm stuck with PHP 5.4 and a single database userid because of defects in Comcast's business hosting implementation. The userid problem means that the same credentials I use to manage a MySQL database must also be used while handling requests from ordinary users. I have control over who can login as an ordinary user by delegating the creation of ids to a group of trusted users who will communicate to the ultimate end users the URL they will need to login to the app. And, although the server is running Apache, it ignores an Authorization header.
Positing that some hacker might defeat the HostWays Unix server's file system protection and read all of my PHP source code, I need to avoid having the database credentials appear in the MySQL connect calls in my PHP source. I've come up with the following scheme for encrypting the database credentials, and I want to be sure that the scheme will actually be effective.
I will define a global variable named $credentials in a configuration file. I'll generate the value in my development environment with this code:
$cipher = "AES-128-CBC";
$secretkey = …; // it's a secret!
$secret = openssl_encrypt("$dbuser:$dbpass", $cipher, $secretkey);
Remember, I'm stuck with PHP 5.4, and I wasn't able to get past mysterious errors in openssl_decrypt when I used an initial-value in the encrypt/decrypt calls. Anyhow, this much of the code at least works.
My PHP app can be accessed two ways: one is via an XML payload in a POST transaction coming from a C++ desktop app; the other is via an ordinary URL in a GET. Both methods will use HTTPS. The XML schema calls for an authentication parameter that supplies the $secretkey.
The URL for the GET transaction will be used by general users from a browser. It will include a secret= parameter that encodes both the user's login id and the $secretkey. I will generate the secret= parameter during the transaction that adds the user to the list of authorized users:
$cipher = "AES-128-CBC";
$key = "Fortune42Cookies!";
$secret = openssl_encrypt("$username:$secretkey", $cipher, $key);
In this code, the $secretkey is left over from when the trusted user who is adding this end user to the list logged in. The corresponding decryption code used when the end user logs in is as follows:
$cipher = "AES-128-CBC";
$key = "Fortune42Cookies!";
$clearsecret = explode(":", openssl_decrypt($secret, $cipher, $key));
$username = $clearsecret[0];
$secretkey = $clearsecret[1];
$this->db = new mydatabase($secretkey);
. . .
class mydatabase extends mysqli {
public function __construct($secretkey) {
public $credentials; // set by the configuration script
$cipher = "AES-128-CBC";
$cred = explode(":", openssl_decrypt($credentials, $cipher, $secretkey);
parent::__construct(<database path>, $cred[0], $cred[1], <dbname>);
}
}
To summarize, the __construct call uses $credentials that are stored in encrypted form in a configuration file that my putative hacker can read. We decrypt them using the $secretkey that comes to us either in a secure XML packet or in the URL for a secure GET transaction.
The one flaw I see in this scheme is that a hacker who learns the login id of a registered user and who reads the PHP source code to learn the silly Fortune42Cookies! key could construct that user's unique $secret and thereby impersonate the user. Because there's only one set of credentials for the database, the hacker would then have the keys to the city.
Still, the extra layer of protection (hacker needs to defeat the UNIX file system protection AND learn the name of a real end user) seems worth it.
Fifty years of software development experience tells me I've missed something major. What is it?
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 have read about users being able to manipulate website cookie and use it to exploits security loopholes. I did a search and came across an idea posted online. Here is the code below that is, after the username and password of the user are authenticated;
$Separator = '--';
$uniqueID = 'jhlhgjh12u0#345';
$Data = $userID.' '.md5('65748');
$expire=time()+60*24;
setcookie('verify-user', $Data.$Separator.md5($Data.$uniqueID), $expire);
The code above will set the cookie using a uniqueID, the userID, a MD5 hash numbers and a separator. The uniqueID, md5 hash numbers and separator are set by the developer. The idea is that a user won't be able to manipulate the cookie because the don't know the UniqueID, and the md5 hash numbers. The code below is used to test each cookie if they are manipulated or not
if ($_COOKIE) {
$Separator="--";
$uniqueID = 'jhlhgjh12u0#345';
$Cut = explode($Separator, $_COOKIE['verify-user']);
if (md5($Cut[0].$uniqueID) === $Cut[1]) {
$_COOKIE['verify-user'] = $Cut[0];
} else {
echo "fake cookie";
}
}
else {
echo "fake cookie";
}
I am wondering if this method is security tight or if there are loopholes too. criticism and corrections are welcomed
This is known as message signing. You hash the message together with a secret and attach that "signature" to the message itself. This allows the recipient to verify that the creator/signer of the message is in possession of the secret, without revealing the secret itself.
The problem with your particular implementation is that
the secret is too small
the hashing algorithm is unsuitable for the task
the cookies never change and never expire; if a cookie is stolen there's no recourse
You should use a longer secret, the longer the better. You should also use a hashing algorithm that is suited for the task, namely something like HMAC (hash-based message authentication). E.g.:
hash_hmac('sha512', $data, $secret)
You can see an implementation of a similar thing, including expiration of values, here.
The most important thing though: think thrice about whether a signed plain text message is the best way to go here in the first place. Perhaps you want a session-like system, in which an entirely meaningless random string is used as an id for data that is stored on the server. This completely eliminates the problem of users manipulating the cookie.
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().