Hello,

I have implemented an alternate implementation for salted password.
This is an RFC. Please see the attachment. It is a diff against 2.0.6 release.

I understand that the common usage is to send a push request but as a long time SVN user I didn't understand how to do so.

Matthieu

--
If you want to report a vulnerability issue on symfony, please send it to 
security at symfony-project.com

You received this message because you are subscribed to the Google
Groups "symfony developers" group.
To post to this group, send email to symfony-devs@googlegroups.com
To unsubscribe from this group, send email to
symfony-devs+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/symfony-devs?hl=en
diff --git 
a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php 
b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
index f5cdcf4..70ead1f 100644
--- 
a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
+++ 
b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
@@ -350,6 +350,7 @@ class MainConfiguration implements ConfigurationInterface
                         ->beforeNormalization()->ifString()->then(function($v) 
{ return array('algorithm' => $v); })->end()
                         ->children()
                             ->scalarNode('algorithm')->cannotBeEmpty()->end()
+                                                       
->scalarNode('salt_length')->defaultValue(4)->end()
                             ->booleanNode('ignore_case')->defaultFalse()->end()
                             
->booleanNode('encode_as_base64')->defaultTrue()->end()
                             
->scalarNode('iterations')->defaultValue(5000)->end()
diff --git 
a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php 
b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
index 95786d8..0d81322 100644
--- 
a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+++ 
b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
@@ -425,6 +425,7 @@ class SecurityExtension extends Extension
         // message digest encoder
         $arguments = array(
             $config['algorithm'],
+                       $config['salt_length'],
             $config['encode_as_base64'],
             $config['iterations'],
         );
diff --git 
a/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php
 
b/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php
index f17eaa4..8f0476f 100644
--- 
a/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php
+++ 
b/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php
@@ -63,7 +63,7 @@ class DaoAuthenticationProvider extends 
UserAuthenticationProvider
                 throw new BadCredentialsException('The presented password 
cannot be empty.');
             }
 
-            if 
(!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(),
 $presentedPassword, $user->getSalt())) {
+            if 
(!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(),
 $presentedPassword)) {
                 throw new BadCredentialsException('The presented password is 
invalid.');
             }
         }
diff --git 
a/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php 
b/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php
deleted file mode 100644
index 58c64db..0000000
--- a/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fab...@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Security\Core\Encoder;
-
-/**
- * BasePasswordEncoder is the base class for all password encoders.
- *
- * @author Fabien Potencier <fab...@symfony.com>
- */
-abstract class BasePasswordEncoder implements PasswordEncoderInterface
-{
-    /**
-     * Demerges a merge password and salt string.
-     *
-     * @param string $mergedPasswordSalt The merged password and salt string
-     *
-     * @return array An array where the first element is the password and the 
second the salt
-     */
-    protected function demergePasswordAndSalt($mergedPasswordSalt)
-    {
-        if (empty($mergedPasswordSalt)) {
-            return array('', '');
-        }
-
-        $password = $mergedPasswordSalt;
-        $salt = '';
-        $saltBegins = strrpos($mergedPasswordSalt, '{');
-
-        if (false !== $saltBegins && $saltBegins + 1 < 
strlen($mergedPasswordSalt)) {
-            $salt = substr($mergedPasswordSalt, $saltBegins + 1, -1);
-            $password = substr($mergedPasswordSalt, 0, $saltBegins);
-        }
-
-        return array($password, $salt);
-    }
-
-    /**
-     * Merges a password and a salt.
-     *
-     * @param string $password the password to be used
-     * @param string $salt the salt to be used
-     *
-     * @return string a merged password and salt
-     */
-    protected function mergePasswordAndSalt($password, $salt)
-    {
-        if (empty($salt)) {
-            return $password;
-        }
-
-        if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) {
-            throw new \InvalidArgumentException('Cannot use { or } in salt.');
-        }
-
-        return $password.'{'.$salt.'}';
-    }
-
-    /**
-     * Compares two passwords.
-     *
-     * This method implements a constant-time algorithm to compare passwords to
-     * avoid (remote) timing attacks.
-     *
-     * @param string $password1 The first password
-     * @param string $password2 The second password
-     *
-     * @return Boolean true if the two passwords are the same, false otherwise
-     */
-    protected function comparePasswords($password1, $password2)
-    {
-        if (strlen($password1) !== strlen($password2)) {
-            return false;
-        }
-
-        $result = 0;
-        for ($i = 0; $i < strlen($password1); $i++) {
-            $result |= ord($password1[$i]) ^ ord($password2[$i]);
-        }
-
-        return 0 === $result;
-    }
-}
diff --git 
a/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php 
b/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php
index a5b2c81..6f4b66f 100644
--- 
a/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php
+++ 
b/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php
@@ -16,40 +16,50 @@ namespace Symfony\Component\Security\Core\Encoder;
  *
  * @author Fabien Potencier <fab...@symfony.com>
  */
-class MessageDigestPasswordEncoder extends BasePasswordEncoder
+class MessageDigestPasswordEncoder implements PasswordEncoderInterface
 {
     private $algorithm;
-    private $encodeHashAsBase64;
+       private $saltLength;
+       private $iterations;
+       private $encodeHashAsBase64;
 
     /**
      * Constructor.
      *
      * @param string  $algorithm          The digest algorithm to use
+        * @parma integer $saltLength         The Length of the salt (0
      * @param Boolean $encodeHashAsBase64 Whether to base64 encode the 
password hash
      * @param integer $iterations         The number of iterations to use to 
stretch the password hash
      */
-    public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = 
true, $iterations = 5000)
+    public function __construct($algorithm = 'sha512', $saltLength = 4, 
$encodeHashAsBase64 = true, $iterations = 5000)
     {
-        $this->algorithm = $algorithm;
-        $this->encodeHashAsBase64 = $encodeHashAsBase64;
-        $this->iterations = $iterations;
+               if (!in_array($algorithm, hash_algos(), true)) {
+                       throw new \LogicException(sprintf('The algorithm "%s" 
is not supported.', $algorithm));
+               }
+               
+               if ($saltLength < 0) {
+                       throw new \LogicException(sprintf('The Salt Length must 
be a positive integer ("%d" given).', $saltLength));
+               }
+       
+               $this->algorithm = $algorithm;
+               $this->saltLength = $saltLength;
+               $this->encodeHashAsBase64 = $encodeHashAsBase64;
+               $this->iterations = $iterations;
     }
 
     /**
      * {@inheritdoc}
      */
-    public function encodePassword($raw, $salt)
+    public function encodePassword($plain)
     {
-        if (!in_array($this->algorithm, hash_algos(), true)) {
-            throw new \LogicException(sprintf('The algorithm "%s" is not 
supported.', $this->algorithm));
-        }
-
-        $salted = $this->mergePasswordAndSalt($raw, $salt);
-        $digest = hash($this->algorithm, $salted, true);
-
-        // "stretch" hash
+               $salt = "";
+               for ($i = 0; $i < $this->saltLength; $i++) {
+                       $salt .= pack('c', rand(0x00, 0xff));
+               }
+               
+               $digest = hash($this->algorithm, $plain . $salt, true) . $salt;
         for ($i = 1; $i < $this->iterations; $i++) {
-            $digest = hash($this->algorithm, $digest.$salted, true);
+            $digest = hash($this->algorithm, $digest . $salt, true) . $salt;
         }
 
         return $this->encodeHashAsBase64 ? base64_encode($digest) : 
bin2hex($digest);
@@ -58,8 +68,16 @@ class MessageDigestPasswordEncoder extends 
BasePasswordEncoder
     /**
      * {@inheritdoc}
      */
-    public function isPasswordValid($encoded, $raw, $salt)
-    {
-        return $this->comparePasswords($encoded, $this->encodePassword($raw, 
$salt));
+    public function isPasswordValid($encoded, $plain)
+    {  
+               $digest1 = $this->encodeHashAsBase64 ? base64_decode($encoded, 
true) : hex2bin($encoded);       
+               $salt = substr($digest1, -$this->saltLength);
+               
+               $digest2 = hash($this->algorithm, $plain . $salt, true) . $salt;
+        for ($i = 1; $i < $this->iterations; $i++) {
+            $digest2 = hash($this->algorithm, $digest2 . $salt, true) . $salt;
+        }
+
+               return 0 == strcmp($digest1, $digest2);
     }
 }
diff --git 
a/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php 
b/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php
index dae6c69..6fce971 100644
--- a/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php
+++ b/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php
@@ -21,21 +21,19 @@ interface PasswordEncoderInterface
     /**
      * Encodes the raw password.
      *
-     * @param string $raw  The password to encode
-     * @param string $salt The salt
+     * @param string $plain The password to encode
      *
      * @return string The encoded password
      */
-    function encodePassword($raw, $salt);
+    function encodePassword($plain);
 
     /**
      * Checks a raw password against an encoded password.
      *
      * @param string $encoded An encoded password
-     * @param string $raw     A raw password
-     * @param string $salt    The salt
+     * @param string $plain   A plain password
      *
      * @return Boolean true if the password is valid, false otherwise
      */
-    function isPasswordValid($encoded, $raw, $salt);
+    function isPasswordValid($encoded, $plain);
 }
diff --git 
a/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php 
b/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php
index 21a9a97..a9bf9c3 100644
--- a/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php
+++ b/src/Symfony/Component/Security/Core/Encoder/PlaintextPasswordEncoder.php
@@ -16,7 +16,7 @@ namespace Symfony\Component\Security\Core\Encoder;
  *
  * @author Fabien Potencier <fab...@symfony.com>
  */
-class PlaintextPasswordEncoder extends BasePasswordEncoder
+class PlaintextPasswordEncoder implements PasswordEncoderInterface
 {
     private $ignorePasswordCase;
 
@@ -28,22 +28,18 @@ class PlaintextPasswordEncoder extends BasePasswordEncoder
     /**
      * {@inheritdoc}
      */
-    public function encodePassword($raw, $salt)
+    public function encodePassword($plain)
     {
-        return $this->mergePasswordAndSalt($raw, $salt);
+        return $plain;
     }
 
     /**
      * {@inheritdoc}
      */
-    public function isPasswordValid($encoded, $raw, $salt)
+    public function isPasswordValid($encoded, $plain)
     {
-        $pass2 = $this->mergePasswordAndSalt($raw, $salt);
-
-        if (!$this->ignorePasswordCase) {
-            return $this->comparePasswords($encoded, $pass2);
-        }
-
-        return $this->comparePasswords(strtolower($encoded), 
strtolower($pass2));
+        return 0 == ($this->ignorePasswordCase
+                       ? strcasecmp($encoded, $plain)
+                       : strcmp($encoded, $plain));
     }
 }
diff --git a/src/Symfony/Component/Security/Core/User/UserInterface.php 
b/src/Symfony/Component/Security/Core/User/UserInterface.php
index 9091bfc..ac98178 100644
--- a/src/Symfony/Component/Security/Core/User/UserInterface.php
+++ b/src/Symfony/Component/Security/Core/User/UserInterface.php
@@ -33,13 +33,6 @@ interface UserInterface
     function getPassword();
 
     /**
-     * Returns the salt.
-     *
-     * @return string The salt
-     */
-    function getSalt();
-
-    /**
      * Returns the username used to authenticate the user.
      *
      * @return string The username

Reply via email to