python crypt in OSX

2020-06-03 02:24发布

问题:

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)?

回答1:

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.



回答2:

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.



回答3:

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.