{"id":729,"date":"2006-08-25T16:03:03","date_gmt":"2006-08-25T06:03:03","guid":{"rendered":"http:\/\/www.flamingspork.com\/blog\/2006\/08\/25\/storing-passwords-securly-in-mysql\/"},"modified":"2006-08-25T16:03:03","modified_gmt":"2006-08-25T06:03:03","slug":"storing-passwords-securly-in-mysql","status":"publish","type":"post","link":"https:\/\/www.flamingspork.com\/blog\/2006\/08\/25\/storing-passwords-securly-in-mysql\/","title":{"rendered":"Storing Passwords (securly) in MySQL"},"content":{"rendered":"<p>Frank talks about <a href=\"http:\/\/mysqldatabaseadministration.blogspot.com\/2006\/08\/storing-passwords-in-mysql.html\">Storing Passwords in MySQL<\/a>. He does, however, miss something that&#8217;s <strong>really, really important<\/strong>. I&#8217;m talking about the salting of passwords.<\/p>\n<p>If I want to find out what\u00c2\u00a0 5d41402abc4b2a76b9719d911017c592 or 015f28b9df1bdd36427dd976fb73b29d MD5s mean, the first thing I&#8217;m going to try is a dictionary attack (especially if i&#8217;ve seen a table with only user and password columns). Guess what? A list of words and their MD5SUMS can be used to very quickly find what these hashes represent.<\/p>\n<p>I&#8217;ll probably have this dictionary in a MySQL database with an index as well. Try it yourself &#8211; you&#8217;ll probably find a dictionary with the words &#8220;hello&#8221; and &#8220;fire&#8221; in it to help. In fact, do this:<\/p>\n<p>mysql> create table words (word varchar(100));<br \/>\nQuery OK, 0 rows affected (0.13 sec)<br \/>\nmysql> load data local infile &#8216;\/usr\/share\/dict\/words&#8217; into table words;<br \/>\nQuery OK, 98326 rows affected (0.85 sec)<br \/>\nRecords: 98326\u00c2\u00a0 Deleted: 0\u00c2\u00a0 Skipped: 0\u00c2\u00a0 Warnings: 0<\/p>\n<p>mysql> alter table words add column md5hash char(32);<br \/>\nQuery OK, 98326 rows affected (0.39 sec)<br \/>\nRecords: 98326\u00c2\u00a0 Duplicates: 0\u00c2\u00a0 Warnings: 0<\/p>\n<p>mysql> update words set md5hash=md5(word);<br \/>\nQuery OK, 98326 rows affected (3.19 sec)<br \/>\nRows matched: 98326\u00c2\u00a0 Changed: 98326\u00c2\u00a0 Warnings: 0<br \/>\nmysql> alter table words add index md5_idx (md5hash);<br \/>\nQuery OK, 98326 rows affected (2.86 sec)<br \/>\nRecords: 98326\u00c2\u00a0 Duplicates: 0\u00c2\u00a0 Warnings: 0<br \/>\nmysql> select * from words where md5hash=&#8217;5d41402abc4b2a76b9719d911017c592&#8242;;<br \/>\n+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| word\u00c2\u00a0 | md5hash\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 |<br \/>\n+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| hello | 5d41402abc4b2a76b9719d911017c592 |<br \/>\n+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n1 row in set (0.11 sec)<br \/>\nmysql> select * from words where md5hash=&#8217;015f28b9df1bdd36427dd976fb73b29d&#8217;;<br \/>\n+&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| word | md5hash\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 |<br \/>\n+&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| fire | 015f28b9df1bdd36427dd976fb73b29d |<br \/>\n+&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n1 row in set (0.00 sec)<br \/>\n$EXCLAMATION I hear you go.<\/p>\n<p>Yes, this is not a good way to &#8220;secure&#8221; passwords. Oddly enough, people have known about this for a long time and there&#8217;s a real easy\u00c2\u00a0 solution. It&#8217;s called salting.<\/p>\n<p>Salting is prepending a random string to the start of the password when you store it (and when you check it).<\/p>\n<p>So, let&#8217;s look at how our new password table may look:<\/p>\n<p>mysql> select * from passwords;<br \/>\n+&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| user | salt\u00c2\u00a0\u00c2\u00a0 | md5pass\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 |<br \/>\n+&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| u1\u00c2\u00a0\u00c2\u00a0 | ntuk24 | ce6ac665c753714cb3df2aa525943a12 |<br \/>\n| u2\u00c2\u00a0\u00c2\u00a0 | drc,3\u00c2\u00a0 | 7f573abbb9e086ccc4a85d8b66731ac8 |<br \/>\n+&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n2 rows in set (0.00 sec)<br \/>\nAs you can see, the MD5s are different than before. If we search these up in our dictionary, we won&#8217;t find a match.<\/p>\n<p>mysql> select * from words where md5hash=&#8217;ce6ac665c753714cb3df2aa525943a12&#8242;;<br \/>\nEmpty set (0.01 sec)<\/p>\n<p>instead, we&#8217;d have to get the salt and do an md5 of the salt and the dictionary word and see if the md5 matches. Guess what, no index for that! and with all the possible values for salt, we&#8217;ve substantially increased the problem space to construct a dictionary (i won&#8217;t go into the maths here).<\/p>\n<p>mysql> create view v as select word, md5(CONCAT(&#8216;ntuk24&#8242;,word)) as salted from words;<br \/>\nQuery OK, 0 rows affected (0.05 sec)<\/p>\n<p>mysql> select * from v where salted=&#8217;ce6ac665c753714cb3df2aa525943a12&#8217;;<br \/>\n+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| word\u00c2\u00a0 | salted\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 |<br \/>\n+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| hello | ce6ac665c753714cb3df2aa525943a12 |<br \/>\n+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n1 row in set (2.04 sec)<\/p>\n<p>mysql> create or replace view v as select word, md5(CONCAT(&#8216;drc,3&#8242;,word)) as salted from words;<br \/>\nQuery OK, 0 rows affected (0.00 sec)<\/p>\n<p>mysql> select * from v where salted=&#8217;7f573abbb9e086ccc4a85d8b66731ac8&#8217;; +&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| word | salted\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 |<br \/>\n+&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n| fire | 7f573abbb9e086ccc4a85d8b66731ac8 |<br \/>\n+&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br \/>\n1 row in set (2.12 sec)<\/p>\n<p>So we&#8217;ve gone from essentially instantaneous retreival, to now taking about 2 seconds. Even if I assume that one of your users is going to be stupid enough to have a dictionary password, It&#8217;s going to take me 2 seconds to check <span style=\"font-weight: bold\">each user<\/span> &#8211; as the salt is different for each user! So it could take me hours just to find that user. Think about how many users are in your user table &#8211; with 1000 users, it&#8217;s over 1\/2hr. For larger systems, it&#8217;s going to be hours.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Frank talks about Storing Passwords in MySQL. He does, however, miss something that&#8217;s really, really important. I&#8217;m talking about the salting of passwords. If I want to find out what\u00c2\u00a0 5d41402abc4b2a76b9719d911017c592 or 015f28b9df1bdd36427dd976fb73b29d MD5s mean, the first thing I&#8217;m going &hellip; <a href=\"https:\/\/www.flamingspork.com\/blog\/2006\/08\/25\/storing-passwords-securly-in-mysql\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1,13,14],"tags":[],"class_list":["post-729","post","type-post","status-publish","format-standard","hentry","category-general","category-memberdb","category-mysql"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p5a6n8-bL","jetpack-related-posts":[{"id":847,"url":"https:\/\/www.flamingspork.com\/blog\/2007\/06\/19\/my-top-5-wishlist-for-mysql\/","url_meta":{"origin":729,"position":0},"title":"My Top 5 Wishlist for MySQL","author":"Stewart Smith","date":"2007-06-19","format":false,"excerpt":"I'm going and stealing Jay's idea (who stole it off Brian Duff... but his was for Oracle so obviously doesn't count :) So, my five wishes for MySQL Are: 5. Six-monthly release cycles Getting a release out there takes way too long. There's a variety of reasons, but seeing the\u2026","rel":"","context":"In &quot;mysql&quot;","block_context":{"text":"mysql","link":"https:\/\/www.flamingspork.com\/blog\/category\/work-et-al\/mysql\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1655,"url":"https:\/\/www.flamingspork.com\/blog\/2009\/06\/09\/drizzle-pluggable-metadatastore-or-no-table-definition-file-on-disk\/","url_meta":{"origin":729,"position":1},"title":"Drizzle pluggable MetadataStore (or: no table definition file on disk)","author":"Stewart Smith","date":"2009-06-09","format":false,"excerpt":"My code is shaping up rather nicely (see https:\/\/code.launchpad.net\/~stewart\/drizzle\/discovery) and I'm planning to submit a merge-request for it later today. I'm about to commit code that implements a MetadataStore for the ARCHIVE engine. This means that for ARCHIVE tables, you only have the .ARZ file on disk. The table definition\u2026","rel":"","context":"In &quot;General&quot;","block_context":{"text":"General","link":"https:\/\/www.flamingspork.com\/blog\/category\/general\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1251,"url":"https:\/\/www.flamingspork.com\/blog\/2008\/11\/06\/goodbye-frm-or-at-least-the-steps-to-it\/","url_meta":{"origin":729,"position":2},"title":"Goodbye FRM (or at least the steps to it)","author":"Stewart Smith","date":"2008-11-06","format":false,"excerpt":"Since before MySQL was MySQL, there has been the .FRM file. Of course, what it really wanted to be was \".form\" -\u00c2\u00a0 a file that stored how to display a form on your (green) CRT. Jan blogged earlier in the year on this still being there, even in MySQL 5.1\u2026","rel":"","context":"In &quot;drizzle&quot;","block_context":{"text":"drizzle","link":"https:\/\/www.flamingspork.com\/blog\/category\/work-et-al\/drizzle-work-et-al\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1745,"url":"https:\/\/www.flamingspork.com\/blog\/2009\/12\/09\/drizzle-frm-replacement-the-table-proto\/","url_meta":{"origin":729,"position":3},"title":"Drizzle FRM replacement: the table proto","author":"Stewart Smith","date":"2009-12-09","format":false,"excerpt":"Drizzle originally inherited the FRM file from MySQL (which inherited it from UNIREG). The FRM file stores metadata about a table; what columns it has, what type those columns are, what indexes, any default values, comments etc are all stored in the FRM. In the days of MyISAM, this worked\u2026","rel":"","context":"In &quot;drizzle&quot;","block_context":{"text":"drizzle","link":"https:\/\/www.flamingspork.com\/blog\/category\/work-et-al\/drizzle-work-et-al\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":446,"url":"https:\/\/www.flamingspork.com\/blog\/2005\/08\/03\/fancy-shortcuts-to-mysql-bugs\/","url_meta":{"origin":729,"position":4},"title":"Fancy shortcuts to MySQL Bugs","author":"Stewart Smith","date":"2005-08-03","format":false,"excerpt":"So Elliot Murphy is talking about QuickSearch shortcut for bugs.mysql.com which is quite useful if you use Firefox. However, I'm using Epiphany (which is based on the same rendering engine, but is a bit more GNOMEy - and besides, i've been using it for a while, i have all my\u2026","rel":"","context":"In &quot;mysql&quot;","block_context":{"text":"mysql","link":"https:\/\/www.flamingspork.com\/blog\/category\/work-et-al\/mysql\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":3801,"url":"https:\/\/www.flamingspork.com\/blog\/2014\/09\/19\/mysql-architecture\/","url_meta":{"origin":729,"position":5},"title":"Some current MySQL Architecture writings","author":"Stewart Smith","date":"2014-09-19","format":false,"excerpt":"So, I've been looking around for a while (and a few times now) for any good resources that cover a bunch of MySQL architecture and technical details aimed towards the technically proficient but not MySQL literate audience. I haven't really found anything. I mean, there's the (huge and very detailed)\u2026","rel":"","context":"In &quot;mysql&quot;","block_context":{"text":"mysql","link":"https:\/\/www.flamingspork.com\/blog\/category\/work-et-al\/mysql\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.flamingspork.com\/blog\/wp-json\/wp\/v2\/posts\/729","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.flamingspork.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.flamingspork.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.flamingspork.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.flamingspork.com\/blog\/wp-json\/wp\/v2\/comments?post=729"}],"version-history":[{"count":0,"href":"https:\/\/www.flamingspork.com\/blog\/wp-json\/wp\/v2\/posts\/729\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.flamingspork.com\/blog\/wp-json\/wp\/v2\/media?parent=729"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.flamingspork.com\/blog\/wp-json\/wp\/v2\/categories?post=729"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.flamingspork.com\/blog\/wp-json\/wp\/v2\/tags?post=729"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}