Search code examples
phpqtcryptographycrypt

PHP crypt() to Qt


I'm currently learning Qt5 for GUI applications. My first project is to make an authentication script and connect it to our database.

The problem is the password column is populated by PHP's crypt(), which generates a hash string that starts with $1$.

(e.g. echo crypt("password"); prints $1$d41.iA3.$XfuFXpCJfxSduzidGnKBR0

How do I use Qt to compare the password inputted in my GUI application to the password column in database that's generated by crypt()?


Solution

  • TL;DR: Use a key derivation function for password storing.


    PHP's crypt() is horrible to use from other programming languages, because

    • it uses a strange way to mix salt and password, instead standard HMAC
    • it has it's own base64 alphabet that needs to be re-implemented

    Apart from that you're using plain md5 as a password hash algorithm. Never use md5 for password hashing. Read more. And more. And more.


    But let's get our handy dirty then.

    1. The structure of crypt()s output is the following: enter image description here where algorithm $1$ with no algorithm options means MD5.
    2. The hashed password is base64 encoded using a custom alphabet. This can be archived by replacing the output of QByteArray::toBase64() with PHPs alphabet ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ ./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz or re-implementing base64 if performance matters.
    3. As seen in the md5_crypt.c implementation, max. 8 chars of the salt are used (d41.iA3. is your case).
    4. Then input for md5 is constructed as foo = password || $1$ || salt where || is the string concatenation. Use QByteArray as type for foo.
    5. Calculae md5(password || salt || password) and call it bar.
    6. Set bar = '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
    7. Cut length(password) bytes from bar (in binary representation) and append it to foo. Repeat bar as often as needed if length(password) > 16.
    8. Uff, let me quote the original source Then something really weird...

      for (j = 0, i = length(password); i; i >>= 1)
          if (i & 1)
              foo += bar[j]
          else
              foo += password[j]
      

      which I hope i ready properly from the source.

    9. Run md5 on QByteArray: bar = md5(foo).
    10. Do that

      for (i = 0; i < 1000; i++) {
          moo = ""
          if (i & 1) {
              moo += password
          }
          else {
              moo += bar
          }
          if (i % 3) {
              moo += salt
          }
          if (i % 7) {
              moo += password
          }
          if (i & 1) {
              moo += bar
          }
          else {
              moo += password
          }
          bar = md5(moo)
      }
      
    11. Glue everything together: $1$ || salt || $ || base64(bar).