Registro de usuarios desde cero.

Gracias Serverdns. Miraré cómo reemplazarlo. Lo intenté cambiando el comando sin más pero no funcionaba. Tendré que estudiar su sintaxis mejor. Tomo nota para actualizarlo, gracias!
[TUTORIAL :: SISTEMA DE REGISTRO DE USUARIOS :: PARTE 15]

Vamos a aplicar un método sencillo al formulario para mejorar su seguridad. Con él, nos aseguraremos de que los datos enviados a la tabla provienen tan solo del formulario y no de cualquier otro lugar.

Como sabréis, en el formulario podemos colocar campos ocultos a los que nosotros mismos le damos el valor que queramos. Ese campo y valor no lo ven los usuarios, pero es enviado junto con el resto de campos que sí rellena.

La idea es añadir un campo oculto llamado por ejemplo, testigo, al que daremos el valor valido mediante esta línea:
<input type="hidden" name="testigo" value="valido" />

El archivo formulario quedará entonces así:

Nombre del archivo: formulario.php

<?php

//incluimos configuracion.php con los datos de conexión
include_once('configuracion.php');

//Creamos el formulario de registro
echo '
<form action="registro.php" method="POST">
Nick: <input type="text" name="nick" size="25" /><br />
Contraseña: <input type="password" name="password" size="40" /><br />
Repite la contraseña: <input type="password" name="password2" size="40" /><br />
E-mail: <input type="text" name="email" size="60" /><br />
<input type="hidden" name="testigo" value="valido" />
<input type="submit" name="submit" value="Registrar" />
</form>
';

// Damos la opción de volver a la página de login si no desea registrarse
echo '<br /><a href="login.php">Login</a>'; 

?> 

Seguido de esto, añadimos una condición en el archivo registro.php con la que abortaremos el proceso si el valor de testigo no es igual a valido. Por ejemplo, colocando la línea if ( ($_POST['testigo]) != "valido") {$error = true;} al principio del archivo. Con ella, si testigo no tiene el valor valido, damos a la variable $error el valor true, que provoca que se aborte el proceso en el siguiente if que hay pocas líneas más abajo.

Nombre del archivo: registro.php

<?php

//incluimos configuracion.php con los datos de conexión
include_once('configuracion.php');

if ( ($_POST['testigo]) != "valido") {$error = true;}

function limpia($cadena)
    {
    return (ereg_replace('[^A-Za-z0-9_-ñÑ]', '', $cadena));
    }

// definimos las variables
$nick     = limpia($_POST['nick'])     ;
$email    = $_POST['email']            ;
$password = limpia($_POST['password']) ;
$password2= limpia($_POST['password2']);
// marcamos el error
if ( empty($nick) OR empty($email) OR empty($password) OR empty($password2) ) {$error = true;}
// si eliminando caráteres raros queda algún campo vacio, generamos un error y devolvemos al formulario
if ( !empty($error) )
{
   header("Refresh: 2; URL=formulario.php");
   echo "<p>ERROR. Los datos introducidos no tienen un formato correcto.</p>";
   die;
}

function verificar_email($email) 
   {
   // Verificamos que no hay más de una arroba y que las longitudes son normales
   if (!ereg("^[^@]{1,64}@[^@]{1,255}$", $email)) 
      {
      return false;
      }
   $email_array = explode("@", $email);
   $local_array = explode(".", $email_array[0]);
   for ($i = 0; $i < sizeof($local_array); $i++) 
      {
      if (!ereg("^(([A-Za-z0-9!#$%&'*+/=?^_`{|}~-][A-Za-z0-9!#$%&'*+/=?^_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$", $local_array[$i])) 
         {
         return false;
         }
      } 
   // se revisa si el dominio es una IP. Si no, debe ser un nombre de dominio válido
   if (!ereg("^\[?[0-9\.]+\]?$", $email_array[1])) 
      { 
      $domain_array = explode(".", $email_array[1]);
      if (sizeof($domain_array) < 2) 
         {
         return false; // No son suficientes partes para ser un dominio
         }
      for ($i = 0; $i < sizeof($domain_array); $i++) 
         {
         if (!ereg("^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$", $domain_array[$i])) 
            {
            return false;
            }
         }
      }
      return true;
   }

   if ( verificar_email($email)==false )
      {
      header("Refresh: 5; URL=formulario.php");
      echo "<p>ERROR. El Email $email no parece ser real.</p>";
      die;
      }

//Comprobamos que la password y password2 son iguales, sino, volvemos a registro.php
if($password != $password2)
    {
    echo 'ERROR. Las passwords no coinciden';
    }
else
    {
    //cuantos usuarios hay ya con ese nick?
    $cuantosnicks=mysql_query("SELECT nick FROM ".$tabla." WHERE nick='".$nick."' ");
    //cuantos usuarios hay ya con ese email?
    $cuantosemails=mysql_query("SELECT email FROM ".$tabla." WHERE email='".$email."' ");
    $nickrepetido = mysql_num_rows($cuantosnicks);
    $emailrepetido = mysql_num_rows($cuantosemails);
    if($nickrepetido >= 1)
        {
        echo 'El nick propuesto ya estaba registrado. Escoge otro.';
        // damos la opcion de registrarse de nuevo
        echo '<br /><a href="formulario.php">Quieres intentarlo con otro nick?</a>'; 
        }
    else if($emailrepetido >= 1)
        {
        echo 'El email propuesto ya estaba registrado. Escoge otro.';
        // damos la opcion de registrarse de nuevo
        echo '<br /><a href="formulario.php">Quieres intentarlo con otro email?</a>';
        }
    else
        {
        //introducimos el nuevo registro en la tabla users
        mysql_query("INSERT INTO $tabla (nick,password,email) values ('$nick','$password','$email') ");
        echo "<br /><p>Usuario registrado con éxito con Nick=$nick, Contraseña=$password y email=$email</p>";
        // damos la opcion de logerase
        echo '<br /><a href="login.php">Quieres logearte?</a>';
        }
    mysql_free_result($cuantosnicks);  //liberamos memoria
    mysql_free_result($cuantosemails); //liberamos memoria
   }
?> 
Qué os parece?
Jorge creo que tendrias que poner un limite de caracteres en los campos del formulario por seguridad pues te pueden pasar un script ejecutable e incluso robarte la web.
Ok, gracias lantiga, se me pasó por alto. Añadiré un maxlength="XX" en los campos del formulario.

Gracias una vez más!
[TUTORIAL :: SISTEMA DE REGISTRO DE USUARIOS :: PARTE 16]

Gracias al aporte de Lantiga, vamos a limitar la cantidad de carácteres que admiten los campos del formulario. Con eso limitamos la cantidad de código malicioso que un craker pueda inyectarnos y por tanto, limitamos el poder malicioso del ataque, pues no podrá hacer tantas cosas con un número tan limitado de carácteres.

Para ello basta con colocar un maxlength en cada campo, igual al número máximo de carácteres que le hemos definido anteriormente.

Quedaría así por tanto el archivo formulario.php.

<?php

//incluimos configuracion.php con los datos de conexión
include_once('configuracion.php');

//Creamos el formulario de registro
echo '
<form action="registro.php" method="POST">
Nick: <input type="text" name="nick" maxlength="25" size="25" /><br />
Contraseña: <input type="password" name="password" maxlength="40" size="40" /><br />
Repite la contraseña: <input type="password" name="password2" maxlength="40" size="40" /><br />
E-mail: <input type="text" name="email" maxlength="60" size="60" /><br />
<input type="hidden" name="testigo" value="valido" />
<input type="submit" name="submit" value="Registrar" />
</form>
';

// Damos la opción de volver a la página de login si no desea registrarse
echo '<br /><a href="login.php">Login</a>'; 

?> 
[TUTORIAL :: SISTEMA DE REGISTRO DE USUARIOS :: PARTE 17]

Nos toca ahora mejorar la seguridad de las contraseñas. Parece que es de locos guardar las contraseñas en la tabla tal cuál. Se hace imprescindible encriptarlas de algún modo. Existen varios métodos para ello. Yo he leido sobre HASH y SALT por ahora y se me ocurre aplicar ambas a la vez. Quizás sea exagerar, pero por el momento es lo que se me ocurre mientras alguno de vosotros no me proponga algo mejor :=)

Mediante el HASH, convertimos la contraseña propuesta por el usuario en una cadena de 64 carácteres mediante un algoritmo que transforma la entrada en una cadena única, es decir, siempre que se aplica ese algoritmo a esa contraseña, se obtiene la misma ristra final. El algoritmo se llama Sha256 y se aplica simplemente de este modo:

$hasheada = hash('sha256', $password);

El segundo método que os comentaba, el SALT, minimiza la posibilidad de que por fuerza bruta (brute force) puedan descifrar la contraseña a la que habíamos aplicado el HASH. El SALT es una cadena de carácteres aleatoria que se añade al resultado de aplicar el HASH. Esto lo podemos hacer mediante esta función:

function saltear ()
{
$cadena = md5 (uniqid(rand(), true));
return substr($cadena, 0, 3);
}

que definiremos en el archivo registro.php. Además de definirla, claro, hay que utilizarla, no? Definimos en ese mismo archivo una variable que guarde la contraseña salteada, es decir, con el salt aplicado. Posteriomente, aplicamos el hash a esa variable y guardamos el resultado en la variable $hasheada. Terminado esto, en lugar de guardar en la tabla la variable $password, guardaremos la variable $hasheada y la variable $salteada.

Fíjate que tenemos que añadir un campo más en la tabla de la base de datos, llamado salteada para guardar el valor de la variable $salteada. Para ello basta con acceder a la lengüeta llamada SQL del PhpMyAdmin e introducir estas líneas en el recuadro:

ALTER TABLE `usuarios` ADD `salteada` VARCHAR( 3 ) NOT NULL

Además, tenemos que modificar las características del campo password pues ahora va a guardar un valor con más carácteres que los 40 que habíamos indicado al principio. Para ello, en esa misma ventana de SQL ejecutamos esta otra línea:

ALTER TABLE `usuarios` CHANGE `password` `password` VARCHAR( 64 ) NOT NULL


En el campo password de la tabla no guardaremos la contraseña tal cuál la introdujo el usuario, sino su valor "hasheado", con lo que realmente no estamos guardando el valor propio de la contraseña que es lo que queríamos evitar. Por eso, en el archivo registro.php tendremos que modificar la línea que envía los datos a la tabla y, añadir un par de líneas antes de esa, en la que definimos el valor de las variables $salteada y $hasheada antes de meterlas en la tabla:

$hasheada= hash('sha256', $password) ;
$salteada = saltear();
$hasheada = hash('sha256', $salteada . $hasheada);
mysql_query("INSERT INTO $tabla (nick,password,salteada,email) values ('$nick','$hasheada','$salteada','$email') ");

Por último, cuando un usuario ya registrado pretenda logearse tendremos que cambiar el modo de verificar su contraseña, pues ya no podemos pretender que la que introduzca en el formulario de login sea igual a la guardada en la tabla, pues ha sido hasheada y salteada, verdad? Modificamos entonces el modo de verificar esos valores, la contraseña que indica el usuario registrado y la guardada en la tabla.

Para empezar cazamos los datos provenientes del formulario de login en el archivo chequeo.php. Si no existen las variables password o salteada para el nick dado, será que no existe ese usuario, claro, de modo que se muestra una advertencia al visitante y en 4 segundos se le reenvia de nuevo a la página de login.

Si existen esos valores, le hacemos un hash mediante el algoritmo sha256 a una cadena formada por la concatenación del salt que había en la tabla, seguido de un hash hecho a la contraseña indicada por el usuario mediante el algoritmo sha256. De este modo, obtenemos los valores de las variables $hash, procedente de los datos dados por el usuario al intentar logearse, y de la variable $datosdeusuario que es la contraseña encriptada que hay guardada en la tabla para ese nick. Si todo es correcto, deberían coincidir, no? En ese caso le abrimos sesión al usuario monstrándole un mensaje. Si no coinciden, se lo indicamos y lo mandamos al archivo login.php por si quiere intentarlo de nuevo.

Todo este rollo dicho en código es lo que sigue...

Nombre del archivo: chequeo.php

. . . . . . . . 
// Chequeamos que el usuario existe y que la contraseña es correcta
$nick= $_POST['nick']; // cazamos el valor de nick del formulario
$password = $_POST['password']; // cazamos el valor de la contraseña del formulario
//sacamos los valores correspondientes a ese nick de las variables $password (que está hasheada) y $salteada
$consulta= mysql_query("SELECT * FROM ".$tabla." WHERE nick='".$nick."' ");
if(mysql_num_rows($consulta) < 1) //si no existe el usuario...
{
   header("Refresh: 4; URL=login.php");
   echo "<p>ERROR. El nick dado no existe.</p>";
   die;
}
$datosdelusuario= mysql_fetch_array($consulta, MYSQL_ASSOC);
$hash = hash('sha256', $datosdelusuario['salteada'] . hash('sha256', $password) );  //aplicamos el algoritmo a la contraseña dada por el usuario en el login para poder compararla con la que tenemos guardada
if($hash != $datosdelusuario['password']) //si la contraseña es incorrecta...
{
   header("Refresh: 4; URL=login.php");
   echo "<br /><p>ERROR. La contraseña no es válida.</p>";
   die;
}
// login correcto, continuamos... 
. . . . . . . . .

Aún no he probado, os aviso cuando lo haga. Es mucho cambio y quizás haya metido la pata en algo, je je je. Mientras podemos comentar, claro.
[TUTORIAL :: SISTEMA DE REGISTRO DE USUARIOS :: PARTE 18]

El contenido de los archivos modificados en la parte 17 quedarían de este modo:

Nombre del archivo: registro.php

<?php

include_once('configuracion.php');

if ( ($_POST['testigo']) != "valido") {$error = true;}

function limpia($cadena)
    {
    return (ereg_replace('[^A-Za-z0-9_-ñÑ]', '', $cadena));
    }

function saltear ()
{
$cadena = md5 (uniqid(rand(), true));
return substr($cadena, 0, 3);
}

// definimos las variables
$nick     = limpia($_POST['nick'])     ;
$email    = $_POST['email']            ;
$password = limpia($_POST['password']) ;
$password2= limpia($_POST['password2']);

// marcamos el error
if ( empty($nick) OR empty($email) OR empty($password) OR empty($password2) ) {$error = true;}
// si eliminando caráteres raros queda algún campo vacio, generamos un error y devolvemos al formulario
if ( !empty($error) )
{
   header("Refresh: 2; URL=formulario.php");
   echo "<p>ERROR. Los datos introducidos no tienen un formato correcto.</p>";
   die;
}

function check_email_address($email) 
{
    // Primero, checamos que solo haya un símbolo @, y que los largos sean correctos
  if (!ereg("^[^@]{1,64}@[^@]{1,255}$", $email)) 
    {
        // correo inválido por número incorrecto de caracteres en una parte, o número incorrecto de símbolos @
    return false;
  }
  // se divide en partes para hacerlo más sencillo
  $email_array = explode("@", $email);
  $local_array = explode(".", $email_array[0]);
  for ($i = 0; $i < sizeof($local_array); $i++) 
    {
    if (!ereg("^(([A-Za-z0-9!#$%&'*+/=?^_`{|}~-][A-Za-z0-9!#$%&'*+/=?^_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$", $local_array[$i])) 
        {
      return false;
    }
  } 
  // se revisa si el dominio es una IP. Si no, debe ser un nombre de dominio válido
    if (!ereg("^\[?[0-9\.]+\]?$", $email_array[1])) 
    { 
     $domain_array = explode(".", $email_array[1]);
     if (sizeof($domain_array) < 2) 
         {
        return false; // No son suficientes partes o secciones para se un dominio
     }
     for ($i = 0; $i < sizeof($domain_array); $i++) 
         {
        if (!ereg("^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$", $domain_array[$i])) 
                {
           return false;
        }
     }
  }
  return true;
}

if ( check_email_address($email)==false )
{
   header("Refresh: 5; URL=formulario.php");
   echo "<p>ERROR. El Email $email no parece ser real.</p>";
   die;
}

//Comprobamos que la password y password2 son iguales, sino, volvemos a registro.php
if($password != $password2)
    {
    echo 'ERROR. Las passwords no coinciden';
    }
else
    {
    //cuantos usuarios hay ya con ese nick?
    $cuantosnicks=mysql_query("SELECT nick FROM ".$tabla." WHERE nick='".$nick."' ");
    //cuantos usuarios hay ya con ese email?
    $cuantosemails=mysql_query("SELECT email FROM ".$tabla." WHERE email='".$email."' ");
    $nickrepetido = mysql_num_rows($cuantosnicks);
    $emailrepetido = mysql_num_rows($cuantosemails);
    if($nickrepetido >= 1)
        {
        echo 'El nick propuesto ya estaba registrado. Escoge otro.';
        // damos la opcion de registrarse de nuevo
        echo '<br /><a href="formulario.php">Quieres intentarlo con otro nick?</a>'; 
        }
    else if($emailrepetido >= 1)
        {
        echo 'El email propuesto ya estaba registrado. Escoge otro.';
        // damos la opcion de registrarse de nuevo
        echo '<br /><a href="formulario.php">Quieres intentarlo con otro email?</a>';
        }
    else
        {
        //introducimos el nuevo registro en la tabla users
        $hasheada= hash('sha256', $password) ;
        $salteada = saltear();
        $hasheada = hash('sha256', $salteada . $hasheada);
        mysql_query("INSERT INTO ".$tabla." (nick,password,salteada,email) values ('$nick','$hasheada','$salteada','$email') ");  
        echo "<br /><p>Usuario registrado con éxito con Nick=$nick, Contraseña=$password y email=$email</p>";
        // damos la opcion de logerase
        echo '<br /><a href="login.php">Quieres logearte?</a>';
        }
    mysql_free_result($cuantosnicks);  //liberamos memoria
    mysql_free_result($cuantosemails); //liberamos memoria
   }
?> 



Nombre del archivo: chequeo.php

<?php

include_once('configuracion.php');

// Chequeamos que el usuario existe y que la contraseña es correcta
$nick= $_POST['nick']; // cazamos el valor de nick del formulario
$password = $_POST['password']; // cazamos el valor de la contraseña del formulario
//sacamos los valores correspondientes a ese nick de las variables $password (que está hasheada) y $salteada
$consulta= mysql_query("SELECT * FROM ".$tabla." WHERE nick='".$nick."' ");
if(mysql_num_rows($consulta) < 1) //si no existe el usuario...
{
   header("Refresh: 4; URL=login.php");
   echo "<p>ERROR. El nick dado no existe.</p>";
   die;
}
$datosdelusuario= mysql_fetch_array($consulta, MYSQL_ASSOC);
$hash = hash('sha256', $datosdelusuario['salteada'] . hash('sha256', $password) );  //aplicamos el algoritmo a la contraseña dada por el usuario en el login para poder compararla con la que tenemos guardada
if($hash != $datosdelusuario['password']) //si la contraseña es incorrecta...
{
   header("Refresh: 4; URL=login.php");
   echo "<br /><p>ERROR. La contraseña no es válida.</p>";
   die;
}
// login correcto, continuamos... 

session_register("usuario"); //registramos la variable usuario que contendrá el nick del user
session_register("idusuario"); //registramos la variable idusuario que contendrá la id del user
$_SESSION[usuario] = $datosdelusuario["nick"]; //damos el nick a la variable usuario
$_SESSION[idusuario] = $datosdelusuario["id"]; //damos la id del user a la variable idusuario
header("Refresh: 4; URL=login.php");
echo '<br /><p>Logeado compañero'.$_SESSION[usuario].'</p>';

?> 
Jorgens, qué te parece usar md5 en vez de hash?

$password = $_POST[password];
$password = strip_tags($password);
$password = md5($password);
Yo siempre uso ese código en los formularios con contraseña
Interesante aporte sobre encriptación en php; Quizás sea interesante ampliar la info:

Como bien dice Jorge la seguridad es importante tanto que en php podemos utilizar las funciones criptográficas hash (basadas en algoritmos de la familia SHA-2 http://es.wikipedia.org/wiki/Secure_Hash_Algorithm ); pero siempre es bueno saber lo que estos algoritmos son fiables por eso es mejor leer esto:

http://es.php.net/manual/es/faq.passwords.php#faq.passwords.fasthash

antes de dar por hecho que nuestro codigo no nos dará problemas.
Doodleo, uso como ves MD5, pero solo para obtener el SALT, de tres carácteres.


Olvidad el texto de acá abajo, pues el problema lo he solucionado y he corregido ya el código de las partes correspondientes.

Mientras leo eso, solo comentar que no me funciona aún. Demasiado código para no liarse un poco, je je je.
En el archivo chequeo.php comparo si son iguales o no la variable
$hash = hash('sha256', $datosdelusuario['salteada'] . hash('sha256', $password) );
(la que indica el usuario que se quiere logear)
y la variable
$hash != $datosdelusuario['password']
(la que guarda la tabla).
Seguramente no hago bien la transformación y no coinciden los valores. Seguiré mirando dónde está el error y leeré lo que me comentais. Cualquier ayuda para arreglar este fallo será bienvenida!

Gracias!
Lantiga, muy interesante el enlace que nos mandas, según el cuál deduzco que sería mejor usar la encriptación llamada Blowfish acompañada del SALT en lugar de mi primera propuesta, HASH con SALT, no?

Esto de la seguridad de aplicaciones es todo un munto aparte, je je je. Da para toda una web! A ver si conseguimos sacar algo en claro que sirva a todos los colegas de CCTW pues por lo que veo, no es fácil encontrar explicaciones bien claras ni aplicaciones terminadas y que funciones teniendo a la vez todos los requisitos y características de mejora que pretendemos alcanzar. Esto promete ser un buen proyecto educativo, verdad?

Un abrazo!
Creo que te estas equivocando Jorge blowfish se utiliza para el tema de analisis de encriptación y creo que no es lo que quieres; como te he puesto antes es mejor utilizar una función crypt() en vez de una función hash pues tienes la ventaja aparte de que te generará el salt automaticamente y que puedes utilizar varias linea de condición para establecer tantos hash`s como veas convenientes.
Una cosa más Jorge estaria bien que simplificaras el codigo para que no te lies y lo tengas claro ; si lo que te interesa es ver si la fase de encriptación funciona; abarca solo eso trozo de codigo y si necesitas comprobación con un echo te basta; una vez tengas claro que eso funciona comentas el echo temparalmente y haces las solicitudes a la base de datos y demas sino te vas a perder tu solo.
Si si, claro. Eso es lo que hago pues si no, no puedo saber qué parte falla. Concretamente en ese fallo que comenté, coloqué echos para imprimir incluso los valores de ambas variables, las que se comparan para ver si el login es correcto, es decir, la contraseña proporcionada por el usuario que pretende logearse, ya encriptada, y la que se guarda en la tabla y, no coinciden. Es por eso que se que me he debido liar en la forma de encriptar la proporcionada por el usuario al intentar logearse, pero aún no he dado con el error.

Gracias en cualquier caso por el consejo, vendrá muy bien también al resto saber esas cosas.

Sigo!

Fragmento del código con los echos colocados para detectar el fallo:

[QUOTE]$datosdelusuario= mysql_fetch_array($consulta, MYSQL_ASSOC);
$hash = hash('sha256', $datosdelusuario['salteada'] . hash('sha256', $password) ); //aplicamos el algoritmo a la contraseña dada por el usuario en el login para poder compararla con la que tenemos guardada
if($hash != $datosdelusuario['password']) //si la contraseña es incorrecta...
{
header("Refresh: 13; URL=login.php");
echo "<br /><p>ERROR. La contraseña no es válida.</p>";
echo "<br /><p>hash = $hash.</p>";
echo "<br /><p>datosdelusuario = $datosdelusuario.</p>";
die;
}[/QUOTE]


Datos obtenidos al intentar logearme, es decir, lo que me muestra la pantalla:

ERROR. La contraseña no es válida.

hash = 1cb43b9ceb33ee6c2bbdfc2152df6ba5790608e550fbcb6fb2bbe20f34b98a99.
datosdelusuario = 873467642674d52b853341382dfb16570f4c3fb2f10641fcf0660fa6524aa591.


Puse un Refresh: 13 para que me diera tiempo a ver el resultado, je je je.
Una ultima pregunta Jorge como estas haciendo las pruebas en local o online?
Si escribo en gris es porque esos comentarios han quedado fuera de lugar. El problema que comento en esos textos lo he podido arreglar y he corregido los códigos en los mensajes de modo que esos comentarios no tienen ya sentido.

Dejadme que piense en voz alta.... je je je, rara cosa cuando en lugar de pensar escribo, je je je. Es que me tengo que marchar a grabar unas cosas con mi grupo y no quiero perder el hilo de lo que llevaba entre manos. Lo esribo y así sigo luego mejor.

El valor que se añade a la tabla cuando un usuario se registra es este:

(En rojo señalo la contraseña proporcionada por el usuario que pretende logearse. En azul la que está guardada en la tabla.)

function saltear ()
{
$cadena = md5 (uniqid(rand(), true));
return substr($cadena, 0, 3);
}
$salteada = saltear();
$hasheada = hash('sha256', $salteada . $hasheada);
mysql_query("INSERT INTO $tabla (nick,password,salteada,email) values ('$nick','$hasheada','$salteada','$email')

Es decir, el campo password toma ese valor que tiene la variable $hasheada.

Por otro lado, la contraseña que introduce un usuario al intentar logearse, viene de:

$password = $_POST['password']; // cazamos el valor de la contraseña del formulario
//sacamos los valores correspondientes a ese nick de las variables $password (que está hasheada) y $salteada
$consulta= mysql_query("SELECT password, salteada FROM ".$tabla." WHERE nick='".$nick."' ");
$datosdelusuario= mysql_fetch_array($consulta, MYSQL_ASSOC);
$hash = hash('sha256', $datosdelusuario['salteada'] . hash('sha256', $password) );
if($hash != $datosdelusuario['password']) //si la contraseña es incorrecta...
{
header("Refresh: 13; URL=login.php");
echo "<br /><p>ERROR. La contraseña no es válida.</p>";
echo "<br /><p>hash = $hash.</p>";
echo "<br /><p>datosdelusuario = $datosdelusuario.</p>";
die;
}

Luego lo sigo analizando.
mmm.... antes de irme creo que el fallo está en

$hasheada = hash('sha256', $salteada . $hasheada);

Quizás antes debería haber algo así como esto:

$hasheada= hash('sha256', $password) ;

Lo pienso luego un poco más.
prueba a usar un editor en local con simulador de php algo tipo aptana te ayudara en la depuración del codigo.
Ok, tomo nota. Nunca he usado editores para escribir códigos, y si que pueden ayudar a detectar cosas raras ahora que lo dices. Lo pruebo en un ratito, gracias.
Efectivamente, el fallo era que me faltaba $hasheada= hash('sha256', $password) ; en el archivo registro.php.

Ahora todo funciona correctamente y de un modo más seguro que al principio. Parece que este nivel de seguridad puede aumentarse aún siguiendo los consejos de los compañeros, aunque por el momento no he sacado en claro cómo implantarlo. Lo dejo pendiente de investigarlo mejor.
[TUTORIAL :: SISTEMA DE REGISTRO DE USUARIOS :: PARTE 19]

Os acordáis que en el formulario de registro de formulario.php habíamos añadido un campo oculto para verificar luego que los datos que llegan al archivo registro.php provenian solo de ese formulario y no de otro lugar? Era la línea:

<input type="hidden" name="testigo" value="valido" />

Vamos ahora a aplicar lo mismo en el formulario que hay en el archivo login.php, pues lo tenemos desprotegido en ese sentido, oki? Basta con abrir el archivo y colocarle la línea siguiente:

<input type="hidden" name="testigo[B]2[/B]" value="valido" />

Además le añadimos a cada campo un tamaño máximo, para no darle facilidades al que quiera inyectarnos código maligno. Lo hacemos con la etiqueta maxlenght, quedando el archivo así:

Nombre del archivo: login.php

<?php

include_once('configuracion.php');

if(!isset($_SESSION[usuario]) )  // Esto es, si no está ya logeado
// Si aún no está logeado le mostramos el formulario de login
{
// Formulario para recoger los datos de login y enviarlos a chequeo.php, donde miramos que sean correctos
echo '
<form action="chequeo.php" method="POST">
  Nick:     <input type="text"     name="nick"     maxlength="25" size="25" /><br />
  Password: <input type="password" name="password" maxlength="40" size="40" /><br />
            <input type="hidden"   name="testigo2" value="valido" />
            <input type="submit"   name="submit"   value="Login" />
</form>
    ';
// le damos la opcion de registrarse si no lo está
echo '<br /><a href="formulario.php">Registro</a>'; 
}
else   // Esto es, si está ya logeado
{
// Si está logeado indicamos que no es necesario logearse de nuevo. Le mostramos el nick y la opción de deslogearse
echo 'Bienvenido a la web <b>'.$_SESSION[usuario].'</b>'; // Mensaje de bienvenida
echo '<br /><a href=logout.php>Salir</a>'; //link para deslogearse, iría a logout.php
}

?>

La variable testigo2 irá a para entonces al archivo chequeo.php con el valor valido. Ahora modificamos el archivo chequeo.php para que aborte el login en el caso de que esa variable no tenga ese valor. Quedaría así ese archivo:

Nombre del archivo: chequeo.php

<?php

include_once('configuracion.php');

if ( ($_POST['testigo2']) != "valido")
{
   header("Refresh: 4; URL=registro.php");
   echo "<br /><p>ERROR.</p>";
   die;
}

// Chequeamos que el usuario existe y que la contraseña es correcta
$nick= $_POST['nick']; // cazamos el valor de nick del formulario
$password = $_POST['password']; // cazamos el valor de la contraseña del formulario
//sacamos los valores  de las variables $password (que está hasheada) y $salteada correspondientes a ese nick
$consulta= mysql_query("SELECT * FROM ".$tabla." WHERE nick='".$nick."' ");
if(mysql_num_rows($consulta) < 1) //si no existe el usuario...
{
   header("Refresh: 4; URL=login.php");
   echo "<p>ERROR. El nick dado no existe.</p>";
   die;
}
$datosdelusuario= mysql_fetch_array($consulta, MYSQL_ASSOC);
//aplicamos el algoritmo a la contraseña dada por el usuario en el login para poder
//compararla con la que tenemos guardada
$hash = hash('sha256', $datosdelusuario['salteada'] . hash('sha256', $password) );
// coinciden?
if($hash != $datosdelusuario['password']) //si la contraseña es incorrecta...
{
   header("Refresh: 4; URL=login.php");
   echo "<br /><p>ERROR. La contraseña no es válida.</p>";
   die;
}
// login correcto, continuamos... 

session_register("usuario"); //registramos la variable usuario que contendrá el nick del user
session_register("idusuario"); //registramos la variable idusuario que contendrá la id del user
$_SESSION[usuario] = $datosdelusuario["nick"]; //damos el nick a la variable usuario
$_SESSION[idusuario] = $datosdelusuario["id"]; //damos la id del user a la variable idusuario
header("Refresh: 4; URL=login.php");
echo '<br /><p>Logeado compañero'." $_SESSION[usuario]".'.</p>';

;?>
Hey, una consulta... pensando en esto de las contraseñas y en los métodos de fuerza bruta y tal... no aumentaría esponencialmente la seguridad de una aplicación si hago que el usuario deba registrar dos contraseñas y a la vez, le obligo a poner esas dos contraseñas al logearse? si, se que sería un tostón para el usuario pero, no sería prácticamente 99.9% eficaz? Ambas con métodos de encriptación, claro. [:?]
De verdad, sería la primera vez que en un registro haya que poner dos contraseñas jajaja