Blog alskare

.Net y lo que surja

Archive for octubre 2009

C#. Generador de Passwords aleatorios

Posted by alskare en 13/10/2009

Estoy más que seguro que cualquiera de nosotros hemos sido usuarios en cualquier ocasión de algún tipo de herramienta similar a la que intento crear en estos momentos. De hecho, con una búsqueda bastante genérica en Google surgen multitud de rutinas en cualquier lenguaje. No obstante, como soy de aquellas personas que prefiere crear sus propias rutinas antes de hacer un “Copia y Pega” de lo que han hecho otros, me dispongo a la creación de una utilidad que me permita obtener contraseñas de una manera aleatoria.

¿Qué necesito?

En principio, sólo necesito una rutina que me permita generar un password de 8 caracteres, y que incluya, al menos, un número, un carácter no alfanumérico y algún carácter en mayúscula. Podemos apreciar enseguida que, aunque la necesidad inminente es bastante sencilla, lo mejor es poder parametrizar estos elementos a los que podemos llamar “Caracteres especiales”:

  • Números
  • Mayúsculas
  • Símbolos

Debo reconocer que, a la hora de parametrizar valores, soy un amante de los porcentajes y, en este principio, baso muchas de las rutinas creadas. Es decir, si genero una rutina que me permita crear una contraseña de 10 caracteres, prefiero decir que tenga un 20% de números, un 20% de mayúsculas y otro 20% de símbolos para que la contraseña tenga: 2 números + 2 letras mayúsculas + 2 símbolos + 4 caracteres normales en minúscula. De esta manera, si tengo que ampliar la longitud de la contraseña a 20 caracteres, automáticamente cambiará el número de caracteres especiales, manteniendo la proporción.

Otro de los puntos a tener en cuenta es que los caracteres que pueden emplearse no siempre serán válidos en todos los entornos, por tanto, aunque no me he preocupado de parametrizar este punto, sí que he tenido la precaución de “separarlo” por si alguien tiene que añadir/eliminar algún carácter no compatible, por ejemplo, con un determinado servidor de correo.

string caracteres = "abcdefghijklmnopqrstuvwxyz";
string numeros = "0123456789";
string simbolos = "%$#@+-=&";

¿Cómo enfoco el resultado?

En mi caso, con contraseñas de 8 caracteres suelo pasar. De hecho, debo reconocer que la necesidad de la creación de una rutina que cree passwords automáticamente viene dada por una aplicación Web que tengo que crear en la que, cuando un usuario pierde una contraseña, se le enviará por e-correo y, la primera vez que entre, tendrá que cambiarla, así que prima más la “estética” de la contraseña que la seguridad. Lo que pasa es que, a la gente le gusta más ver símbolos raros que cualquier nombre.

Pues eso, que la idea inicial es la de crear un constructor estándar con 8 caracteres, de los cuales tendrá un 20% de caracteres especiales. Otro constructor que permita crear una contraseña con una longitud variable y otro que permita definir todo:

public GeneradorPassword()
{ }

public GeneradorPassword(int longitudCaracteres)
{ }

public GeneradorPassword(int longitudCaracteres, 
                         int porcentajeMayusculas, 
                         int porcentajeSimbolos, 
                         int porcentajeNumeros)
{ }

En cuanto al sistema de crear la rutina, no me complico tampoco la vida:

  1. Creo una cadena con la longitud de la contraseña todo en minúscula
  2. Obtengo el número de caracteres especiales a añadir
  3. Obtengo una posición aleatoria para colocar los caracteres especiales
  4. Reemplazo los caracteres especiales en las posiciones obtenidas.

El resultado

Dejo la rutina tal cuál me ha quedado:

using System;
using System.Collections.Generic;
using System.Text;

namespace jnSoftware.Utiles
{
    /// <summary>
    /// Clase que permite la generación de una contraseña. 
    /// La contraseña contiene un número de caracteres fijos y permite especificar el porcentaje
    /// de caracteres en mayúsculas y símbolos que se quieren obtener
    /// </summary>
    public class GeneradorPassword
    {


        /// <summary>
        /// Enumeración que permite conocer el tipo de juego de carácteres a emplear
        /// para cada carácter
        /// </summary>
        private enum TipoCaracterEnum { Minuscula, Mayuscula, Simbolo, Numero }

        #region Campos

        private int porcentajeMayusculas;
        private int porcentajeSimbolos;
        private int porcentajeNumeros;
        Random semilla;

        // Caracteres que pueden emplearse en la contraseña
        string caracteres = "abcdefghijklmnopqrstuvwxyz";
        string numeros = "0123456789";
        string simbolos = "%$#@+-=&";

        // Cadena que contiene el password generado
        private StringBuilder password;

        #endregion



        #region Propiedades

        /// <summary>
        /// Obtiene o establece la longitud en carácteres de la contraseña a obtener
        /// </summary>
        public int LongitudPassword { get; set; }

        /// <summary>
        /// Obtiene o establece el porcentaje de carácteres en mayúsculas que 
        /// contendrá la contraseña
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un valor que no coincida con un porcentaje</exception>
        public int PorcentajeMayusculas
        {
            get { return porcentajeMayusculas; }
            set
            {
                if (value < 0 || value > 100)
                    throw new ArgumentOutOfRangeException("El porcentaje es un número entre 0 y 100");
                porcentajeMayusculas = value;
            }
        }


        /// <summary>
        /// Obtiene o establece el porcentaje de símbolos que contendrá la contraseña
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un valor que no coincida con un porcentaje</exception>
        public int PorcentajeSimbolos
        {
            get { return porcentajeSimbolos; }
            set
            {
                if (value < 0 || value > 100)
                    throw new ArgumentOutOfRangeException("El porcentaje es un número entre 0 y 100");
                porcentajeSimbolos = value;
            }
        }

        /// <summary>
        /// Obtiene o establece el número de caracteres numéricos que contendrá la contraseña
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un valor que no coincida con un porcentaje</exception>
        public int PorcentajeNumeros
        {
            get { return porcentajeNumeros; }
            set
            {
                if (value < 0 || value > 100)
                    throw new ArgumentOutOfRangeException("El porcentaje es un número entre 0 y 100");
                porcentajeNumeros = value;
            }
        }

        #endregion


        #region Constructores
        /// <summary>
        /// Constructor. La contraseña tendrá 8 caracteres, incluyendo una letra mayúscula, 
        /// un número y un símbolo
        /// </summary>
        public GeneradorPassword()
            : this(8)
        { }


        /// <summary>
        /// Constructor. La contraseña tendrá un 20% de caracteres en mayúsculas y otro tanto de 
        /// símbolos
        /// </summary>
        /// <param name="longitudCaracteres">Longitud en carácteres de la contraseña a obtener</param>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un porcentaje de caracteres especiales mayor de 100</exception>
        public GeneradorPassword(int longitudCaracteres)
            : this(longitudCaracteres, 20, 20, 20)
        { }


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="longitudCaracteres">Longitud en carácteres de la contraseña a obtener</param>
        /// <param name="porcentajeMayusculas">Porcentaje a aplicar de caracteres en mayúscula</param>
        /// <param name="porcentajeSimbolos">Porcenta a aplicar de símbolos</param>
        /// <param name="porcentajeNumeros">Porcentaje de caracteres numéricos</param>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un porcentaje de caracteres especiales mayor de 100</exception>
        public GeneradorPassword(int longitudCaracteres, int porcentajeMayusculas, int porcentajeSimbolos, int porcentajeNumeros)
        {
            LongitudPassword = longitudCaracteres;
            PorcentajeMayusculas = porcentajeMayusculas;
            PorcentajeSimbolos = porcentajeSimbolos;
            PorcentajeNumeros = porcentajeNumeros;

            if (PorcentajeMayusculas + porcentajeSimbolos + PorcentajeNumeros > 100)
                throw new ArgumentOutOfRangeException(
                "La suma de los porcentajes de caracteres especiales no puede superar el " +
                "100%, es decir, no puede ser superior a la longitud de la contraseña");
            semilla = new Random(DateTime.Now.Millisecond);
        }

        #endregion


        #region Métodos públicos

        /// <summary>
        /// Obtiene el password
        /// </summary>
        /// <returns></returns>
        public string GetNewPassword()
        {
            GeneraPassword();
            return password.ToString();
        }


        /// <summary>
        /// Permite establecer el número de caracteres especiales que se quieren obtener
        /// </summary>
        /// <param name="numeroCaracteresMayuscula">Número de caracteres en mayúscula</param>
        /// <param name="numeroCaracteresNumericos">Número de caracteres numéricos</param>
        /// <param name="numeroCaracteresSimbolos">Número de caracteres de símbolos</param>
        public void SetCaracteresEspeciales(
            int numeroCaracteresMayuscula
            , int numeroCaracteresNumericos
            , int numeroCaracteresSimbolos)
        {
            // Comprobación de errores
            if (numeroCaracteresMayuscula
                    + numeroCaracteresNumericos
                    + numeroCaracteresSimbolos > LongitudPassword)
                throw new ArgumentOutOfRangeException(
                    "El número de caracteres especiales no puede superar la longitud del password");

            PorcentajeMayusculas = numeroCaracteresMayuscula * 100 / LongitudPassword;
            PorcentajeNumeros = numeroCaracteresNumericos * 100 / LongitudPassword;
            PorcentajeSimbolos = numeroCaracteresSimbolos * 100 / LongitudPassword;
        }



        /// <summary>
        /// Constructor. La contraseña tendrá 8 caracteres, incluyendo una letra mayúscula, 
        /// un número y un símbolo
        /// </summary>
        public static string GetPassword()
        {
            // Se crea un método estático para facilitar el uso
            GeneradorPassword gp = new GeneradorPassword();
            return gp.GetNewPassword();
        }


        #endregion


        #region Métodos de cálculo

        /// <summary>
        /// Método que genera el password. Primero crea una cadena de caracteres 
        /// en minúscula y va sustituyendo los caracteres especiales
        /// </summary>
        private void GeneraPassword()
        {
            // Se genera una cadena de caracteres en minúscula con la longitud del 
            // password seleccionado
            password = new StringBuilder(LongitudPassword);
            for (int i = 0; i < LongitudPassword; i++)
            {
                password.Append(GetCaracterAleatorio(TipoCaracterEnum.Minuscula));
            }

            // Se obtiene el número de caracteres especiales (Mayúsculas y caracteres) 
            int numMayusculas = (int)(LongitudPassword * (PorcentajeMayusculas / 100d));
            int numSimbolos = (int)(LongitudPassword * (PorcentajeSimbolos / 100d));
            int numNumeros = (int)(LongitudPassword * (PorcentajeNumeros / 100d));

            // Se obtienen las posiciones en las que irán los caracteres especiales
            int[] caracteresEspeciales =
                    GetPosicionesCaracteresEspeciales(numMayusculas + numSimbolos + numNumeros);
            int posicionInicial = 0;
            int posicionFinal = 0;

            // Se reemplazan las mayúsculas
            posicionFinal += numMayusculas;
            ReemplazaCaracteresEspeciales(caracteresEspeciales,
                 posicionInicial, posicionFinal, TipoCaracterEnum.Mayuscula);

            // Se reemplazan los símbolos
            posicionInicial = posicionFinal;
            posicionFinal += numSimbolos;
            ReemplazaCaracteresEspeciales(caracteresEspeciales,
                 posicionInicial, posicionFinal, TipoCaracterEnum.Simbolo);

            // Se reemplazan los Números
            posicionInicial = posicionFinal;
            posicionFinal += numNumeros;
            ReemplazaCaracteresEspeciales(caracteresEspeciales,
                 posicionInicial, posicionFinal, TipoCaracterEnum.Numero);
        }



        /// <summary>
        /// Reemplaza un caracter especial en la cadena Password
        /// </summary>
        private void ReemplazaCaracteresEspeciales(
                                        int[] posiciones
                                        , int posicionInicial
                                        , int posicionFinal
                                        , TipoCaracterEnum tipoCaracter)
        {
            for (int i = posicionInicial; i < posicionFinal; i++)
            {
                password[posiciones[i]] = GetCaracterAleatorio(tipoCaracter);
            }
        }



        /// <summary>
        /// Obtiene un array con las posiciones en las que deberán colocarse los caracteres
        /// especiales (Mayúsculas o Símbolos). Es importante que no se repitan los números
        /// de posición para poder mantener el porcentaje de dichos carácteres
        /// </summary>
        /// <param name="numeroPosiciones">Valor que representa el número de posiciones
        /// que deberán crearse sin repetir</param>
        private int[] GetPosicionesCaracteresEspeciales(int numeroPosiciones)
        {
            List<int> lista = new List<int>();
            while (lista.Count < numeroPosiciones)
            {
                int posicion = semilla.Next(0, LongitudPassword);
                if (!lista.Contains(posicion))
                {
                    lista.Add(posicion);
                }
            }
            return lista.ToArray();
        }


        /// <summary>
        /// Obtiene un carácter aleatorio en base a la "matriz" del tipo de caracteres
        /// </summary>
        private char GetCaracterAleatorio(TipoCaracterEnum tipoCaracter)
        {
            string juegoCaracteres;
            switch (tipoCaracter)
            {
                case TipoCaracterEnum.Mayuscula:
                    juegoCaracteres = caracteres.ToUpper();
                    break;
                case TipoCaracterEnum.Minuscula:
                    juegoCaracteres = caracteres.ToLower();
                    break;
                case TipoCaracterEnum.Numero:
                    juegoCaracteres = numeros;
                    break;
                default:
                    juegoCaracteres = simbolos;
                    break;
            }

            // índice máximo de la matriz char de caracteres
            int longitudJuegoCaracteres = juegoCaracteres.Length;

            // Obtención de un número aletorio para obtener la posición del carácter
            int numeroAleatorio = semilla.Next(0, longitudJuegoCaracteres);

            // Se devuelve una posición obtenida aleatoriamente
            return juegoCaracteres[numeroAleatorio];
        }

        #endregion

    }
}

 

La rutina puede probarse gracias a los amigos de velasco.biz: Probar rutina

Posted in C#, Helpers, NetFramework | 3 Comments »