pike.git / lib / modules / Crypto.pmod / Password.pmod

version» Context lines:

pike.git/lib/modules/Crypto.pmod/Password.pmod:1: + //! Password handling. + //! + //! This module handles generation and verification of + //! password hashes. + //! + //! @seealso + //! @[verify()], @[hash()], @[crypt()]    -  + //! Verify a password against a hash. + //! + //! This function attempts to support most common + //! password hashing schemes. The @[hash] can be on any + //! of the following formats. + //! + //! LDAP-style (RFC2307) hashes: + //! @dl + //! @item "{SHA}XXXXXXXXXXXXXXXXXXXXXXXXXXXX" + //! The @expr{XXX@} string is taken to be a @[MIME.encode_base64] @[SHA1] + //! hash of the password. Source: OpenLDAP FAQ + //! @url{http://www.openldap.org/faq/data/cache/347.html@}. + //! + //! @item "{SSHA}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + //! The @expr{XXX@} string is taken to be a @[MIME.encode_base64] + //! string in which the first 20 chars are an @[SHA1] hash and the + //! remaining chars the salt. The input for the hash is the password + //! concatenated with the salt. Source: OpenLDAP FAQ + //! @url{http://www.openldap.org/faq/data/cache/347.html@}. + //! + //! @item "{MD5}XXXXXXXXXXXXXXXXXXXXXXXX" + //! The @expr{XXX@} string is taken to be a @[MIME.encode_base64] @[MD5] + //! hash of the password. Source: OpenLDAP FAQ + //! @url{http://www.openldap.org/faq/data/cache/418.html@}. + //! + //! @item "{SMD5}XXXXXXXXXXXXXXXXXXXXXXXXXXXX" + //! The @expr{XXX@} string is taken to be a @[MIME.encode_base64] + //! string in which the first 16 chars are an @[MD5] hash and the + //! remaining chars the salt. The input for the hash is the password + //! concatenated with the salt. Source: OpenLDAP FAQ + //! @url{http://www.openldap.org/faq/data/cache/418.html@}. + //! + //! @item "{CRYPT}XXXXXXXXXXXXX" + //! The @expr{XX@} string is taken to be a crypt(3C)-style hash. + //! This is the same thing as passing the @expr{XXX@} string without + //! any preceding method name within @expr{{...}@}. I.e. it's + //! interpreted according to the crypt-style hashes below. + //! @enddl + //! + //! Crypt-style hashes: + //! @dl + //! @item "$6$SSSSSSSSSSSSSSSS$XXXXXXXXXXXXXXXXXXXXXX" + //! The string is interpreted according to the + //! "Unix crypt using SHA-256 and SHA-512" standard + //! Version 0.4 2008-4-3, where @expr{SSSSSSSSSSSSSSSS@} + //! is up to 16 characters of salt, and the string @expr{XXX@} + //! the result of @[SHA512()->hash_password()] with @expr{5000@} + //! rounds. Source: Unix crypt using SHA-256 and SHA-512 + //! @url{http://www.akkadia.org/drepper/SHA-crypt.txt@} + //! + //! @item "$6$rounds=RR$SSSSSSSSSSSSSSSS$XXXXXXXXXXXXXXXXXXXXXX" + //! This is the same algorithm as the one above, but with the + //! number of rounds specified by @expr{RR@} in decimal. Note + //! that the number of rounds is clamped to be within + //! @expr{1000@} and @expr{999999999@} (inclusive). + //! Source: Unix crypt using SHA-256 and SHA-512 + //! @url{http://www.akkadia.org/drepper/SHA-crypt.txt@} + //! + //! @item "$5$SSSSSSSSSSSSSSSS$XXXXXXXXXXXXXXXXXXXXXX" + //! The string is interpreted according to the + //! "Unix crypt using SHA-256 and SHA-512" standard + //! Version 0.4 2008-4-3, where @expr{SSSSSSSSSSSSSSSS@} + //! is up to 16 characters of salt, and the string @expr{XXX@} + //! the result of @[SHA256()->hash_password()] with @expr{5000@} + //! rounds. Source: Unix crypt using SHA-256 and SHA-512 + //! @url{http://www.akkadia.org/drepper/SHA-crypt.txt@} + //! + //! @item "$5$rounds=RR$SSSSSSSSSSSSSSSS$XXXXXXXXXXXXXXXXXXXXXX" + //! This is the same algorithm as the one above, but with the + //! number of rounds specified by @expr{RR@} in decimal. Note + //! that the number of rounds is clamped to be within + //! @expr{1000@} and @expr{999999999@} (inclusive). + //! Source: Unix crypt using SHA-256 and SHA-512 + //! @url{http://www.akkadia.org/drepper/SHA-crypt.txt@} + //! + //! @item "$1$SSSSSSSS$XXXXXXXXXXXXXXXXXXXXXX" + //! The string is interpreted according to the GNU libc2 extension + //! of @expr{crypt(3C)@} where @expr{SSSSSSSS@} is up to 8 chars of + //! salt and the @expr{XXX@} string is an @[MD5]-based hash created + //! from the password and the salt. Source: GNU libc + //! @url{http://www.gnu.org/software/libtool/manual/libc/crypt.html@}. + //! + //! @item "XXXXXXXXXXXXX" + //! The @expr{XXX@} string (which doesn't begin with @expr{"{"@}) is + //! taken to be a password hashed using the classic unix + //! @expr{crypt(3C)@} function. If the string contains only chars + //! from the set @expr{[a-zA-Z0-9./]@} it uses DES and the first two + //! characters as salt, but other alternatives might be possible + //! depending on the @expr{crypt(3C)@} implementation in the + //! operating system. + //! + //! @item "" + //! The empty password hash matches all passwords. + //! @enddl + //! + //! @returns + //! Returns @expr{1@} on success, and @expr{0@} (zero) otherwise. + //! + //! @note + //! This function was added in Pike 7.9. + //! + //! @seealso + //! @[hash()], @[predef::crypt()] + int verify(string password, string hash) + { +  if (hash == "") return 1; +  +  // Detect the password hashing scheme. +  // First check for an LDAP-style marker. +  string scheme = "crypt"; +  sscanf(hash, "{%s}%s", scheme, hash); +  // NB: RFC2307 proscribes lower case schemes, while +  // in practise they are usually in upper case. +  switch(lower_case(scheme)) { +  case "md5": // RFC 2307 +  case "smd5": +  hash = MIME.decode_base64(hash); +  password += hash[16..]; +  hash = hash[..15]; +  return Crypto.MD5.hash(password) == hash; +  +  case "sha": // RFC 2307 +  case "ssha": +  // SHA1 and Salted SHA1. +  hash = MIME.decode_base64(hash); +  password += hash[20..]; +  hash = hash[..19]; +  return Crypto.SHA1.hash(password) == hash; +  +  case "crypt": // RFC 2307 +  // First try the operating systems crypt(3C), +  // since it might support more schemes than we do. +  if ((hash == "") || crypt(password, hash)) return 1; +  if (hash[0] != '$') { +  if (hash[0] == '_') { +  // FIXME: BSDI-style crypt(3C). +  } +  return 0; +  } +  +  // Then try our implementations. +  sscanf(hash, "$%s$%s$%s", scheme, string salt, string hash); +  int rounds = UNDEFINED; +  if (has_prefix(salt, "rounds=")) { +  sscanf(salt, "rounds=%d", rounds); +  sscanf(hash, "%s$%s", salt, hash); +  } +  switch(scheme) { +  case "1": // crypt_md5 +  return Nettle.crypt_md5(password, salt) == hash; +  +  case "2": // Blowfish (obsolete) +  case "2a": // Blowfish (possibly weak) +  case "2x": // Blowfish (weak) +  case "2y": // Blowfish (stronger) +  break; +  +  case "3": // MD4 NT LANMANAGER (FreeBSD) +  break; +  + #if constant(Nettle.SHA256_Info) +  // cf http://www.akkadia.org/drepper/SHA-crypt.txt +  case "5": // SHA-256 +  return Crypto.SHA256.crypt_hash(password, salt, rounds) == hash; + #endif + #if constant(Nettle.SHA512_Info) +  case "6": // SHA-512 +  return Crypto.SHA512.crypt_hash(password, salt, rounds) == hash; + #endif +  } +  break; +  } +  return 0; + } +  + //! Generate a hash of @[password] suitable for @[verify_password()]. + //! + //! @param password + //! Password to hash. + //! + //! @param scheme + //! Password hashing scheme. If not specified the strongest available + //! will be used. + //! + //! If an unsupported scheme is specified an error will be thrown. + //! + //! Supported schemes are: + //! @dl + //! @item "6" + //! @item "$6$" + //! @[SHA512.crypt_hash()] with 96 bits of salt and a default + //! of @expr{5000@} rounds. + //! + //! @item "5" + //! @item "$5$" + //! @[SHA256.crypt_hash()] with 96 bits of salt and a default + //! of @expr{5000@} rounds. + //! + //! @item "1" + //! @item "$1$" + //! @[MD5.crypt_hash()] with 48 bits of salt and @expr{1000@} rounds. + //! + //! @item "" + //! @[predef::crypt()] with 12 bits of salt. + //! + //! @enddl + //! + //! @param rounds + //! The number of rounds to use in parameterized schemes. If not + //! specified the scheme specific default will be used. + //! + //! @returns + //! Returns a string suitable for @[verify_password()]. + //! + //! @note + //! Note that the availability of @[SHA512] depends on the version + //! of @[Nettle] that Pike has been compiled with. + //! + //! @note + //! This function was added in Pike 7.9. + //! + //! @seealso + //! @[verify()], @[predef::crypt()], @[Nettle.crypt_md5()], + //! @[Nettle.HashInfo()->crypt_hash()] + string hash(string password, string|void scheme, int|void rounds) + { +  function(string, string, int:string) crypt_hash; +  int salt_size = 16; +  int default_rounds = 5000; +  +  string render_crypt_hash(string scheme, string salt, +  string hash, int rounds) +  { +  if (rounds != default_rounds) { +  salt = "rounds=" + rounds + "$" + salt; +  } +  +  return sprintf("$%s$%s$%s", scheme, salt, hash); +  }; +  +  string render_ldap_hash(string scheme, string salt, +  string hash, int rounds) +  { +  if (scheme[0] != '{') scheme = "{" + scheme + "}"; +  return upper_case(scheme) + MIME.encode_base64(hash + salt); +  }; +  +  function(string, string, string, int:string) render_hash = render_crypt_hash; +  +  switch(lower_case(scheme)) { +  case "crypt": +  case "{crypt}": +  case UNDEFINED: +  // FALL_THROUGH + #if constant(Nettle.SHA512_Info) +  case "6": +  case "$6$": +  crypt_hash = Crypto.SHA512.crypt_hash; +  scheme = "6"; +  break; + #endif + #if constant(Nettle.SHA256_Info) +  case "5": +  case "$5$": +  crypt_hash = Crypto.SHA256.crypt_hash; +  scheme = "5"; +  break; + #endif +  case "1": +  case "$1$": +  crypt_hash = Crypto.MD5.crypt_hash; +  salt_size = 8; +  rounds = 1000; // Currently only 1000 rounds is supported. +  default_rounds = 1000; +  scheme = "1"; +  break; +  case "": +  return crypt(password); +  +  case "sha": +  case "{sha}": +  salt_size = 0; +  // FALL_THROUGH +  case "ssha": +  case "{ssha}": +  crypt_hash = lambda(string passwd, string salt, int rounds) { +  return Crypto.SHA1.hash(passwd + salt); +  }; +  render_hash = render_ldap_hash; +  break; +  +  case "md5": +  case "{md5}": +  salt_size = 0; +  // FALL_THROUGH +  case "smd5": +  case "{smd5}": +  crypt_hash = lambda(string passwd, string salt, int rounds) { +  return Crypto.MD5.hash(passwd + salt); +  }; +  render_hash = render_ldap_hash; +  break; +  +  default: +  error("Unsupported hashing scheme: %O\n", scheme); +  } +  +  if (!rounds) rounds = default_rounds; +  +  // NB: The salt must be printable. +  string salt = +  MIME.encode_base64(Crypto.Random.random_string(salt_size))[..salt_size-1]; +  +  string hash = crypt_hash(password, salt, rounds); +  +  return render_hash(scheme, salt, hash, rounds); + } +    Newline at end of file added.