I've been looking at articles online about solutions to encrypting id's in a url. I've tried the basic encode decode but the problem i'm having when decoding it on the next page where I do a select where id = decoded id. It won't grab the proper user still from the table.
My link:
My link:
<a href="sendContract.inc.php?id=<?php echo
encrypt($customer_id) ?>"> View Contract </a>
sendContract.inc.php page:
$customer_id = $_GET['id'];
$decryped_id = base64_decode($customer_id);
$sql = "SELECT *
FROM bookings
LEFT JOIN customers
USING (customer_id)
WHERE customer_id = '".$decryped_id."'
";
UPDATE: Now that I understand to that urlencode needed to be used, it works in the URL properly. The page is displaying a customers contract. And it's only unique to them. The contract link gets sent by email which is just a link with their customer_id (which is now encoded, decoded). I'm wondering what else can I do to secure their link and info? The contract is displayed as a PDF in the link (using tcpdf).
I like to use Hashids for this:
Hashids is a small open-source library that generates short, unique, non-sequential ids from numbers.
It converts numbers like 347 into strings like “yr8”, or array of numbers like [27, 986] into “3kTMd”.
You can also decode those ids back. This is useful in bundling several parameters into one or simply using them as short UIDs.
Hashids has been ported to many languages, including PHP. Here is an example from their website:
$hashids = new Hashids\Hashids('this is my salt');
$id = $hashids->encode(1, 2, 3);
$numbers = $hashids->decode($id);
It appears that you're not encrypting the ID, you're only encoding it in base64 which means that anyone can decode it. Here's an example showing a simple encoded string.
$str = 'This is an encoded string';
echo base64_encode($str);
This will output VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==. If you notice, this string contains an equals sign. In fact, base64 encoded strings can contain "+", "/", or "=" which all need to be URL encoded before they can exist in a URL. Therefore, use the urlencode() function before passing it into the URL. For example,
$str = 'This is an encoded string';
echo urlencode(base64_encode($str));
will output VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw%3D%3D which is safe for URLs. Then when you need to decode the URL as in your example, you would do the following.
$customer_id = $_GET['id'];
$decoded_id = base64_decode(urldecode($customer_id));
$sql = "SELECT *
FROM bookings
LEFT JOIN customers
USING (customer_id)
WHERE customer_id = '".$decoded_id."'
";
BUT REMEMBER, this implementation DOES NOT encrypt the ID, it is only encoded which means ANYONE can decode it.
I would create a table specifically for this email operation. In said table I would have the id( auto-increment), user_id, create_date and hash. Then in the email you would pass the hash and look this up. The tie with the user_id is there but you are not exposing their actual account information. You can also delete this data after it is used, or after some amount of time has elapsed. This way the link will only work for limited amount of time. You could connect this table back to the user table with a very simple join INNER JOIN users ON {email_table}.user_id = user.id or what have you.
For the hash it could be as simple as
md5($id . $create_date );
This would work just fine and serves only to make the url "pretty". Because you are saving the hash in the table as part of the row data, it dosn't need to be related to the data in that row at all. Basically it takes all the guess work out of it. I would also look into not exposing the '.php' extension. Most MVC frameworks can do this and it just make the url a bit cleaner like this
http://yoursite.com/sendContract/jsdkfe1902303kllllde
In my opinion the only way to really secure this is to limit how often and how long the url in the email could be used. You could also force them to login at the url, then you can be sure it is the account holder.
In order to avoid guessing Id by end user, I could suggest a way that takes use of hashed id. For example, you use MD5 as hash algorithm and database MySQL
Your url would be
.../path?id={hashed_id}
When looking up at database your query will be
Select ...
From ...
Where MD5(ID_COLUMN) = {hashed_id}
By this way, you only expose hashed value of id to customer, not the id itself. Moreover, you dont need to maintain a secret key which could be potentially leaked out.
Related
I am currently working on a new web based project with various types of entities. This service will be accessible through an REST API, and I'm thinking about endpoints like:
api.example.com/users/{user_id}
For this, I think that an auto-incremental ID for users will be a bad approach, since anybody can hit:
api.example.com/users/1, and then api.example.com/users/2, api.example.com/users/3, and so on.
Now, I'm thinking to use UUID, but I don't know if it's a good idea, because it's a VARCHAR(36). For these reason, I do something like this when I generate the user ID on the INSERT query (I'm using MySQL):
unhex(replace(uuid(),'-',''))
With this, I'm casting the UUID to binary. And I'm storing an BINARY(16) on the database instead.
And when I want to retrieve info from database, I can use something like that:
SELECT hex(id), name FROM user;
and
SELECT hex(id), name FROM user WHERE hex(id) = '36c5f55620ef11e7b94d6c626d968e15';
So, I'm working with Hexadecimal form, but storing it in binary form.
It is this a good approach?
Almost there...
Indexes are your performance friend. Presumably id is indexed, then
WHERE id = function('...')
uses the index and goes straight to the row, but
WHERE function(id) = '...'
cannot use the index; instead, it scans all the rows, checking each one. For a large table, this is sloooow.
So...
Use this to store it:
INSERT INTO tbl (uuid, ...)
VALUES
(UNHEX(REPLACE(UUID(), '-', '')), ...);
And this to test for it:
SELECT ... WHERE
uuid = UNHEX(REPLACE('786f3c2a-21f6-11e7-9392-80fa5b3669ce', '-', ''));
If you choose to send (via REST) the 32 characters without the dashes, you can figure that minor variation.
Since that gets tedious, build a pair of Stored Functions. Oh, I have one for you; see http://mysql.rjweb.org/doc.php/uuid .
That also discusses why UUIDs are inefficient for huge tables, and a possible way around it.
This is probably a repeated question but I really can't find any answers. I have a forgotten password script which grabs the users email address from the URL using $_GET. The script then checks the $_GET for error messages then passes through to mysql to check weather the user id is the same.
Now the script itself is fine but the user id comes back as a string instead of an integer in var_dump(). I was going to let it go but then I read that if the user id is not an integer in a $_GET variable it could lead to an attack, which has got me a little bit worried. I have tried to change the num_rows value to an int but with no success, because num_rows returns an array. The code that checks the user id is:
if (mysql_num_rows($result) == 1) {
// NEED TO TURN THIS ARRAY TO AN INT
list($userId) = mysql_fetch_array($result, MYSQL_NUM);
}
//elseif (mysql_num_rows($result) <= 0) {
else {
$wrong = '<p style="color: red">Something went wrong. Please try again</p>';
}
Like I said before the script itself is fine, it's just that I read something and now can't get it out of my head, which has lead me to ask you guys. Doe's a user id have to be an integer or can i get away with it as a string?
EDIT: Thanks for all your comments and suggestions. There's still a lot to learn in php. Cheers again.
The user ID doesn't have to be an integer, however it is usually
unique (one per user)
formatted a way that it is searchable efficiently in a database (for instance)
short so that it doesn't take much space, especially for large users bank
therefore, ideally, that would be an integer (which number of bits must cover the largest number of expected users)
unique - for instance allocated automatically (incrementally) by a database
efficiently indexed, and being the small pointer to a larger user table
short
one of the drawbacks is that if the integer is given to the client browser (eg in a form), the client may guess the number of user / change the form user ID since numbers in sequence can be easily guessed... (that needs to be prevented via further security)
Everything you pull from the $_GET array is going to be a string, owing to the fact that there's no type information sent through http.
You should, however, always be sanitizing user input or using prepared statements to prevent SQL injection attacks.
You mixed things up, I'll try to clarify them. When PHP receives data via HTTP protocol, be it GET or POST - all the data is considered to be a string because there is no way to safely identify what the data should be.
That means you can try to coerce a piece of data you got via $_GET or $_POST into a different type internally, using typecasting, such as $id = (int)$_GET['id'];
As for databases, the id (let's call it Primary Key) should always be an integer.
Why:
- InnoDB uses a certain principle internally when it comes to actual data-structure organisation and writing the information to the disk. It accomplishes a significant performance gain by using sequentially incremented integers
- It's easy to create unique identifiers using integers. Just increment some number by an offset every time you do an insert and you get a unique number assigned to the row, no need for a complex algorithm that calculates unique identifier such as UUID() etc.
Long story short - keep the user's ID as integer in MySQL and if you receive data via $_POST/$_GET - type cast them into what you want them to be (int, float, string etc.)
A user ID can be anything you want it to be ... I would only suggest that it be something that has a unique key on it in the database and doesn't change over time.
ID's are traditionally used because it is good practice to have an auto-incrementing primary key on your tables anyway and it meets all of the criteria to be an effective user id.
I am looking for the best way to write out a php/mysql query to create unique user id's rather than using the autoincrement method in mysql.
Ex: Facebook gives users a long string of numbers as a user id when singing up before you can assign a username. This string of numbers can be used to view your profile OR you can use username. I want users to be able to change username in the future, so don't want to design my system based on username.
I don't know how big the site will get, so please take that into consideration with the solution. I don't want something that is going to be server intensive if there are alot of users signing up.
There isn't really a best route for something like this. Essentially you need to ask yourself what your system requires. You may be able to use an email address as the ID, an auto-incremented number, MD5 hash, or even a heavy-entropy GUID.
Keep in mind that email addresses may change, auto-incremented numbers can be leveraged in automated exploits, and there's technically some chance of hashes colliding.
If you decided to go the route of generating a high-entropy GUID using PHP, you could do so using a function like uniqid.
echo uniqid(); // 513ac40699d85
echo uniqid("_", true); // _513ac3e00bfe46.78760239
The second line shows the two arguments you can provide; a prefix, and a request for more entropy, which will result in a more unique result.
You should follow some algorithm like this:
Enter your new user into the database.
Get the record ID
Generate the userID
Insert the userID next to the name into the sql database.
Enter your new user into the database.
//get username from previous form
$user=$_POST['user'];
// login into mysql server and prepare data for writing
$connect=mysql_connect('localhost', $user, $pass);
$selectdb = mysql_select_db('mydb');
$query = "insert into users_table set
username='$user';";
$run_query=mysql_query($query);
Get the record ID
$id=mysql_insert_id();
Generate the userID
$first_chars=substr($user, 2);
$year=date('y');
$new_user_id= $first_chars.$year.$id;
Insert the UserID next to the name into the sql database
$query="update users_table set userid='$new_user_id' where id='$id';";
$run_query=mysql_query($query);
if (!$run_query) {
echo mysql_error();
}
else {
echo 'your user name is '.$user.' and user id is '.$new_user_id ; }
You can use mysql as a database. Wampserver combines everything and makes it easy. However, i'm not sure if I can help you very much because your question is very vague. Add some more detail please.
Use a hexdigest like sha or md5 to generate an id something like sha1($uname+$timestamp+$salt)
By doing this your will be storing a lot of data for each entry as sha1 takes up 40 bytes.You have already mentioned that the site is may go big,making it a huge amount of data.Decide whether its worth that lot of space.
PS:you can always slice the string,but the collision chance is more that way.
Right now I can get the information for a product in my database by simply using its ID in the URL lik so: http://www.example.com/?productID=5
Now I can guess the next one since these IDs are all auto incremented.
How would you hide this ID and replace it with something better? I mean it still has to be a unique identifier.
To complicate it a little I was thinking I could use a global unique identifier and add this in the database in another column. But I have the feeling that this is not the best way to do this :)
Please let me know your thoughts and tell me how you solved/would solve this in your project.
What I basically want is to get rid of knowing the next/previous ID.
There are no security limitations on the products themselves. Users are most likely allowed to see them. I was thinking that this technique could prevent crawling all products?!?
Like what amazon.com does: It looks like they hide the item's ID in some way, right?
You could also use a slug which works great for SEO. so Product 1 slug could be http://www.example.com/?productSlug=exampleitemnameasoneword
You just have to make sure slugs are unique. You would of course need a slug field in your database entry and be able to enter/edit it.
First of all, have a look at mod_rewrite.
I am making use of CakePhp, this allows me to have easy access to urls created by the .htaccess rules. In my case, my titles are 100% unique, which allows me to have a slug version of the title.
For example my title is SZD-51 Junior, this will become SZD-51_Junior. I just add this slug to my database and use this as a secondary unique identifier. When a user visits www.example.com/planes/SZD-51_Junior I can do something like:
// pseudo mysql
SELECT *
FROM table
WHERE slug = url_param
//in cakephp
$this->Plane->findBySlug($this->params['slug']);
There are a lot of approaches, what would you like the url to become? Which format?
you can use some kind of hashing algorithm like md5(), sha1() etc... and yes, it is better to store that hash in another column in your table for faster lookup.
If you use md5() it is a good practice to seed it with fixed or random salt string.
My favorite is to do it with microtime().
Generate hash before db insert:
$salt = "my_secret_salt_string";
$entry['hash_code'] = md5($salt . microtime()); // every time a different result is generated which will ensure uniqueness
// insert into ...
Or if you prefer using generated id as a salt, then update entry:
// insert into ...
// get id
$entry['hash_code'] = md5($id . microtime());
// update ...
A solution to this is to use post instead, this is just a quick mashup i did.
<?php
//how to handle the request
if(isset($_POST['transfer']) && !empty($_POST['transfer']))
{
$transfer = $_POST['transfer'];
//what to do?
}
?>
//how to make a link with id 5
link
//this is for the functionality
<form id="transfer_form" action="http://www.example.com/" method="POST">
<input type="hidden" id="transfer" name="productID" value="" />
</form>
//this should be placed within the <head>
<script>
function tranfer(link)
{
document.getElementById('transfer').value = link;
document.getElementById('transfer_form').submit();
}
</script>
I am making a search feature where the search value should be compared with two columns in a db table. One column is just a name and the other column is en encrypted value in format "xxxxxx-xxxx" (only numbers). The user should be able to just search for part of the total string in the table.
For the name comparison I use where name LIKE %search_value%, but for the encrypted value I can't use that way of doing it.
Any ideas to how a good way of doing the comparison would be?
You couldn't use a wildcard search for crypted values, because the crypting of 'a' is ENTIRELY and UTTERLY different than the crypting of 'bac'. There's no practical method of doing sub-string matching within a crypted field. However, a simple direct equality test is doable. If you're a DB-side function like mysql's aes_encrypt(), then you could do
... WHERE
(name LIKE '%search%') OR
(cryptedfield = AES_ENCRYPT('search', 'key'))
For substring matching, you'd have to decrypt the field first:
... WHERE
(name LIKE '%search%') OR
(AES_DECRYPT(cryptedfield, 'key') LIKE '%search%')
basically, if it needs to be encrypted, no part of the system should be able to search it. if it should be searchable, then it probably doesn't need to be encrypted.
otherwise you are kind of defeating the purpose of encryption.
If you're looking for full-text search capability of encrypted data (without the database server being able to decrypt the messages), you're in academic research territory.
However, if you only need a limited subset of searching capabilities on encrypted data, you can use blind indexes constructed from the plaintext which can be used in SELECT queries.
So instead of:
SELECT *
FROM humans
WHERE name LIKE '%search_value%';
You could do this:
SELECT h.*
FROM humans h
JOIN humans_blind_indexes hb ON hb.human_id = h.id
WHERE hb.first_initial_last_name = $1 OR hb.first_name = $2 OR hb.last_name = $3
And then pass it three truncated hash function outputs, and you'll get your database records with matching ciphertexts.
This isn't just a theoretical remark, you can actually do this today with this open source library.