MD5'ing Passwords

Discussions of secure PHP coding. Security in software is important, so don't be afraid to ask. And when answering: be anal. Nitpick. No security vulnerability is too small.

Moderator: General Moderators

Roja
Tutorials Group
Posts: 2692
Joined: Sun Jan 04, 2004 10:30 pm

Post by Roja »

AGISB wrote:I totally disagree. As I stated they are stored in a special table of the database that is only accessable with a special mysql user.
On the contrary, the signup script has that access, so whether it is a seperate user or not, if your script is compromised, that account is - so the table is exposed.
AGISB wrote:The added security is minimal. You prepare for the near impossible superdisaster because if the database is hacked you should have probably paid more attention to secure the server. So if I store them for further usage in an extra secured table I have done nothing to make my site more insecure.
Yet again, you aren't correctly stating the risks in reality.

I've personally seen multiple sites compromised, allowing their user account information to be stolen. None of them were through obvious webholes. One in fact took *three* unique and fairly recent security weaknesses to get "to the gold".

Having your users information encrypted isn't overzealous, isn't "anticipating the near impossible", or prevention for a superdisaster. Its avoiding the most common source of information leak, and doing so with minimal cost.

As to your concern about users using weak passwords, you are making an extremely poor cost benefit analysis. To avoid the cost of one user account being compromised (because they choose a weak password), you are avoiding encrypting *all* users. The benefit is that you get to ensure that an easily guessible password is tested.

In contrast, you can allow users to pick their passwords at will, encrypt them, and by doing so, ensure that if there is a compromise, *no additional accounts will be compromised*. Thats not something to shrug off - thats throwing out reasonable security for hundreds of users to prevent a bad choice by one.

Testing a password isn't challenging while still maintaining encryption. Either you allow javascript to be off (in which case, you pass the password in cleartext at least initially, and can test it before encrypting), or you use javascript to encrypt - in which case you can instead use javascript to test the length, complexity, and so forth before encrypting.

Either way, you can have both goals - a test to ensure the password is complex, and encryption for the password.

But please don't argue that storing passwords unencrypted is secure. There is nothing secure about it, and it is in no way more secure than encrypting them.
AGISB
Forum Contributor
Posts: 422
Joined: Fri Jul 09, 2004 1:23 am

Post by AGISB »

I never argued that it is more secure but made a statement that for me there are reasons that one can choose to save the passwords in plain text. I never stated that I exclusively do it for all my sites.

If I store personal information in the database there are way more important fields to worry about than the password. At one of my sites the intruder would get everything from address to financial information. As I require secure passwords I dont need them in plain text here but if an intruder gets in I have bigger problems than a password. Thats the reason I look out security updates for every used software and OS. I don't run shared environment and have a lot of messures in place to detect intruders.

I totally agree that you should md5 the passwords but if I don't check or require the password to be secure this messure will only cost the intruder not more than a day to check and resolve about 95% of the password against a possitive list.

What I am trying to say here is that this measure is not as effective as you all seem to think.
Roja
Tutorials Group
Posts: 2692
Joined: Sun Jan 04, 2004 10:30 pm

Post by Roja »

AGISB wrote:If I store personal information in the database there are way more important fields to worry about than the password.
Only for your site. Those passwords are in all likelihood reused across multiple sites. So while you may find the SSN or address to be more valuable, Bob Smith who reuses the same password (and login) on his bank site may be *far* more concerned about the password.

Passwords are a shared secret. When you, as a site admin (or developer acting on the admins behalf), save passwords in cleartext, you are violating a trust. You are telling your user you safeguard their data, but you are not doing so.

No matter what other controls you have in place, storing a password in cleartext is not taking the appropriate steps to secure the data. Its insecure in and of itself - even if the rest of the site is Fort Knox.
AGISB wrote:As I require secure passwords I dont need them in plain text
Thats the exact issue. You do NOT require them to be in plaintext. Every argument you've given for having them in cleartext is wrong. You can check the strength, you can store them securely, and your other protections do not change the fact that storing them in cleartext is insecure - and for no valid reason.
AGISB wrote:here but if an intruder gets in I have bigger problems than a password. Thats the reason I look out security updates for every used software and OS. I don't run shared environment and have a lot of messures in place to detect intruders.
All you are arguing is that while it is an insecure choice, you have made other choices that make it less insecure. None of them change the fact that it IS an insecure choice. You could do better.
AGISB wrote:I totally agree that you should md5 the passwords but if I don't check or require the password to be secure
They are not mutually exclusive. You can hash the password AND check it for strength! I've already explained how to do so.
AGISB wrote:this messure will only cost the intruder not more than a day to check and resolve about 95% of the password against a possitive list.
Absolutely false. If you use a salt correctly, they will not be able to use a rainbow table to brute force the table. They will be forced to manually attempt every entry. Hopefully, that increased time will give you the chance to notice that your site has been compromised.
AGISB wrote:What I am trying to say here is that this measure is not as effective as you all seem to think.
Hashing a password is extremely effective at increasing the time to recover the passwords. Your method takes *zero* seconds, and *zero* effort, and for no good reason.

You are sacrificing security, and don't have to. Please stop encouraging others to follow that bad example!
User avatar
jayshields
DevNet Resident
Posts: 1912
Joined: Mon Aug 22, 2005 12:11 pm
Location: Leeds/Manchester, England

Post by jayshields »

To me, there's 4 stupid points here:
1. If you have the table open in PHPMyAdmin (or whatever) and someone looks over your shoulder, they could steal any password.
2. You are indirectly stealing peoples passwords. If you told your users that when they register you will be letting the site admin see the password they would not only think that was terrible, they would just simply click away and forget about registering.
3. It takes absolutely no effort to hash a password, and it gives infinite security benefits over not hashing the password.
4. If you are on a shared host, anyone from the hosting company can check your databases at any time and steal passwords at will.

You say there are often more important pieces of information that are being stored than the password, that may infact be true, but you are completely avoiding the point. Why not store this information in your special, super security table aswell? Infact, why not store everything in it?!
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

jayshields wrote:To me, there's 4 stupid points here:
1. If you have the table open in PHPMyAdmin (or whatever) and someone looks over your shoulder, they could steal any password.
2. You are indirectly stealing peoples passwords. If you told your users that when they register you will be letting the site admin see the password they would not only think that was terrible, they would just simply click away and forget about registering.
3. It takes absolutely no effort to hash a password, and it gives infinite security benefits over not hashing the password.
4. If you are on a shared host, anyone from the hosting company can check your databases at any time and steal passwords at will.
Nice points jayshields. Specifically the part about telling the users that you are not hashing their passwords. I would bet, in all likelihood, that if you put a note in your registration/login form that the password the user chose was going to be stored exactly as is with no back-end hashing/encryption, no user would use your site. Just a thought.
AGISB
Forum Contributor
Posts: 422
Joined: Fri Jul 09, 2004 1:23 am

Post by AGISB »

Everah wrote:
jayshields wrote:To me, there's 4 stupid points here:
1. If you have the table open in PHPMyAdmin (or whatever) and someone looks over your shoulder, they could steal any password.
2. You are indirectly stealing peoples passwords. If you told your users that when they register you will be letting the site admin see the password they would not only think that was terrible, they would just simply click away and forget about registering.
3. It takes absolutely no effort to hash a password, and it gives infinite security benefits over not hashing the password.
4. If you are on a shared host, anyone from the hosting company can check your databases at any time and steal passwords at will.
Nice points jayshields. Specifically the part about telling the users that you are not hashing their passwords. I would bet, in all likelihood, that if you put a note in your registration/login form that the password the user chose was going to be stored exactly as is with no back-end hashing/encryption, no user would use your site. Just a thought.
You guys are constructing issues here. Do you really think your customers care? The majority gives a <span style='color:blue' title='I&#39;m naughty, are you naughty?'>smurf</span> as long as they get what they paid for. If they would care they wouldn't use the same password on all sites.

I can also show you dozens of complain emails of people having to choose new passwords when they forgot their old one.

Some other poster mentioned using a salt to protect against rainbow tables. As the intruder got into your server and gained database access he also has full access to your php sources. Your salt is therefore also in the open.

He can also just add some code to save the passwords in cleartext whenever a user uses it. He can then just read it later or he can have the password emailed to him.

So its hashed but the intruder has FULL ACCESS!!! . It makes the reading of the passwords just somewhat harder but someone that skilled gets the info anyway if he just wants it hard enough. As I stated I use md5 passwords on all of my sites but on a couple of them I save the cleartext ones as well. In 2 occasions it was even requested by the customer that I programmed it for.
Roja
Tutorials Group
Posts: 2692
Joined: Sun Jan 04, 2004 10:30 pm

Post by Roja »

AGISB wrote:You guys are constructing issues here. Do you really think your customers care? The majority gives a <span style='color:blue' title='I'm naughty, are you naughty?'>smurf</span> as long as they get what they paid for. If they would care they wouldn't use the same password on all sites.
They care enough that they are filing lawsuits nationwide against companies that have stored their data insecurely, and had it compromised. Here is a quick search result for a few: http://www.post-gazette.com/pg/05202/541454.stm
AGISB wrote:I can also show you dozens of complain emails of people having to choose new passwords when they forgot their old one.
Sounds like your password reset isn't easy enough to use.
AGISB wrote:Some other poster mentioned using a salt to protect against rainbow tables. As the intruder got into your server and gained database access he also has full access to your php sources. Your salt is therefore also in the open.
The goal of the salt in that scenario isn't to be another shared secret. It is to prevent rainbow table lookups. Rainbow tables work because they are precomputed md5(password). By adding salt, it becomes md5(password.salt), which makes a rainbow table lookup impossible - even if you have the salt - because the table wasn't generated with a salt present. That pushes the processing time back up to the full brute force strength of an md5, which again gives enough time to discover the compromise.
AGISB wrote:He can also just add some code to save the passwords in cleartext whenever a user uses it. He can then just read it later or he can have the password emailed to him.
Thats why we advocate not using cleartext passwords at all. Thats a consequence of your choice to do strength checking on the password. More importantly, that code has to be added to the registration process, something the attacker may not have access to do. Best, it only works on new users moving forward - not existing users, who send their password encrypted.
AGISB wrote:So its hashed but the intruder has FULL ACCESS!!!
No, he does not. He has to brute force through each and every password, using their specific salt, which cant be done quickly. Compared to having the password in cleartext, that is a substantial difference, and you continue to downplay it.
AGISB wrote:It makes the reading of the passwords just somewhat harder but someone that skilled gets the info anyway if he just wants it hard enough.
Anything in crypto is generally "possible". But the difference between your solution (zero time to exploit), and a proper solution is not "just somewhat harder" - its a substantial amount of processing time.

In Crypto, the goal is to make it computationally infeasible for the attacker. He may be able to do so, but it is not worth the computing resources to do so. Your solution requires ZERO resources for the attacker.
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

AGISB wrote:You guys are constructing issues here. Do you really think your customers care? The majority gives a <span style='color:blue' title='I'm naughty, are you naughty?'>smurf</span> as long as they get what they paid for. If they would care they wouldn't use the same password on all sites.
Using the same password across sites wouldn't be an issue if all of the sites hashed the password. Not hashing a password seems silly to me. A hacker that gets a hold of hashed passwords still needs to know the hash algorithm used to create the hash, then needs to be able to replicate that algorithm, guess a salt if there is one used, then manage to process the hashing algorithm against your script to use the script to access functionality of the data.

In my opinion, the only exception to this is when a database-intelligent hacker gets into the core of your database for the sole purpose of snagging data from your database. At that point, the password hash is useless seeing as they have access to the data that the password is protecting. But as others have mentioned here, there are cases in which people have chosen to replicate their identities on various servers and sites. It is convenient for them and should be secure for them as well. They are the user and should be given that level of flexibility across the internet. It shouldn't be compromised because one site's developer/owner/admin didn't want to hash a password thereby giving out identity information that could be used for malicious intent at another site.

This is of course just an opinion of mine. I am not trying to create issues or start a flame war. I post here for the same reason as a lot of others... to help developers. Someone, some day, may end up at the forums seeking information on password hashing or the use of md5() on passwords. I would hate for them to leave with the impression that NOT hashing a password in an acceptable method of programming access control. Further, I would hate for them to use insecure logic they found here to deveop their apps, then tell everyone they know that the technique they used to allow their site to get hacked was found on the PHP Developers Network Forums.

I also understand your point that some clients have asked for plain-text storing of passwords. If that is what they want, then that is what they get. But from me, they also get a warning as to what they are doing. If, after being told the risks of their choice, they still choose to do it, that is on them, not me.

AGISB, I don't think anyone is taking this argument to you personally. I just think those in favor of hashing a password are less able to see the logic in not hashing them.
basdog22
Forum Contributor
Posts: 158
Joined: Sun Nov 30, 2003 3:03 pm
Location: Greece

Post by basdog22 »

Please... don't just think of an attacker taking over your server as the only way to view your DB contents... For example my site emails a backup of the db every 3 days to me. What could happen if someone hijacks my email account????

He would have the db backups and the only thing he has to do is just open the file and view the not hashed passwords for every member of my site including me.

So don't go easy on it about someone taking over your server and things like that
AGISB
Forum Contributor
Posts: 422
Joined: Fri Jul 09, 2004 1:23 am

Post by AGISB »

basdog22 wrote:Please... don't just think of an attacker taking over your server as the only way to view your DB contents... For example my site emails a backup of the db every 3 days to me. What could happen if someone hijacks my email account????
Exactly. Why do you mail a backup. This is like the biggest security risk. Use an SSH tunnel to transmit the backup.

It always seems to me that the md5 hashing is used to secure other security problems like this mentioned above.


Roja wrote: They care enough that they are filing lawsuits nationwide against companies that have stored their data insecurely, and had it compromised. Here is a quick search result for a few: http://www.post-gazette.com/pg/05202/541454.stm
Thats an American problem and not a security one. The people only care for an easy buck and the US law system provides them with the means. Besides that people don't sue sites that didn't save their password encrypted and the same password was used to get into other sites where the danger was done.

Roja wrote:Sounds like your password reset isn't easy enough to use.
On the contrary. You just klick on the link and it resets the password to a random one which is mailed. The user then must change that password on their first revisit.

Roja wrote: The goal of the salt in that scenario isn't to be another shared secret. It is to prevent rainbow table lookups. Rainbow tables work because they are precomputed md5(password). By adding salt, it becomes md5(password.salt), which makes a rainbow table lookup impossible - even if you have the salt - because the table wasn't generated with a salt present. That pushes the processing time back up to the full brute force strength of an md5, which again gives enough time to discover the compromise.
It takes less then a couple of hours to create a new rainbow table using a dictionary list or some other lists including a known salt. This is in now way comparable to brute force.

Roja wrote: Thats why we advocate not using cleartext passwords at all. Thats a consequence of your choice to do strength checking on the password. More importantly, that code has to be added to the registration process, something the attacker may not have access to do. Best, it only works on new users moving forward - not existing users, who send their password encrypted.
I am with you here but if the hacker got that deep in the fact of the matter is, that he may not have access to the php sources but he might as well have. The other thing is that you always must have a backup for transmitting unhashed passwords or you need to require all users to have jaascript on which will <span style='color:blue' title='I'm naughty, are you naughty?'>smurf</span> many off.
Roja wrote:
AGISB wrote:It makes the reading of the passwords just somewhat harder but someone that skilled gets the info anyway if he just wants it hard enough.
Anything in crypto is generally "possible". But the difference between your solution (zero time to exploit), and a proper solution is not "just somewhat harder" - its a substantial amount of processing time.

In Crypto, the goal is to make it computationally infeasible for the attacker. He may be able to do so, but it is not worth the computing resources to do so. Your solution requires ZERO resources for the attacker.

Again I am not stating you should not use md5. My point is that the security enhancement is not as great as many peole think. But sometimes you have to go with a little less security, even more so if the customer requested functions that need it that way. That customer doesn't care about if it is less secure he just wants what he wants.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Again I am not stating you should not use md5. My point is that the security enhancement is not as great as many peole think. But sometimes you have to go with a little less security, even more so if the customer requested functions that need it that way. That customer doesn't care about if it is less secure he just wants what he wants.
You should note I make at least the second person with a job related to security to reply...
My point is that the security enhancement is not as great as many peole think.
The security enhancement is immense. It's immense because it's such a basic idea - hash the password into a form which make it computationally challenging to find a valid collision. Bear in mind MD5 is heading out to pasture since its been severely weakened in recent years. SHA256 is remains far more challenging.

What you are stating, apparently, is that other security measures mitigate the risk of someone reading your database of passwords. This *may* be the case. But your practice violates Defense in Depth principals. What if your database is compromised? You think its impossible? Have you additional protections in place in the event of a security breach?

Your statement is not accurate.
But sometimes you have to go with a little less security, even more so if the customer requested functions that need it that way.
This, by the way, is a valid justification within reason. Just so long as you have a client standing over you stating to do it "their" way or else. Password resetting and strength checking can be performed just as easily in an environment where a password is hashed as it is in one with plain text passwords. Roja has already explained the basic methods.

That client may complain about onerous tasks which increase security substantially is their problem. Happens all the time in countless situations. It's one of the common complaints any developer will likely see in their careers. I have dozens too, worse I've seen responses from clients which hammer it to death. I usually have a canned response for the situation which lays out what happens without it. If they don't like it then tough - I'm not in the business of editing reports when the folk in charge refuse to understand a simple concept.

It's a useability issue - and often it's a delicate balance between security and useability (witness the horrible near criminal practice of CAPTCHAs - you think visually impaired people like it? Even I have issues with it sometimes...). The question of course is where the balance sits. Common experience indicates dropping hashed passwords in favour of an easier user experience is plain dangerous. Forget about hackers - what about employees?
That customer doesn't care about if it is less secure he just wants what he wants.
Then they are idiots. Compromised security is one of the best ways to lose your customers when something goes wrong.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

A friend of mine did (unauthorized) penetration testing on a server of mine and managed to gain access (I actually posted a topic on that here). I found out that he had made an SQL dump of the database, although he hadn't touched anything.

Since the passwords were hashed, they could not be accessed. He managed to get in by using the remember me tokens stored unhashed in the database by simply placing them in his own, this was fixed by resetting all the remember me tokens.

I'm starting to think that not only should passwords be hashed, so should any sort of sensitive data that doesn't need to be regurgitated.

Anyway...
It takes less then a couple of hours to create a new rainbow table using a dictionary list or some other lists including a known salt. This is in now way comparable to brute force.
That's an interesting argument, and I agree, if the user used a dictionary word, it's bound to get cracked this way, but... isn't this brute force? Rainbow tables are meant to be reused, as they're a form of time-memory tradeoff.

However, I will admit I'm not exactly sure how rainbow tables work. My conception is that the hacker will systematically generate hashes for strings like 'a', 'b', 'c' ... 'f8dhs3sa' ... however, Wikipedia mentions some sort of reduction function which I have no clue about :lol:

Just an interesting thing to note: whenever the password is sent to the server to, say, login, the server knows about the password. While this somewhat dampens security, it also makes upgrading hashes possible. ;-) See AuthTools for more details.
Roja
Tutorials Group
Posts: 2692
Joined: Sun Jan 04, 2004 10:30 pm

Post by Roja »

Ambush Commander wrote:I'm starting to think that not only should passwords be hashed, so should any sort of sensitive data that doesn't need to be regurgitated.
Be careful that carrying a hammer makes everything look like a nail. There are few other pieces of data that you need to verify, but not access/display. As a result, hashing is rarely useful for webapps beyond data verification and password hashing.

Thats not to say its NEVER useful beyond those, but, its rare.
Ambush Commander wrote:Just an interesting thing to note: whenever the password is sent to the server to, say, login, the server knows about the password. While this somewhat dampens security, it also makes upgrading hashes possible. ;-) See AuthTools for more details.
Not neccesarily. Depending on how you implement it, the server may only receive the hashed version. As you mention, you can in fact make a tradeoff in security and send it plaintext, but then you are solving one problem (encrypted storage) while ignoring another (plaintext in transit).
AGISB wrote:Thats an American problem and not a security one. The people only care for an easy buck and the US law system provides them with the means. Besides that people don't sue sites that didn't save their password encrypted and the same password was used to get into other sites where the danger was done.
You both misunderstood my meaning, and made a wrong conclusion.

I provided the link to disprove your earlier statement that "Users don't care". They do care, and they care enough to take their business elsewhere AND file lawsuits to show just how important it is. Thats not an American problem - thats a business problem. If you choose to ignore security issues, it will impact your bottom line. Thats why several security professionals in this thread are telling you that in the real world, choosing not to hash a password isn't a choice at all, let alone a valid tradeoff.

Further, people do sue for damage done beyond the site compromise itself. Identity Theft is a huge and growing field in the legal community, and compensatory damages have been awarded in numerous cases where litigants have been able to prove negligence. Not encrypting passwords, despite having an easy ability to do so, is absolutely negligent handling of data.
AGISB wrote:It takes less then a couple of hours to create a new rainbow table using a dictionary list or some other lists including a known salt. This is in now way comparable to brute force.
You do not understand the meaning of Brute Force, and Rainbow tables.

Rainbow tables are simply stored output from a brute force attempt. Limiting that brute force attempt to dictionary attacks, is still a brute force attempt - it just has a smaller range of targets.

Further, building a rainbow table, with a salt, for all possible *dictionary* words (we're going to assume no users have anything but that) takes far longer than a lookup. It can take days for even 8 characters.

Now factor in that *each* user could have a unique salt, and its suddenly days for *each* user. That *is* a substantial improvement, and yes, it is comparable to a brute force attempt.
AGISB wrote:The other thing is that you always must have a backup for transmitting unhashed passwords or you need to require all users to have jaascript on which will smurf many off.
I guess you don't use the web much. :)

Plenty of sites do not do so, and keep large numbers of users - even ones with javascript off. PHPBB, for example, simply assigns you a new temporary password, which it mails to you, and then allows you to reset it to one of your choosing. Ebay has a similar system, as does Yahoo. Gmail sends to your cellphone (how cool!?), and I've seen plenty of other systems.

The point is, there is no reason today to store unhashed passwords. You don't have to require users to have javascript on, you don't have to ruin your "recover password" system, you don't even have to give up testing the strength of their passwords. There is a way to solve every issue you've brought up, they've been used on dozens of (very) successful sites, and the sky isn't falling for them - but it is falling for companies taking stupid risks with people's data. Please stop arguing that doing so is okay.
AGISB wrote:Again I am not stating you should not use hashing. My point is that the security enhancement is not as great as many peole think.
Actually, on multiple posts, you've argued that you don't need to, that there is little benefit in doing so, and that not doing so is reasonable. None of those are accurate, and they are all supporting arguments that lead people to not use MD5. Thats irresponsible, and its wrong. We've clearly answered every issue you've brought up that you claim justifies not hashing passwords.
AGISB wrote:But sometimes you have to go with a little less security, even more so if the customer requested functions that need it that way. That customer doesn't care about if it is less secure he just wants what he wants.
1. There are no functions that "need it that way". Every issue you've brought up can be addressed while still hashing passwords.
2. The customer does in fact care deeply about not being sued, not losing customers, and not looking incompetent because they ignored a critical need for protecting customer information.

There really isn't anything left to discuss. Hashing passwords is in every way a substantial improvement in security, and depending on your specific needs, can be implemented without any impact on functionality. Its best practice, it has low overhead, and there is no valid justification for not doing so.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Since the passwords were hashed, they could not be accessed. He managed to get in by using the remember me tokens stored unhashed in the database by simply placing them in his own, this was fixed by resetting all the remember me tokens.
At a minimum always require authentication before the user can effect change. Hashing in your scenario would work as a primary check - a remember me code is a password substitute so it should have equivalent attention in securing.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Well, it's not my software (MediaWiki, the software that runs Wikipedia and friends).
Post Reply