Una idea de como tratar la seguridad en nuestros sistemas cuando usamos SQLServer:
Escrito por Takeichi Kanzaki Cabrera
Uno de los módulos más difícil de implementar de un software generalmente es la seguridad del mismo, en la búsqueda de diversas formas de programar esta se nos ocurrió la idea de crear una tabla dentro de la base de datos que recogiera la información de registro del mismo (nombre del usuario, nombre con el cual entraría al sistema, contraseña, nivel de acceso) y crear una conexión al sistema de base de datos con un usuario y contraseña que le fueran desconocidos al usuario final de nuestro sistema, de manera tal que el mismo sólo pudiera establecer una conexión con los datos por medio de nuestro software.
Una vez conformada esta parte, nos dimos a la tarea de buscar un algoritmo de encriptamiento para no guardar la contraseña del usuario dentro de la base de datos en texto plano, pues es sabido que muchos usuarios cometen el error de usar la misma palabra clave para todos los sistemas que la solicitan y si no la encriptáramos el administrador del servidor de base de datos podría conocer la misma con solo listar el contenido de la tabla que usáramos para almacenar estos datos.
En la búsqueda de un algoritmo de encriptamiento se nos ocurrieron unas cuantas ideas, pero la mayoría rondaba sobre la implementación de uno de los existentes en este campo, lo cual a mí personalmente no me satisfacía mucho pues programar uno de esto algoritmos en Transact-SQL no es una tarea graciosa, y en caso de programarlo dentro del software y no dentro del sistema de base de datos se nos presentaba el problema de que si necesitábamos implementar algo en otro lenguaje de programación teníamos que implementarlo nuevamente, aunque en un inicio pensamos que si lo implementábamos dentro de una dll nos sería suficiente para cualquier otro lenguaje pero la práctica nos demostró lo contrario ya que hicimos unas pruebas usando Visual Basic y Delphi (ambos en su versión 6) y no pudimos llamar a las funciones programadas en uno de ellos desde el otro.
Fue en este momento donde se nos ocurrió la idea de buscar como el SQLServer codifica la contraseña de los usuarios que agregamos sabiendo que el mismo para la gran mayoría de las funciones que realiza lo que hace por debajo es una llamada a alguno de los procedimientos almacenados que trae consigo. Como resultado de la búsqueda encontramos que el mismo cuenta con la función pwdencrypt que encripta una cadena de caracteres y la convierte en un binario de 256 bits, es decir, que podíamos contar con un algoritmo de encriptamiento de 256 bits ya probado sin ni siquiera tener que poner un línea de código, el problema estaba entonces que el mismo cuando encriptaba una contraseña dos veces esta no siempre generaba el mismo código binario, es decir, que dos llamadas a la función pwdencrypt con el mismo parámetro no producía siempre la misma salida, lo cual nos hizo pensar que debía existir una función que se usara para comparar el resultado de la llamada a la función pwdencrypt con la contraseña original, ahora la búsqueda nos era un poco más fácil pues ya teníamos una noción de por donde buscar, en esta ocasión encontramos la función pwdcompare cuya llamada era un poco más difícil pues precisaba de varios parámetros a diferencia de pwdencrypt que sólo requería la contraseña que se deseaba encriptar, pero lo peor de todo fue que no encontramos ningún tipo de documentación sobre estas funciones ni en la ayuda del SQLServer ni en Internet. A continuación presentamos un pequeño problema, el cual resolveremos usando Microsoft Visual Basic .NET, para mostrar el funcionamiento de ambas funciones.
A su empresa se le da la tarea de realizar un sistema de control automatizado para una compañía que tiene sucursales en cinco países diferentes. El mismo debe de ser capaz de detectar a partir de la información de inicio de sesión de un usuario a cual de las sucursales pertenece (o si pertenece a la dirección de la empresa) y según sea el caso será al módulo del sistema al que acceda, es decir, cada sucursal tiene una función específica y el sistema tendrá un módulo por cada sucursal, uno para la dirección de la compañía y un módulo de administración de usuarios. Después de realizada la etapa de análisis y diseño del sistema a usted se le da tarea de implementar el método de administración de usuarios, el cual debe de tener las siguientes características:
- Es el módulo de inicio del sistema y dependiendo de la información registro entrada por el usuario será el módulo del sistema al que accederá el mismo de manera directa.
- La información de registro de cada usuario va a estar almacenada dentro de una tabla de la base de datos con el nombre: tUsuariosInfo y debe de tener el Login que usará el usuario para entrar al sistema, la contraseña, el nombre completo del usuario y la sucursal a la cual pertenece (se incluyen la dirección general y el módulo de administración como una sucursal más), el diseño final de la tabla dentro de la base de datos corre por su cuenta y puede contener cualquier otra información que sea útil para el sistema o necesaria para el módulo que usted tiene que implementar. El diseño final que se decide implementar es el siguiente:
| Nombre |
Tipo |
Llave |
| Login |
NVarChar (20) |
X |
| Contraseña |
VarBinary (256) |
|
| NombreUsuario |
NVarChar (50) |
|
| IdSucursal |
Int |
|
- La interface gráfica del módulo será realizada por un diseñador. Para el acoplamiento de la misma, el código programado por usted debe de tener para el caso de registro de los usuarios una función llamada ValidarUsuario que recibirá como parámetros el nombre del usuario y la contraseña entrada por este, y debe de devolver un dato de tipo boolean especificando si es válida o no la información de registro.
Sobre la base del problema planteado y usando las especificaciones del módulo, empecemos a trabajar. Primeramente vamos a empezar por lo más fácil: la creación de la tabla dentro de la base de datos, la cual podemos generar con el siguiente código de Transact-SQL:
--Tabla con la Información de Registro del Usuario.--
CREATE TABLE tUsuariosInfo
(
Login NVarChar(20) NOT NULL,
Contrasena VarBinary(256) NOT NULL,
NombreUsuario NVarChar(50) NOT NULL,
IdSucursal Int NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE tUsuariosInfo ADD CONSTRAINT
PK_ tUsuariosInfo PRIMARY KEY CLUSTERED
(
Login
) ON [PRIMARY]
--****************************************--
OK, ya tenemos la tabla para guardar la información de registro del usuario, ahora empecemos con los procedimientos almacenados para agregar, modificar y validar usuarios (el de eliminar no lo vamos a implementar pues es bastante sencillo):
--Procedimiento almacenado para Agregar la Información de Registro de los Usuarios.
CREATE PROCEDURE spUsuariosIns
(@Login nvarchar(20),
@Contrasena nvarchar(50),
@Nombre nvarchar(50),
@IdSucursal int)
AS INSERT INTO tUsuariosInfo
(Login,
Contrasena,
NombreUsuario,
IdSucursal)
VALUES
(@Login,
pwdencrypt(@Contrasena),
@Nombre,
@IdSucursal)
GO
--****************************************--
--Procedimiento almacenado para Modificar la Información de Registro del Usuario.
CREATE PROCEDURE spUsuariosUpd
(
@Login nvarchar(20),
@Nombre nvarchar(50),
@Contrasena varbinary(256),
@IdSucursal int
)
AS UPDATE tUsuariosInfo
SET Nombre = @Nombre,
Contrasena = pwdencrypt(@Contrasena),
IdSucursal = @IdSucursal
WHERE
(Login = @Login)
GO
--****************************************--
--Procedimiento almacenado para Validar la Información de Registro del Usuario.
CREATE PROCEDURE spValidarUsuario
(
@Login nvarchar(20),
@Contrasena sysname
)
AS
DECLARE @pass sysname --contraseña del usuario en la base de datos
SELECT @pass=(SELECT Contrasena FROM tEstudiantes WHERE Login=@Login)
IF (pwdcompare(@Contrasena, @pass, 0)=1 )
SELECT
Login,
Nombre,
IdSucursal
FROM
tUsuariosInfo
WHERE
Login=@Login
GO
--****************************************--
No te preocupes si no has entendido mucho sobre lo que has visto hasta aquí, pues trataremos de explicar lo más significativo, sin entrar en detalles con el código SQL básico. En el procedimiento almacenado spUsuariosIns que usamos para agregar nuevos usuarios al sistema vemos por primera vez el uso de la función pwdencrypt, la cual como mencionamos anteriormente, toma como parámetro la contraseña en texto plano y nos devuelve la misma encriptada; fuera de este punto no hay nada más que llame la atención en el mismo pues lo demás es código SQL estándar que no nos proponemos detallar en este material. Lo mismo ocurre con el procedimiento spUsuariosUpd que usamos para actualizar la información de registro de los usuarios, aunque el mismo pudiera complicarse un poquito si para modificar esta información solicitáramos la contraseña actual del usuario, aunque bastaría con una llamada al procedimiento spValidarUsuario. En el procedimiento almacenado spValidarUsuario aparece la función pwdcompare, que como pudiste apreciar requiere de algunos parámetros: el primer parámetro es la contraseña que queremos verificar si es correcta, el segundo parámetro es la contraseña con la cual la queremos comparar y el tercero sería el método de encriptamiento usado para codificar la contraseña, este parámetro existe para tener lograr una compatibilidad con las versiones anteriores de SQLServer (como dijimos anteriormente este es el método de encriptamiento que usa el SQLServer para controlar el acceso a él) que usaban otro método de encriptamiento, en caso de que hallamos encriptado la misma usando pwdencrypt siempre le enviaremos un 0; la función pwdencrypt nos devuelve 1 si las dos contraseñas coinciden y un valor diferente a 1 en caso contrario; dentro del procedimiento almacenado verificamos si el valor de retorno es 1 y en este caso "cogemos" los datos del usuario que nos van a ser útiles dentro del sistema.
Hasta aquí llegaría la parte que tendríamos que implementar dentro del SQLServer, ahora veremos como podríamos hacer uso de esta usando Visual Basic .NET, partiremos de que tenemos una conexión establecida con el SQLServer por medio de una variable llamada cnnConnexión y de que tenemos dentro del sistema tres variables globales (sólo lo suponemos, no lo aconsejamos) para almacenar la información de registro del mismo, strLogin, strNombre y intIdSucursal.
'Función para validar los usuarios.
Public Function ValidarAcceso(ByVal strLogin As String, ByVal strContraseña As String) As Boolean
Dim cmdProc As SqlCommand
Dim dtrProc As SqlDataReader
cmdProc = New SqlCommand("spValidarUsuario", cnnConexión)
cmdProc.CommandType = CommandType.StoredProcedure
cmdProc.Parameters.Add(New SqlParameter("@Login", SqlDbType.NVarChar, 15))
cmdProc.Parameters("@Login").Value = strLogin
cmdProc.Parameters.Add(New SqlParameter("@Contraseña", SqlDbType.NVarChar, 50))
cmdProc.Parameters("@Password").Value = strContraseña
dtrProc = cmdProc.ExecuteReader
If dtrProc.Read Then
If Not dtrProc.IsDBNull(0) Then
strLogin = dtrProc.GetString(0)
strNombre = dtrProc.GetString(1)
intIdSucursal = dtrProc.GetInt32(0)
ValidarAcceso = True
Else
ValidarAcceso = False
End If
Else
ValidarAcceso = False
End If
dtrProc.Close()
End Function 'Procedimiento para agregar usuarios.
Public Sub AgregarUsuario(ByVal strLogin As String, ByVal strContraseña As
String, ByVal strNombre as String, ByVal intIdSucursal As Integer)
Dim cmdProc As SqlCommand
cmdProc = New SqlCommand("spValidarUsuario", cnnConexión)
cmdProc.CommandType = CommandType.StoredProcedure
cmdProc.Parameters.Add(New SqlParameter("@Login", SqlDbType.NVarChar, 15))
cmdProc.Parameters("@Login").Value = strLogin
cmdProc.Parameters.Add(New SqlParameter("@Contraseña", SqlDbType.NVarChar, 50))
cmdProc.Parameters("@Password").Value = strContraseña
cmdProc.Parameters.Add(New SqlParameter("@Nombre", SqlDbType.NVarChar, 50))
cmdProc.Parameters("@Nombre").Value = strNombre
cmdProc.Parameters.Add(New SqlParameter("@IdSucursal", SqlDbType.Int))
cmdProc.Parameters("@IdSucursal").Value = intIdSucursal Try
cmdProc.ExecuteNonReader
Catch Exp as SqlException
MsgBox(Exp.Message)
End Try
End Sub
Hasta aquí llegamos con nuestra idea, esperamos que les halla sido útil, cualquier comentario, idea o pregunta puede enviarlo a takeichi.kanzaki@uho.edu.cu o tkanzakic@hotmail.com
Sobre el autor:
Mi nombre es Takeichi Kanzaki Cabrera, tengo 22 años, vivo en Cuba. Actualmente soy estudiante de cuarto año de Ingeniería en Informática en la Universidad de Holguín, Cuba. Programo en Delphi, Visual Basic, Java, asp, ASP.NET, php,html, y nociones de LabView (sólo lo he usado para trabajos de la escuela). Entre los sistemas de base de datos Cliente/Servidor con los que he trabajado están SQLServer, Interbase y Firebird. Entre mis trabajos más está la confección de tres Intranets: para una corporación de turismo, una compañía de seguros y para una central termoeléctrica (asp, ASP.NET, Java y Delphi para sistemas auxiliares), además he trabajado en varios sistemas para la informatización de la universidad (Delphi y Visual Basic). Actualmente estoy al frente de un equipo de trabajo que se encuentra desarrollando la Intranet de la central termoeléctrica antes mencionada.