Encriptando columnas de una tabla

3 comentarios

Una pregunta que se repite bastante frecuentemente es la siguiente: ¿se pueden encriptar las columnas de una tabla en Firebird? Y si la respuesta es afirmativa ¿cómo hacerlo?

Bueno, esas preguntas tienen dos respuestas contradictorias: sí se puede y no se puede.

¿Por qué eso?

Porque se puede muy fácilmente encriptar el contenido de una columna usando la función HASH() pero no se puede conocer cual era el valor original.

Por ejemplo, si escribimos:

Listado 1.

SELECT
   HASH('WALTER')
FROM
   RDB$DATABASE
 Esto será lo que obtendremos:
ENCRIPTAR1
Captura 1. Si haces clic en la imagen la verás más grande

Pero viendo el resultado del hash (es decir, el número: 95.819.938) es imposible saber que el texto original era ‘WALTER’, simplemente hagas lo que hagas con el resultado de un hash no podrás obtener de regreso el texto original.

¿Y entonces, para qué te puede servir la función HASH() si es que no puedes obtener el texto original?

Para guardar contraseñas. Guardas en tu tabla el hash de la contraseña, luego cuando un usuario escribe su contraseña, le efectúas el hash a la contraseña que él escribió y comparas el hash de su contraseña con el hash que habías guardado en tu tabla. Si son idénticos podrás saber que es una contraseña legítima, o sea:

Contraseña legítima —> HASH() —> se guarda el resultado en una tabla

Contraseña escrita por el usuario —> HASH() —> se compara con la guardada en la tabla. Si son idénticas, está todo ok.

Este método es muy ventajoso porque como nunca se guarda la contraseña en la tabla sino el hash de la contraseña, jamás un curioso podrá averiguar cual es la contraseña legítima. Lo único que podría hacer es probar con millones y millones de contraseñas con la esperanza de que alguna de ellas sea la correcta y así los hash coincidan.

¿Pero y si se necesita desencriptar la columna para volver a tener el texto original?

Pues en ese caso lamentablemente el Firebird no te provee de alguna herramienta.

¿Y cuál es la solución, si necesito encriptar y desencriptar un texto?

Que la encriptación y la desencriptación la hagas en tu lenguaje de programación (Visual FoxPro, Visual Basic, Delphi, C, C++, Java, etc.) y no en Firebird. En tu Base de Datos solamente guardarás el texto encriptado, pero la desencriptación de ese texto la realizarás en tu lenguaje de programación.

¿Y no podría tener una UDF para encriptar y desencriptar texto?

Sí, claro que podrías, pero en ese caso la UDF será llamada desde dentro de tu Base de Datos y el curioso potencial podría saber como utilizarla para leer el texto original. O sea que no te servirá y además te dará una falsa sensación de seguridad, por lo tanto sería muy mala idea usarla.

Artículos relacionados:

La función HASH()

Un stored procedure para encriptar y desencriptar datos

El índice del blog Firebird21

El foro del blog Firebird21

Un stored procedure para encriptar y desencriptar datos

3 comentarios

Si queremos mantener algunos datos ocultos, si no queremos que sean legibles a simple vista entonces debemos encriptarlos.

Encriptar significa convertir un texto legible en un texto completamente ilegible. Técnicas para encriptar existen al menos desde la época del general romano Julio César; ahora tenemos la gran ventaja de que podemos usar computadoras consiguiendo así algoritmos mucho más poderosos y miles o millones de veces más rápidos que hacerlo manualmente.

Un stored procedure para encriptar y desencriptar datos

Este stored procedure te permitirá encriptar o desencriptar cualquier texto que desees, la única condición es que su longitud debe ser menor o igual a 32765 caracteres. Esa no es una limitación del algoritmo sino de la implementación del tipo de datos VARCHAR en Firebird que solamente nos permite tener como máximo esa cantidad de caracteres.

Pero 32765 caracteres es más que suficiente para la inmensa mayoría de los casos. Si alguna vez necesitas encriptar textos más largos entonces deberás modificar el stored procedure para usar el tipo de datos BLOB de texto en lugar de VARCHAR.

CREATE PROCEDURE ENCRIPTAR(
   tcTexto              VARCHAR(32765),
   tcAccion             CHAR(1),
   tcNumeroEncriptacion VARCHAR(255),
   tcNumeroRepeticion   VARCHAR(255))
RETURNS(
   ftcNuevoTexto VARCHAR(32765))
AS
   DECLARE VARIABLE lnI          SMALLINT;
   DECLARE VARIABLE lnJ          SMALLINT;
   DECLARE VARIABLE lnK          SMALLINT;
   DECLARE VARIABLE lcCaracter   CHAR(1);
   DECLARE VARIABLE lnAscii      SMALLINT;
   DECLARE VARIABLE lnValor1     SMALLINT;
   DECLARE VARIABLE lnValor2     SMALLINT;
   DECLARE VARIABLE lnNuevoAscii SMALLINT;
BEGIN

   ftcNuevoTexto = '';     -- El texto que se devolverá

   lnI = 1;
   lnJ = 1;
   lnK = 1;

   WHILE (lnI <= CHAR_LENGTH(tcTexto)) DO BEGIN
      lcCaracter    = SUBSTRING(tcTexto FROM lnI FOR 1);     -- Obtiene el caracter que está en la posición lnI
      lnAscii       = ASCII_VAL(lcCaracter);                  -- Halla el código ASCII del caracter
      lnValor1      = CAST(SUBSTRING(tcNumeroEncriptacion FROM lnJ FOR 1) AS SMALLINT);
      lnValor2      = CAST(SUBSTRING(tcNumeroRepeticion FROM lnK FOR 1) AS SMALLINT);
      lnNuevoAscii  = MOD((lnAscii + IIF(tcAccion = 'E', 1, -1) * lnValor1 * lnValor2), 256);
      lnNuevoAscii  = lnNuevoAscii + IIF(lnNuevoAscii < 0, 256, 0);
      ftcNuevoTexto = ftcNuevoTexto || ASCII_CHAR(lnNuevoAscii);
      lnI = lnI + 1;
      lnJ = lnJ + 1;
      lnJ = IIF(lnJ > CHAR_LENGTH(tcNumeroEncriptacion), 1, lnJ);
      lnK = lnK + 1;
      lnK = IIF(lnK > CHAR_LENGTH(tcNumeroRepeticion), 1, lnK);
   END

   SUSPEND;

END;

Escribir el algoritmo fue muy sencillo pero su efecto es muy poderoso. Para probar cuan efectivo es puedes encriptar texto con él y luego dárselo a quienes estén interesados en desencriptarlo. Lo más probable es que todos se rindan al cabo de unos días al no conseguir el objetivo de desencriptarlo.

Recibe como parámetros de entrada el texto que se quiere encriptar, la acción a realizar con ese texto (si es “E” significa que se quiere encriptarlo, si es “D” significa que se quiere desencriptarlo), un número de encriptación y un número de repetición.

Se devuelve el resultado de encriptar o desencriptar el texto.

Si quieres leer una explicación del algoritmo puedes entrar en esta página:

https://firebird21.wordpress.com/2014/01/28/bases-de-datos-shareware-3/

Un stored procedure para verificar la encriptación

¿Es el algoritmo suficientemente bueno? ¿Sirve tanto para encriptar como para desencriptar texto?

Para comprobarlo aquí hay otro stored procedure, el cual primero encripta un texto y luego lo desencripta. Si el algoritmo está ok entonces el texto desencriptado debe ser exactamente igual al texto original.

CREATE PROCEDURE VERIFICARENCRIPTACION
RETURNS(
   ftcResultado VARCHAR(32765))
AS
   DECLARE VARIABLE lcTexto              VARCHAR(32765);
   DECLARE VARIABLE lcNumeroEncriptacion VARCHAR(32765);
   DECLARE VARIABLE lcNumeroRepeticion   VARCHAR(32765);
BEGIN

   lcTexto = 'FIREBIRD SQL ES BUENÍSIMO Y ADEMÁS ES SIEMPRE GRATIS. Y ESO ES GENIAL.29/01/2014*01/03/2014*29/01/2014';

   lcNumeroEncriptacion = '93753468';
   lcNumeroRepeticion = '7915317786';

   ftcResultado = (SELECT ftcNuevoTexto FROM ENCRIPTAR(:lcTexto, 'E', :lcNumeroEncriptacion, :lcNumeroRepeticion));

   SUSPEND;

   ftcResultado = (SELECT ftcNuevoTexto FROM ENCRIPTAR(:ftcResultado, 'D', :lcNumeroEncriptacion, :lcNumeroRepeticion));

   SUSPEND;

END;

Y aquí está el resultado de ejecutar el stored procedure anterior. Como puedes ver funciona perfectamente; primero encriptó (hizo ilegible) al texto que se le envió como parámetro de entrada y luego desencriptó ese resultado para volver a mostrar el texto original.

ENCRIPTAR1

Captura 1. Si haces clic en la imagen la verás más grande

¿Aún no estás seguro de su efectividad? Puedes darle el texto que se encuentra en la primera fila a todas las personas que quieras, inclusive diciéndoles que en alguna parte está el número 2014, por ejemplo, para darles una ayuda. Y luego espera sentado a que alguien obtenga la segunda fila.

Hay muchas formas de mejorar el algoritmo, una de ellas es agregarle cada “n” caracteres otro caracter. Por ejemplo, cada 3 caracteres le agregas un caracter al azar. Al desencriptar debes ignorar a los caracteres que se encuentran en las posiciones 3, 6, 9, 12, 15, etc., todos los que sean múltiplos de 3. Eso hará que el texto encriptado sea un tercio más largo que el texto original. Si no quieres insertar un caracter al azar cada “n” posiciones entonces puedes tener un número que te indique donde debes insertar el caracter al azar. Por ejemplo si a la variable la llamas lcCaracteresAdicionales con el valor ‘7,8,13,19,24,31,43,44,45,60’ eso significa que los caracteres que se encuentran en las posiciones 7, 8, 13, 19, 24, 31, 43, 44, 45, 60 deben ser ignorados porque están solamente de relleno.

Cuanto más largos sean lcNumeroEncriptacion y lcNumeroRepeticion más efectivo y poderoso será el algoritmo. Las secuencias se repiten cada CHAR_LENGTH(lcNumeroEncriptacion) * CHAR_LENGTH(lcNumeroRepeticion). En nuestro ejemplo cada 8 * 10, o sea cada 80 caracteres. Si las longitudes de lcNumeroEncriptacion y lcNumeroRepeticion fueran de 25 y 30 respectivamente, entonces se repetirían cada 25 * 30 = 750 caracteres. Con longitudes suficientemente largas puedes hacer que se repitan cada varios miles de caracteres, complicándoles muchísimo la vida a los curiosos.

Lo bueno de este algoritmo es que al 99.99% de las personas que ven la primera fila ya les da pereza intentar obtener la segunda fila luego de unos cuantos minutos de intentarlo (eso, si alguna vez lo intentan).

IMPORTANTÍSIMO

Algo que jamás debes olvidar son los valores que usaste en los parámetros de entrada tcNumeroEncriptacion y tcNumeroRepeticion porque si los olvidas lo más probable es que jamás puedas volver a obtener el texto original. Así que mucho, pero mucho cuidado con eso.

NOTA:

Aunque aquí el algoritmo para encriptar y desencriptar textos se mostró en un stored procedure lo normal es que lo tengas como una función del usuario en tu lenguaje de programación (Visual FoxPro, Visual Basic, Delphi, C, C++, Java, etc.)

Artículos relacionados:

Bases de datos shareware (3)

El índice del blog Firebird21

Bases de datos shareware (3)

Deja un comentario

En estos dos artículos ya habíamos visto las ventajas de que nuestras aplicaciones y nuestras bases de datos sean shareware, las trampas que pueden usar los usuarios para usarlas indefinidamente y las formas de protección.

https://firebird21.wordpress.com/2014/01/23/bases-de-datos-shareware/

https://firebird21.wordpress.com/2014/01/24/bases-de-datos-shareware-2/

Ahora veremos el código fuente que nos permitirá protegerlas. Bueno, al menos otorgarles un nivel razonable de protección porque es imposible una protección que sea 100% efectiva contra el 100% de las personas, siempre habrá alguien que tendrá los conocimientos y el tiempo libre suficientes como para romper cualquier protección.

Encriptando los datos

Evidentemente, para que cualquier método de protección funcione los datos deben encontrarse encriptados o de lo contrario será muy fácil romper la protección.

Se llama “encriptar” al hecho de someter un texto legible a un algoritmo que lo convierta en texto ilegible. Hay millones de métodos para encriptar texto y tú puedes crear el tuyo propio si quieres, solamente debes asegurarte de que funcione siempre.

Un método muy simple es convertir cada letra en la siguiente. Por ejemplo la palabra ‘FIREBIRD’ quedaría convertida en ‘GJSFCJSE’. Puede servir … pero solamente ante gente cuyos conocimientos de criptografía (o sea: el arte de encriptar texto) sean nulos. Porque cualquier criptógrafo en la primera lección ya aprendió como descifrar ese texto.

Un método un poco más complicado es pasar cada letra no a la siguiente, sino a la que está a “n” lugares de su posición. Es decir, para la primera letra, la siguiente. Para la segunda letra, la que está a dos lugares. Para la tercera letra, la que está a 3 lugares, etc. Usando este método la palabra ‘FIREBIRD’ quedaría convertida en ‘GKUIGOYL’. También podría funcionar … contra gente que tiene nulos conocimientos de criptografía.

Un método aún más complicado es usar un número arbitario que sea bastante largo como para que el trabajo de descubrirlo sea inmenso. Como en nuestro ejemplo el texto tiene solamente 8 letras entonces ese número es suficiente conque tenga 8 dígitos. Pero para un texto más largo, por ejemplo de 200 caracteres, el número debería tener como mínimo 25 dígitos para dificultarle mucho la tarea al curioso. Usaremos como número de encriptación al 25907814. Esto significa que a la primera letra le sumaremos 2, a la segunda 5, a la tercera 9, a la cuarta 0, etc.

Usando este algoritmo la palabra ‘FIREBIRD’ quedaría convertida en ‘HN[EIQSH’.

Como puedes ver nuestro número de encriptación tiene 8 dígitos. ¿Y si queremos usar ese mismo número de encriptación pero con un texto más largo? Pues simplemente cuando hayamos usado todos los dígitos volvemos al principio. Por ejemplo, al encriptar ‘FIREBIRD SQL’ obtendríamos: ‘HN[EIQSH”XZL’. O sea, a la primera letra le sumamos 2, a la segunda 5, a la tercera 9, etc. Cuando hayamos usado los 8 dígitos volvemos a sumar 2, luego 5, luego 9, etc.

¿Quiéres hacerlo más complicado?

Bueno, entonces la primera vez que usas tu número de encriptación multiplicas cada dígito por 1. La segunda vez multiplicas por 2, la tercera vez por 3, etc.

Entonces, la primera vez usarías: 2, 5, 9, 0, 7, 8, 1, 4

La segunda vez usarías: 4, 10, 18, 0, 14, 16, 2, 8

La tercera vez usarías: 6, 15, 27, 0, 21, 24, 3, 12

Y así sucesivamente.

¿Quieres mejorarlo aún más?

Pues entonces no multipliques la primera vez por 1, la segunda vez por 2, la tercera vez por 3, etc., sino usa un número de repetición que tenga muchos dígitos. Si por ejemplo usas el 87532149 la primera vez multiplicarás por 8, la segunda vez multiplicarás por 7, la tercera vez por 5, la cuarta vez por 3, etc.

La longitud del número de encriptación (en nuestro ejemplo: 25907814) y la del número de repetición (en nuestro ejemplo: 87532149) no tiene que ser necesariamente la misma, inclusive si sus longitudes son distintas le complicarás aún más la vida al curioso.

Si tu número de encriptación y tu número de repetición son muy largos, este es un algoritmo MUY EFECTIVO y es MUY DÍFICIL que lo puedan descubrir. El gran problema para los curiosos es que las secuencias solamente se repiten luego de miles de iteraciones y como el texto que se está encriptando en cada iteración es distinto entonces es dificilísimo desentrañar el algoritmo.

Usando una tabla de CONTROL o de CONFIGURACIÓN

Para que el algoritmo sea muy efectivo lo que debes hacer es tener en tu Base de Datos una tabla que tenga una sola fila. A esa tabla la puedes llamar CONTROL, CONFIGURACIÓN, PARÁMETROS, o como te guste. Su objetivo es tener guardados en ella valores que se requerirán conocer para usar la Base de Datos y sin dichos valores no se podrá usar. Por ejemplo:

  • Moneda en la cual están los importes (dólares, euros, etc.)
  • Si los códigos se generarán en forma automática
  • Si usan código de barras
  • El formato en el cual se muestran las cantidades
  • El formato en el cual se muestran los importes
  • etc.

Y además de todas esas columnas guardas también la fecha de inicio, la fecha de fin, la fecha última y la cantidad de ejecuciones.

Luego, encriptas todo según el algoritmo que hayas elegido emplear:

CON_CHECKS = HallarChecksum(CON_MONEDA || CON_CODAUT || CON_CODBAR || CON_FORCAN || CON_FORIMP || CON_FECINI || CON_FECFIN || CON_FECULT || CON_CANTID)

lcFila = Encriptar(CON_MONEDA || CON_CODAUT || CON_CODBAR || CON_FORCAN || CON_FORIMP || CON_FECINI || CON_FECFIN || CON_FECULT || CON_CANTID || CON_CHECKS)

y guardas la fila que como está encriptada solamente servirá si se la desencripta. Si el usuario tramposo borra esta fila no podrá usar la Base de Datos porque para usarla se requieren de los valores que se guardan en esta fila.

La última columna es un “checksum”, es decir un número que nos indica si el contenido de esa fila lo hemos guardado nosotros o lo guardó un tramposo. Puedes leer más en este artículo:

https://firebird21.wordpress.com/2013/07/11/detectando-modificaciones-no-autorizadas-a-nuestras-tablas/

Por lo tanto, si se borra la fila, no se puede usar la Base de Datos. Y si se modifica la fila desde afuera de nuestra aplicación, tampoco se puede usar la Base de Datos.

Artículos relacionados:

Bases de datos shareware

Bases de datos shareware (2)

Detectando modificaciones no autorizadas a nuestras tablas

El índice del blog Firebird21