Version: current

      1 <?php
      2 /**
      3  * Allows pluggable password encryption.
      4  * By default, this might be PHP's integrated sha1()
      5  * function, but could also be more sophisticated to facilitate
      6  * password migrations from other systems.
      7  * Use {@link register()} to add new implementations.
      8  *
      9  * Used in {@link Security::encrypt_password()}.
     10  *
     11  * @package sapphire
     12  * @subpackage security
     13  */
     14 abstract class PasswordEncryptor {
     15
     16     /**
     17      * @var array
     18      */
     19     protected static $encryptors = array();
     20
     21     /**
     22      * @return Array Map of encryptor code to the used class.
     23      */
     24     static function get_encryptors() {
     25         return self::$encryptors;
     26     }
     27
     28     /**
     29      * Add a new encryptor implementation.
     30      *
     31      * Note: Due to portability concerns, its not advisable to
     32      * override an existing $code mapping with different behaviour.
     33      *
     34      * @param String $code This value will be stored stored in the
     35      *  {@link Member->PasswordEncryption} property.
     36      * @param String $class Classname of a {@link PasswordEncryptor} subclass
     37      */
     38     static function register($code, $class) {
     39         self::$encryptors[$code] = $class;
     40     }
     41
     42     /**
     43      * @param String $code Unique lookup.
     44      */
     45     static function unregister($code) {
     46         if(isset(self::$encryptors[$code])) unset(self::$encryptors[$code]);
     47     }
     48
     49     /**
     50      * @param String $algorithm
     51      * @return PasswordEncryptor|Boolean Returns FALSE if class was not found
     52      */
     53     static function create_for_algorithm($algorithm) {
     54         if(!isset(self::$encryptors[$algorithm])) {
     55             throw new PasswordEncryptor_NotFoundException(
     56                 sprintf('No implementation found for "%s"', $algorithm)
     57             );
     58         }
     59
     60         $classWithArgs = self::$encryptors[$algorithm];
     61         $class = (($p = strpos($classWithArgs, '(')) !== false) ? substr($classWithArgs, 0, $p) : $classWithArgs;
     62         if(!class_exists($class)) {
     63             throw new PasswordEncryptor_NotFoundException(
     64                 sprintf('No class found for "%s"', $class)
     65             );
     66         }
     67
     68         return eval("return new $classWithArgs;");
     69     }
     70
     71     /**
     72      * Return a string value stored in the {@link Member->Password} property.
     73      * The password should be hashed with {@link salt()} if applicable.
     74      *
     75      * @param String $password Cleartext password to be hashed
     76      * @param String $salt (Optional)
     77      * @param Member $member (Optional)
     78      * @return String Maximum of 512 characters.
     79      */
     80     abstract function encrypt($password, $salt = null, $member = null);
     81
     82     /**
     83      * Return a string value stored in the {@link Member->Salt} property.
     84      * By default uses sha1() and mt_rand();
     85      *
     86      * Note: Only used when {@link Security::$useSalt} is TRUE.
     87      *
     88      * @param String $password Cleartext password
     89      * @param Member $member (Optional)
     90      * @return String Maximum of 50 characters
     91      */
     92     function salt($password, $member = null) {
     93         return substr(sha1(mt_rand()) . time(), 0, 50);
     94     }
     95
     96     /**
     97      * This usually just returns a strict string comparison,
     98      * but is necessary for {@link PasswordEncryptor_LegacyPHPHash}.
     99      *
    100      * @param String $hash1
    101      * @param String $hash2
    102      * @return boolean
    103      */
    104     function compare($hash1, $hash2) {
    105         return ($hash1 === $hash2);
    106     }
    107 }
    108
    109 /**
    110  * This is the default class used for built-in hash types in PHP.
    111  * Please note that the implemented algorithms depend on the PHP
    112  * distribution and architecture.
    113  *
    114  * @package sapphire
    115  * @subpackage security
    116  */
    117 class PasswordEncryptor_PHPHash extends PasswordEncryptor {
    118
    119     protected $algorithm = 'sha1';
    120
    121     /**
    122      * @param String $algorithm A PHP built-in hashing algorithm as defined by hash_algos()
    123      */
    124     function __construct($algorithm) {
    125         if(!in_array($algorithm, hash_algos())) {
    126             throw new Exception(
    127                 sprintf('Hash algorithm "%s" not found in hash_algos()', $algorithm)
    128             );
    129         }
    130
    131         $this->algorithm = $algorithm;
    132     }
    133
    134     /**
    135      * @return string
    136      */
    137     function getAlgorithm() {
    138         return $this->algorithm;
    139     }
    140
    141     function encrypt($password, $salt = null, $member = null) {
    142         if(function_exists('hash')) {
    143             // Available in PHP 5.1+ only
    144             return hash($this->algorithm, $password . $salt);
    145         } else {
    146             // Fallback to global built-in methods
    147             return call_user_func($this->algorithm, $password . $salt);
    148         }
    149     }
    150 }
    151
    152 /**
    153  * Legacy implementation for SilverStripe 2.1 - 2.3,
    154  * which had a design flaw in password hashing that caused
    155  * the hashes to differ between architectures due to
    156  * floating point precision problems in base_convert().
    157  * See http://open.silverstripe.org/ticket/3004
    158  *
    159  * @package sapphire
    160  * @subpackage security
    161  */
    162 class PasswordEncryptor_LegacyPHPHash extends PasswordEncryptor_PHPHash {
    163     function encrypt($password, $salt = null, $member = null) {
    164         $password = parent::encrypt($password, $salt, $member);
    165
    166         // Legacy fix: This shortening logic is producing unpredictable results.
    167         //
    168         // Convert the base of the hexadecimal password to 36 to make it shorter
    169         // In that way we can store also a SHA256 encrypted password in just 64
    170         // letters.
    171         return substr(base_convert($password, 16, 36), 0, 64);
    172     }
    173
    174     function compare($hash1, $hash2) {
    175         // Due to flawed base_convert() floating poing precision,
    176         // only the first 10 characters are consistently useful for comparisons.
    177         return (substr($hash1, 0, 10) === substr($hash2, 0, 10));
    178     }
    179 }
    180
    181 /**
    182  * Uses MySQL's PASSWORD encryption. Requires an active DB connection.
    183  *
    184  * @package sapphire
    185  * @subpackage security
    186  */
    187 class PasswordEncryptor_MySQLPassword extends PasswordEncryptor {
    188     function encrypt($password, $salt = null, $member = null) {
    189         return DB::query(
    190             sprintf("SELECT PASSWORD('%s')", Convert::raw2sql($password))
    191         )->value();
    192     }
    193
    194     function salt($password, $member = null) {
    195         return false;
    196     }
    197 }
    198
    199 /**
    200  * Uses MySQL's OLD_PASSWORD encyrption. Requires an active DB connection.
    201  *
    202  * @package sapphire
    203  * @subpackage security
    204  */
    205 class PasswordEncryptor_MySQLOldPassword extends PasswordEncryptor {
    206     function encrypt($password, $salt = null, $member = null) {
    207         return DB::query(
    208             sprintf("SELECT OLD_PASSWORD('%s')", Convert::raw2sql($password))
    209         )->value();
    210     }
    211
    212     function salt($password, $member = null) {
    213         return false;
    214     }
    215 }
    216
    217 /**
    218  * Cleartext passwords (used in SilverStripe 2.1).
    219  * Also used when Security::$encryptPasswords is set to FALSE.
    220  * Not recommended.
    221  *
    222  * @package sapphire
    223  * @subpackage security
    224  */
    225 class PasswordEncryptor_None extends PasswordEncryptor {
    226     function encrypt($password, $salt = null, $member = null) {
    227         return $password;
    228     }
    229
    230     function salt($password, $member = null) {
    231         return false;
    232     }
    233 }
    234
    235 /**
    236  * @package sapphire
    237  * @subpackage security
    238  */
    239 class PasswordEncryptor_NotFoundException extends Exception {}

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -