283b622013-03-26Martin Nilsson #pike __REAL_VERSION__ #pragma strict_types
73cc5f2013-03-11Henrik Grubbström (Grubba) //! 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. //!
dc93732015-08-22Martin Nilsson //! LDAP-style (@rfc{2307@}) hashes:
c099c92013-03-11Henrik Grubbström (Grubba) //! @string //! @value "{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@}. //! //! @value "{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@}. //! //! @value "{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@}. //! //! @value "{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@}. //! //! @value "{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. //! @endstring
73cc5f2013-03-11Henrik Grubbström (Grubba) //! //! Crypt-style hashes:
c099c92013-03-11Henrik Grubbström (Grubba) //! @string //! @value "$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.crypt_hash()] with @expr{5000@} //! rounds. Source: Unix crypt using SHA-256 and SHA-512 //! @url{http://www.akkadia.org/drepper/SHA-crypt.txt@} //! //! @value "$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@} //! //! @value "$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.crypt_hash()] with @expr{5000@} //! rounds. Source: Unix crypt using SHA-256 and SHA-512 //! @url{http://www.akkadia.org/drepper/SHA-crypt.txt@} //! //! @value "$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@} //! //! @value "$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@}. //! //! @value "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. //! //! @value "" //! The empty password hash matches all passwords. //! @endstring
73cc5f2013-03-11Henrik Grubbström (Grubba) //! //! @returns //! Returns @expr{1@} on success, and @expr{0@} (zero) otherwise. //! //! @note
c099c92013-03-11Henrik Grubbström (Grubba) //! This function was added in Pike 7.8.755.
73cc5f2013-03-11Henrik Grubbström (Grubba) //! //! @seealso //! @[hash()], @[predef::crypt()]
5cffd82013-12-19Martin Nilsson int verify(string(8bit) password, string(8bit) hash)
73cc5f2013-03-11Henrik Grubbström (Grubba) { 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];
5cffd82013-12-19Martin Nilsson  return Crypto.MD5.hash(password) == hash;
73cc5f2013-03-11Henrik Grubbström (Grubba)  case "sha": // RFC 2307 case "ssha": // SHA1 and Salted SHA1. hash = MIME.decode_base64(hash); password += hash[20..]; hash = hash[..19];
5cffd82013-12-19Martin Nilsson  return Crypto.SHA1.hash(password) == hash;
73cc5f2013-03-11Henrik Grubbström (Grubba)  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.
5cffd82013-12-19Martin Nilsson  sscanf(hash, "$%s$%s$%s", scheme, string(8bit) salt, string(8bit) hash);
3115f42013-05-20Martin Nilsson  if( !salt || !hash ) return 0;
73cc5f2013-03-11Henrik Grubbström (Grubba)  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
387ff92013-12-19Martin Nilsson  return Nettle.crypt_md5(password, salt) == [string(7bit)]hash;
73cc5f2013-03-11Henrik Grubbström (Grubba)  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; // cf http://www.akkadia.org/drepper/SHA-crypt.txt case "5": // SHA-256
6ab54c2013-05-20Martin Nilsson  return Crypto.SHA256.crypt_hash(password, salt, rounds) ==
387ff92013-12-19Martin Nilsson  [string(7bit)]hash;
b375e82013-10-15Martin Nilsson #if constant(Crypto.SHA512)
73cc5f2013-03-11Henrik Grubbström (Grubba)  case "6": // SHA-512
6ab54c2013-05-20Martin Nilsson  return Crypto.SHA512.crypt_hash(password, salt, rounds) ==
387ff92013-12-19Martin Nilsson  [string(7bit)]hash;
73cc5f2013-03-11Henrik Grubbström (Grubba) #endif } break; } return 0; }
c099c92013-03-11Henrik Grubbström (Grubba) //! Generate a hash of @[password] suitable for @[verify()].
73cc5f2013-03-11Henrik Grubbström (Grubba) //! //! @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:
c099c92013-03-11Henrik Grubbström (Grubba) //! //! Crypt(3C)-style: //! @string //! @value UNDEFINED //! @value "crypt" //! @value "{crypt}" //! Use the strongest crypt(3C)-style hash that is supported. //! //! @value "6" //! @value "$6$"
73cc5f2013-03-11Henrik Grubbström (Grubba) //! @[SHA512.crypt_hash()] with 96 bits of salt and a default //! of @expr{5000@} rounds. //!
c099c92013-03-11Henrik Grubbström (Grubba) //! @value "5" //! @value "$5$"
73cc5f2013-03-11Henrik Grubbström (Grubba) //! @[SHA256.crypt_hash()] with 96 bits of salt and a default //! of @expr{5000@} rounds. //!
c099c92013-03-11Henrik Grubbström (Grubba) //! @value "1" //! @value "$1$"
73cc5f2013-03-11Henrik Grubbström (Grubba) //! @[MD5.crypt_hash()] with 48 bits of salt and @expr{1000@} rounds. //!
c099c92013-03-11Henrik Grubbström (Grubba) //! @value ""
73cc5f2013-03-11Henrik Grubbström (Grubba) //! @[predef::crypt()] with 12 bits of salt. //!
c099c92013-03-11Henrik Grubbström (Grubba) //! @endstring //!
2eedd52015-08-22Henrik Grubbström (Grubba) //! LDAP (@rfc{2307@})-style. Don't use these if you can avoid it,
c099c92013-03-11Henrik Grubbström (Grubba) //! since they are suspectible to attacks. In particular avoid //! the unsalted variants at all costs: //! @string
607dbd2013-03-11Henrik Grubbström (Grubba) //! @value "ssha" //! @value "{ssha}"
c099c92013-03-11Henrik Grubbström (Grubba) //! @[SHA1.hash()] with 96 bits of salt appended to the password. //!
607dbd2013-03-11Henrik Grubbström (Grubba) //! @value "smd5" //! @value "{smd5}"
c099c92013-03-11Henrik Grubbström (Grubba) //! @[MD5.hash()] with 96 bits of salt appended to the password. //!
607dbd2013-03-11Henrik Grubbström (Grubba) //! @value "sha" //! @value "{sha}"
c099c92013-03-11Henrik Grubbström (Grubba) //! @[SHA1.hash()] without any salt. //!
607dbd2013-03-11Henrik Grubbström (Grubba) //! @value "md5" //! @value "{md5}"
c099c92013-03-11Henrik Grubbström (Grubba) //! @[MD5.hash()] without any salt. //! @endstring
73cc5f2013-03-11Henrik Grubbström (Grubba) //! //! @param rounds //! The number of rounds to use in parameterized schemes. If not //! specified the scheme specific default will be used. //! //! @returns
c099c92013-03-11Henrik Grubbström (Grubba) //! Returns a string suitable for @[verify()]. This means that //! the hashes will be prepended with the suitable markers.
73cc5f2013-03-11Henrik Grubbström (Grubba) //! //! @note //! Note that the availability of @[SHA512] depends on the version //! of @[Nettle] that Pike has been compiled with. //! //! @note
c099c92013-03-11Henrik Grubbström (Grubba) //! This function was added in Pike 7.8.755.
73cc5f2013-03-11Henrik Grubbström (Grubba) //! //! @seealso //! @[verify()], @[predef::crypt()], @[Nettle.crypt_md5()],
4d65952013-10-21Henrik Grubbström (Grubba) //! @[Nettle.Hash()->crypt_hash()]
d8519c2013-12-19Martin Nilsson string(7bit) hash(string(8bit) password, string(7bit)|void scheme, int|void rounds)
73cc5f2013-03-11Henrik Grubbström (Grubba) {
9eb14c2013-12-19Martin Nilsson  function(string(8bit), string(7bit), int:string(8bit)) crypt_hash;
b432312014-04-06Martin Nilsson  int(0..) salt_size = 16;
73cc5f2013-03-11Henrik Grubbström (Grubba)  int default_rounds = 5000;
387ff92013-12-19Martin Nilsson  string(7bit) render_crypt_hash(string(7bit) scheme, string(7bit) salt, string(8bit) hash, int rounds)
73cc5f2013-03-11Henrik Grubbström (Grubba)  { if (rounds != default_rounds) { salt = "rounds=" + rounds + "$" + salt; }
387ff92013-12-19Martin Nilsson  // We claim this to be a string(7bit) string, even though we add
bff3272013-05-28Martin Nilsson  // the string(0..256). It will however only be called with the // already base64 encoded hashes.
d8519c2013-12-19Martin Nilsson  return sprintf("$%s$%s$%s", scheme, salt, [string(7bit)]hash);
73cc5f2013-03-11Henrik Grubbström (Grubba)  };
387ff92013-12-19Martin Nilsson  string(7bit) render_ldap_hash(string(8bit) scheme, string(7bit) salt, string(8bit) hash, int rounds)
73cc5f2013-03-11Henrik Grubbström (Grubba)  { if (scheme[0] != '{') scheme = "{" + scheme + "}";
387ff92013-12-19Martin Nilsson  return [string(7bit)]upper_case(scheme) + MIME.encode_base64(hash + salt);
73cc5f2013-03-11Henrik Grubbström (Grubba)  };
387ff92013-12-19Martin Nilsson  function(string(7bit), string(7bit), string(8bit), int:string(7bit)) render_hash = render_crypt_hash;
73cc5f2013-03-11Henrik Grubbström (Grubba)  switch(lower_case(scheme)) { case "crypt": case "{crypt}": case UNDEFINED: // FALL_THROUGH
b375e82013-10-15Martin Nilsson #if constant(Crypto.SHA512)
73cc5f2013-03-11Henrik Grubbström (Grubba)  case "6": case "$6$": crypt_hash = Crypto.SHA512.crypt_hash; scheme = "6"; break; #endif case "5": case "$5$": crypt_hash = Crypto.SHA256.crypt_hash; scheme = "5"; break; 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}":
9eb14c2013-12-19Martin Nilsson  crypt_hash = lambda(string(8bit) passwd, string(7bit) salt, int rounds) {
73cc5f2013-03-11Henrik Grubbström (Grubba)  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}":
5cffd82013-12-19Martin Nilsson  crypt_hash = lambda(string(8bit) passwd, string(8bit) salt, int rounds) {
73cc5f2013-03-11Henrik Grubbström (Grubba)  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.
387ff92013-12-19Martin Nilsson  string(7bit) salt =
73cc5f2013-03-11Henrik Grubbström (Grubba)  MIME.encode_base64(Crypto.Random.random_string(salt_size))[..salt_size-1];
387ff92013-12-19Martin Nilsson  string(8bit) hash = crypt_hash(password, salt, rounds);
73cc5f2013-03-11Henrik Grubbström (Grubba) 
387ff92013-12-19Martin Nilsson  return render_hash([string(7bit)]scheme, salt, hash, rounds);
73cc5f2013-03-11Henrik Grubbström (Grubba) }