How to hide MySQL generated ID in URL - php

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>

Related

What kind of ID to use and how to store it in the database?

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.

what is a good way to encrypt a id in a url?

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.

Friendly URL Database Strategies

I have a website that I want to start using friendly URLs.
So instead of:
http://server.com/company.php?id=12
I could use:
http://server.com/company/steaks_r_us
I know how to do this on the .htaccess side, but I don't know how to set it up on the backend with the database calls, especially if there is a chance that company name isn't unique (multiple locations for instance).
I've thought about inserting the friendly url into the database after checking for duplicates, but I'm curious if there is a better way
No matter if it's an ID or a user friendly string like company name, you need it to be a unique identifier. You won't avoid that.
There are couple of options.
Some websites adds ID (primary key) to the user friendly name like:
http://server.com/company/steaks_r_us_12
or in other part of URL like:
http://server.com/company/12/steaks_r_us
Then you can easily fetch the ID from the URL, but also it still looks nice.
Also as wheatin suggested, you could create a field in your DB that would be this unique identifier.
In this case you would need some additional logic at the time of creation of company row in database. That's of course because you have to handle duplicated names somehow.You could for example add a digit at the and of this value (facebook does something like that), so if a duplicate of steaks_r_us occurs, you would insert a value steaks_r_us2
Then you would have unique URL for these companies:
http://server.com/company/steaks_r_us
and
http://server.com/company/steaks_r_us2
You could create a unique index on the business key (in this case the company name) to prevent duplicates from being inserted in the first place.
Wheatin has the right idea. You'd want to have a table with two columns, the URL and the id it's referring to. If you made a unique key on the URL column, then you could just have a function/method that tries to insert the clean URL with the content id; I usually do something like
###(pseudocode)
function create_unique_url($url, $id) {
try to insert the url, id combination
if success:
return url
else:
key = 1
start a loop
try to insert the url+key, id combination
if success:
return url
key++
}
Adding a number to the end is a good fail-safe way to make sure you always get a unique url returned. Sort of how Drupal or Wordpress would handle a post url with an identical name.

Obfuscating database ID to customer facing number

I'm using mysql database auto-increment as an order ID. When I display the order ID to the user, I want to somehow mask/obfuscate it.
Why?
So at first glance, it is obvious to admin users what the number
refers to (orders start with 10, customers start with 20 etc)
To hide, at first glance, that this is only my 4th order.
Based on this this answer, I want the masked/obfuscated order id to:
Be only numbers
Consistent length (if possible)
Not cause collisions
Be reversible so I can decode it and get the original ID
How would I acheive this in PHP? It doesn't have to be very complex, just so at first glance it's not obvious.
I think you can use XOR operator to hide "at first glance" for example (MySQL example):
(id*121) ^ 2342323
Where 2342323 and 121 are "magic" numbers - templates for the order number.
To reverse:
(OrderNum ^ 2342323)/121
Additional advantage in this case - you can validate OrderNumber (to avoid spam or something like this in online form) if (OrderNum ^ 2342323) is divided by 121 with no remainder.
SQLFiddle demo
A little bit late, but Optimus (https://github.com/jenssegers/optimus) does exactly what is here asked for.
$encoded = $optimus->encode(20); // 1535832388
$original = $optimus->decode(1535832388); // 20
Only the initial setup is a bit weird (generate primenumbers)
Probably the simplest way is to just generate a long random string and use it instead of the auto-increment ID. Or maybe use it alongside the auto-increment ID. If the string is long enough and random enough, it will be unique for every record (think of GUIDs). Then you can display these to the user and not worry about anything.
Can it help?
echo hexdec(uniqid());
Off course you should store this value at db, at the same row with order id.
Just converting a ID into something like HEX might not give you the result what you like. Moreover its still easy 'guessable'
I would a a extra ID column (i.e. order_id). Set a unqi. index. Then on_creation use one of the following mysql functions:
SHA1(contcat('ORDER', id))
MD5(contcat('ORDER', id))
SHA1(contcat('ORDER', id, customer_id))
MD5(contcat('ORDER', id, customer_id))
UUID()
// try this in your mysql console
SELECT UUID(), SHA(CONCAT('ORDER',10)), SHA1(1);
You could (as in the example), add a simple text prefix like 'order'. Or even combine them. However i think UUID() would be easiest.
Implementation depends a bit on what you prefer you could use a stored procedure) or incorporate it in your model.

creating user id's (best route)

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.

Categories