12

I have a Django application which resets unix user passwords running in an Ubuntu machine, but my development environment is OS X and I've come across this annoying situation:

OS X:

>>> import crypt
>>> crypt.crypt('test','$1$VFvON1xK$')
'$1SoNol0Ye6Xk'

Linux:

>>> import crypt
>>> crypt.crypt('test','$1$VFvON1xK$')
'$1$VFvON1xK$SboCDZGBieKF1ns2GBfY50'

From reading the pydoc for crypt, I saw it uses an OS-specific crypt implementation, so I also tested the following code in both systems with the same results as Python:

#include <unistd.h>

int main() {
        char *des = crypt("test","$1$VFvON1xK$ls4Zz4XTEuVI.1PnYm28.1");
        puts(des);
}

How can I have OS X's crypt() implementation generate the same results as Linux crypt()?
And why isn't that covered by the Python implementation (as I would expect from such cases for cross-platform deployment)?

Chris Krycho
  • 3,125
  • 1
  • 23
  • 35
Filipe Pina
  • 2,201
  • 23
  • 35

4 Answers4

15

This is because Linux's glibc handles passwords differently - the salt of the password on Linux corresponds to the type of hash that it generates. OSX crypt() is plain-old DES encryption, (which is horrible).

glibc supports a variety of hash algorithms (MD5, Blowfish, SHA-256, etc).

If we take a look at the crypt.3 manpage, we can see:

   If salt is a character string starting with the characters "$id$" followed by
   a string terminated by "$":

          $id$salt$encrypted

   then instead of using the DES machine, id identifies the encryption method
   used and this then determines how the rest of the password string is
   interpreted.  The following values of id are supported:

          ID  | Method
          ---------------------------------------------------------
          1   | MD5
          2a  | Blowfish (not in mainline glibc; added in some
              | Linux distributions)
          5   | SHA-256 (since glibc 2.7)
          6   | SHA-512 (since glibc 2.7)

So, given that information.. lets take your password from the second example using Linux's crypt

$1$VFvON1xK$SboCDZGBieKF1ns2GBfY50' ('test', encrypted with salt=VFvON1xK)


1                       == MD5
VFvON1xK                == Salt
SboCDZGBieKF1ns2GBfY50  == Hashed password

Luckily for you, there is a cross-platform solution for this, passlib.hash.md5_crypt.

Here's how you'd use it:

from passlib.hash import md5_crypt
hash = md5_crypt.encrypt("test",salt="VFvON1xK")
print hash

When run on Linux or OSX, produces the glibc friendly password hash of:

$1$VFvON1xK$SboCDZGBieKF1ns2GBfY50

Identical to the original produced on the Linux machine.

synthesizerpatel
  • 27,321
  • 5
  • 74
  • 91
  • I was hoping there would be some extra tweaking available for core _crypt_ but passlib.hash.md5_crypt works great as well! thanks – Filipe Pina Oct 24 '12 at 16:21
5

You're passing specialized salt strings to the function which invoke glibc-specific crypt behaviors that aren't available on Mac OS X. From the crypt(3) man page on Debian 6:

If salt is a character string starting with the characters "$id$" followed by a string terminated by "$"...then instead of using the DES machine, id identifies the encryption method used and this then determines how the rest of the password string is interpreted.

In your python examples, you're telling crypt to use an id of 1, which causes MD5 to be used instead of DES-based hashing. There's no such extension on Mac OS X, where crypt is strictly DES-based. (Mac OS X's crypt has its own extension--the salt can be a 9-character array, beginning with an underscore, followed by 4 bytes of iteration count and 4 bytes of salt--that has no analog in glibc's implementation.)

If you avoid the crypt extensions on both platforms and use traditional crypt, in which salt can only be two bytes, you'll get the same results from the function on both platforms, e.g.:

>>> crypt.crypt( "test", "S/" )
'S/AOO.b04HTR6'

That's obviously terrible from a security perspective. Consider using something like passlib or py-bcrypt instead. Either one will get you vastly better hashing and cross-platform reliability at the same time.

more tension
  • 3,312
  • 17
  • 19
2

why would you want to have a single crypt function in Python ? if you're running in OSX, you'd want the osx version crypt() and if you're running in ubuntu, it'll use ubuntu's crypt().

This IS a cross platform solution - Python is using the OS crypt to ensure compatibility within the environment. If Python used it's own crypt(), then the hashes would be the same -- but it would work on OSX and not Ubuntu ( or vice versa )

You could write something, or find a module, that re-implements the hashing algorithm that crypt uses in each environment -- but again, that would defeat the purpose of being cross platform. You would be hardcoding your app to work on Ubunutu , which might use different crypts not just from OSX, but from other Unix and BSD flavors like RedHat, FreeBSD, etc.

Jonathan Vanasco
  • 15,111
  • 10
  • 48
  • 72
  • 1
    I think the problem is more awkward in that you can't point a mac development system at a shared development database and have it work on the linux frontends that use the same database. – Anya Shenanigans Oct 24 '12 at 15:36
  • indeed, you make a point about _crypt_ performing as it should for that OS, thank you. But I was hoping for an answer that would mention a standard python lib to resolve my issue :) – Filipe Pina Oct 24 '12 at 16:13
  • This answer does not really answer the question :( For example, I am on OS X, I am learning python and trying to use crypt(). I expect it to generate the same results as Linux version does because otherwise it does not make sense to compare hashes generated on different platforms using the same method. Hashes must not change across platform if the cryptographic hash method is the same. – User366 Jul 28 '16 at 19:23
  • 1
    Like the original poster, you don't understand the answer or the technology involved. Python's `crypt` is not a cryptographic hash method. It is just a call to the Operating System's own `crypt` function. Every OS implements `crypt` differently, because it is just a standard interface to the underlying system's password management. If you want to consistently hash strings across different platforms, you need to use the same cryptographic hash method on each platform - that means you do not want to use `crypt`, and instead want to use the hashlib library or something similar. – Jonathan Vanasco Jul 28 '16 at 21:31
1

As an update to @synthesizerpatel's answer, the .encrypt() method is now deprecated in passlib and replaced by the .hash() method:

from passlib.hash import md5_crypt
hash = md5_crypt.hash("test", salt="VFvON1xK")
print(hash)