1

I have a task for my new PHP job, but I don't understand if it is possible to solve. Task:

You need to create PHP web page that implements functional on picture below. Data must be stored in SQL. Hacker can not see user phone numbers and emails even if he got access to database or files. The solutions must contain standard PHP libs.

So, what algorithm should I use to solve problem? I can store emails with password_hash() function and when I need to retrieve phone number from email, I will use password_verify() to find out if email exists. But how to be with phone number, if I need to send it pure and unhashed?

enter image description here

Braiam
  • 1
  • 11
  • 47
  • 78
sirjay
  • 1,767
  • 3
  • 32
  • 52
  • 4
    hashing is one-way, if you need to reverse it then look at encryption. example: [`mcrypt`](http://php.net/manual/en/function.mcrypt-encrypt.php) – mister martin Nov 28 '16 at 15:06
  • will this solution satisfy task if hacker gets database? – sirjay Nov 28 '16 at 15:11
  • 3
    @mistermartin Please do not recommend mcrypt for new development. mcrypt is unmaintained since 2007, contains several security-critical bugs, and [is scheduled for deprecation in PHP 7.1](https://wiki.php.net/rfc/mcrypt-viking-funeral). –  Nov 29 '16 at 20:47
  • @duskwuff that's good to know. what do you recommend? – mister martin Nov 29 '16 at 20:49
  • Ideally, use a high-level library provided by your framework for cryptographic tasks. If that isn't possible for some reason, use the OpenSSL extension to access crypto primitives. –  Nov 29 '16 at 20:52
  • Thanks @duskwuff, I've updated my answer with the info. I wish these sorts of notes were more readily available in the manual itself. The vote was concluded in late March and PHP 7.1 final is right behind the corner. Also the OpenSSL may not be the first obvious thing people look for when thinking of general-purpose encryption, rather than working with certs. And it's not a built-in. Wondering if there'll be something new without dependencies with the upcoming removal of Mcrypt. – Markus AO Nov 30 '16 at 16:27

1 Answers1

3

PHP provides built-in solutions for encrypting and decrypting data. Use e.g. MCrypt, supports a good buffet of common algorithms. What exact cipher you choose is up to you, depending on the level of security required. Read up at Wikipedia on block ciphers for the general picture. Edit: Just learned that MCrypt is deprecated in PHP 7.1 and the use of OpenSSL is recommended in its place.

If the phone numbers must be inaccessible even if a hacker gains complete access to your system, then the decrypt key must obviously be off-server, ie. you can't store it in DB or the filesystem.

In the spec you have, this key would then be the e-mail address that's matched with the phone number. Then, at input time you'd one-way hash the e-mail, and use the unhashed e-mail as a key for encrypting the phone number. At request time, you'd find the matching phone number by using the e-mail hash, and decrypt it using the unhashed email as the key. Should get you sorted.


Update: When fetching a record that matches the phone/email pair in the database, if you've used password_hash() (which generates a new salt and a unique string each time), then your only option is to fetch all records and iterate them through password_verify(). That's not particularly scalable.

Unless this is an exercise in security, I'm not sure I'd bother with more than a simple sha1() hash for the e-mails. Or use e.g. crypt($email, '$2y$08$Some22CharsOfFixedSalt$'); -- see crypt() -- and generate a blowfish-based hash that uses a fixed salt string, resulting in an invariant hash. I'd also then truncate the leading salt-part of the resulting string from the database entry.

If you're feeling crafty, why not cook up an algorithm that derives a unique string from each email, and then use that for salt in your hashing function, instead of using the same salt for hashing all e-mails.

You could also delegate the e-mail hashing for the database and use MySQL's encryption functions. Then you'd use e.g. SHA2('email', 256) in your INSERT and SELECT queries, like so: INSERT INTO records VALUES (SHA2('email@what', 256), 'TheEncryptedTelNo'); and SELECT * FROM records WHERE email = SHA2('email@what', 256);. (Be sure to note the manual's caution on plaintext data possibly getting stored in logs; ie. know your MySQL setup before doing this.)

Markus AO
  • 4,771
  • 2
  • 18
  • 29
  • thank you. what is the best way to find out if email exists in database when user wants to send him phone number? Emails are hashed in db. So, I get all hashed emails and `for(...) { ... }` cycle compare by `password_verify()`? Or there is faster way? – sirjay Nov 29 '16 at 12:42
  • The e-mail is saved as a hash in the database, yes? Simply hash again the e-mail the user inputs in the request, then do a SQL query for an entry with a matching e-mail hash. I don't really see a need for the whole `password_hash` | `password_verify` hop here. I'd simply toss in a `sha1()` hash for the e-mail with some extra salt thrown in, and store that in the database for a reference. Of course if this is actually an exercise in maximizing security, then by all means go the long way, but I don't see how your case calls for it, unless you're storing CIA agent phone numbers or something. – Markus AO Nov 29 '16 at 18:16
  • In a common use-case, the user would input their username (plain) and their password, then a password hash matching the username would be fetched from the database and verified. In this case, the e-mail acts as the only point of reference. Since `password_hash()` returns a unique string every time, if you want to use it, your only option is to compare through the whole database for each request. Which really doesn't scale very gracefully. For a different approach, you could look into MySQL's hashing and encryption functions: https://dev.mysql.com/doc/refman/5.5/en/encryption-functions.html – Markus AO Nov 29 '16 at 18:39