En mi anterior post hablaba de cómo construir un programa "clon" de la utilidad de línea de comandos Windows RunAs. En esta primera parte vimos cómo se lanzaba un proceso suplantando a un usuario, que era el objetivo principal de la aplicación, si bien es la parte más fácil.

La tarea que nos quedó pendiente de ver es la más complicada y consiste en solicitar al usuario la clave de manera segura, sin que llegue a estar almacenada en claro en la memoria siquiera. Para ello usamos la clase SecureString, que apareció para estos menesteres en la versión 2.0 del framework.

Sin más vueltas vamos a ver directamente cómo se solicita dicha clave y pasamos a explicar los puntos de interés:

        private static SecureString PideClave(string nomUsuario)
        {
            Console.WriteLine("Escriba la contraseña para {0}:", nomUsuario);

            int top, left;
            ConsoleKeyInfo cki;
            SecureString clave = new SecureString();

            top = Console.CursorTop;
            left = Console.CursorLeft;

            bool ModoEntrada = true;
            while (ModoEntrada)
            {
                cki = Console.ReadKey(true);

                switch(cki.Key)
                {
                    //Si se pulsa ESC salimos del bucle
                    case ConsoleKey.Escape:
                        ModoEntrada = false;
                        clave = null;   //devolvemos un null para indicar que el programa debe terminar
                        break;

                    //Con ENTER terminamos la entrada de datos
                    case ConsoleKey.Enter:
                        ModoEntrada = false;
                        break;

                    //Si se pulsa la tecla de borrado hacia atrás hay que "currarse" el eliminar el caracter
                    //y que se vea en la pantalla eso
                    case ConsoleKey.Backspace:
                        if (clave.Length > 0)
                        {
                            Console.SetCursorPosition(left + clave.Length - 1, top);
                            Console.Write(" ");
                            Console.SetCursorPosition(left + clave.Length - 1, top);
                            clave.RemoveAt(clave.Length - 1);
                        }
                        break;
                    
                    //Cualquier otra tecla
                    default:
                        if (Char.IsLetterOrDigit(cki.KeyChar) || cki.KeyChar == '_')
                        {
                            clave.AppendChar(cki.KeyChar);
                            //Se sustituye por un asterisco
                            Console.SetCursorPosition(left + clave.Length - 1, top);
                            Console.Write('*');
                        }
                        break;
                }
            }
            Console.Write("\n");
            return clave;
        }

Básicamente lo que se hace es ir solicitando las letras de la clave una a una, capturando las pulsaciones de las teclas con el método ReadKey de la clase Console. Este método toma un parámetro opcional que indica si se debe mostrar el caracter pulsado por pantalla o no. En este caso le indicamos que debe interceptar la pulsación de la tecla y no mostrarla por pantalla (valor true en el parámetro del método). Lo que hacemos posteriormente es escribir un asterisco. De este modo damos información visual sobre cuántos caracteres se han introducido, pero no los mostramos ni permitimos que se almacenen en lado alguno, sino que los metemos dentro de la cadena segura con su método AppendChar.

También capturamos algunas teclas especiales que se pueden pulsar:

  • ESC: con esto devolvemos un nulo que se comprueba en el código llamante parando la ejecución del programa.
  • ENTER: finalizamos la introducción de datos dando por válida la contraseña introducida hasta el momento.
  • BACKSPACE: la tecla de borrado (con la flechita a la izquierda), que sirve para corregir el último caracter.

Listo. con esto pedimos la clave al usuario sin comprometer la seguridad haciéndola pasar por una cadena normal. Éstas son objetos inmutables que permanecen en memoeria mientras no las reclame el recolector de basura y aunque las "sobrescribamos" (es un decir, por que lo que conseguiríamos es sólo una nueva cadena, ya que no se destruyen al asignarle otro valor a la misma variable, como se comenta en ese post antiguo que referencio).

Exactamente el mismo principio se podría aplicar para pedir de manera segura una clave en una aplicación Windows (capturando letra a letra y sin dejar que éstas se reflejen luego en la interfaz de usuario o en alguna cadena).

También os dejo, como prometí, el código completo del programa RunAsClon (20 KB) para que el que quiera le pueda echar un vistazo. He usado regiones condicionales para laznar el programa de manera cómoda para depurar mientras estamos en modo de desarrollo (debug), pero es lo único a mayores que tiene el código. Lo he hecho con Visual Studio 2008 pero funcionará perfectamente con Visual Studio 2005 también si importas el .cs. He incluido el resultado de la compilación tanto en depuración como en "release" por si alguien lo quiere utilizar directamente sin tener que compilarlo.

Si alguien quiere crear su propio programa RunAs que se ejecute sin pedir la clave al usuario, sino incluyéndola como parte de la lñinea de comandos (no recomendable pero útil en muchos casos) puede tocar ligeramente este código para conseguirlo sin problema.

¡Espero qué te resulte útil!

Escrito por un humano, no por una IA