sábado, 12 de julio de 2008

Gestión de requerimientos I

Comenzamos la serie de artículos sobre gestión de requerimientos en software de seguro con la explicación de la orientación general sobre la manipulación de los mismos que daremos en adelante.

La primera de las premisas es que

los requerimientos cambian durante el proyecto

según la cual debemos estar dispuestos a aceptar modificaciones en los requerimientos según avanza el desarrollo.
Porque aunque estemos seguros de la idea que se tiene sobre el proyecto, aunque haya un acuerdo completo en lo esperado, los requisitos son una de las formas de verbalizar esta idea. ¿Qué ocurre? Que la verbalización puede no ser absolutamente precisa al principio y ha de ser refinada a medida que pasa el tiempo o bien puede que no exista ninguna manera de formalizarla. Así que es muy importante que tengamos en cuenta que

los requerimientos son una posible aproximación a lo esperado del sistema.

La manera de ir refinando los requerimientos en conjunto es comunicar de manera fluida con los involucrados en el proyecto, y ello implica identificarlos, temporizar y personalizar los mensajes y tramitar las respuestas.

Consecuentemente, hemos de

enmarcar los requerimientos en un Plan de Requerimientos

que contendrá los siguientes elementos:
  1. Un sumario del proyecto

  2. Personas y roles involucrados

    • Proveedores de requerimientos

    • Interesados en el éxito

    • Responsables de aprobación
    •  
    • Interesados colaterales

    • Reticencias posibles y apoyos residuales


  3. Modelo de gestión de los requerimientos

    • Planificación temporal de tratamiento

    • Esbozo del Plan de Gestión de Cambios

  4. Esbozo del Plan de Comunicación

Simultáneamente, deberíamos conformar dos planes adicionales, perfectamente reutilizables entre proyectos (y que acabarán constituyendo parte del estilo de desarrollo del equipo):
  1. Plan de Gestion de Cambios

  2. Plan de Comunicación
En sucesivos artículos entraremos en la descripción detallada de los elementos de estos dos planes junto con el Plan de Requerimientos global.

Los problemas que suelen encontrarse como manifestaciones de una deficiente gestión de los requerimientos, son de sobra conocidos y atribuidos habitualmente a la hipotética crisis del software:
    ➡ Requerimientos incompletos o poco claros
    ➡ Requerimientos cambian muy a menudo
    ➡ Todo tiene la misma prioridad
    ➡ Retraso en la implantación que se aprovecha para añadir más requerimientos
    ➡ Cliente y otros interesados empiezan a ignorar el desarrollo del proyecto
    ➡ Preocupación desorbitada por el presupuesto excedido

Ante estos indicios, podemos extraer una serie de conclusiones parciales sobre la importancia que tiene la manipulación correcta de los requerimientos:
  • Escuchar a los usuarios, promotore e interesados NO es suficiente
  • No se ha de comenzar demasiado pronto
  • No se ha de comenzar demasiado tarde
  • Aprovechemos todas las herramientas para nuestro beneficio
  • Flexibilidad y aceptación del cambio
Evidentemente, si pensamos que los requerimientos no son más que una de las expresiones posibles de las expectativas que un individuo o un conjunto de ellos tienen del sistema, queda claro que no basta con tomar nota de lo que se nos está pidiendo explícitamente. Hay que entender, proponer, sintetizar, eliminar redundancias, buscar relaciones y prioridades, siempre involucrando a los personajes relacionados con el proyecto. Los requerimientos irán surgiendo, refinándose o desapareciendo a lo largo del tiempo de vida del desarrollo del proyecto. Así que nuestra vida será más fácil si los contemplamos como un fruto del proceso de desarrollo mismo: además de la ya vista evolución fluida de los requisitos

son un fruto también del equipo de desarrollo.

El riesgo derivado del inicio temprano o tardío de la codificación, pruebas u otras actividades tradicionalmente consideradas como productivas, lo veremos en la lista de patrones y antipatrones adaptados a la gestión de los requerimientos. Obtendremos un listado de herramientas útiles para la extracción, análisis y manipulación de los requerimientos, así como la repercusión en los distintos modelos de desarrollo de software ágil. Para poder hacer realidad la flexibilidad ante el cambio, tendremos la aplicación de los planes, herramientas y patrones anteriores explicada en detalle adaptándola a este fin.

Algoritmos de Hashing




Definición



Criptográficamente, entendemos por funcion hash
una aplicación de cifrado en la que el mensaje no se puede recuperar. Más ampliamente, las funciones hash son funciones
de resumen de contenido y reciben, en criptografía, también el
nombre de digest.



Una función hash perfecta es aquella que mapea diferentes
valores de origen en diferentes valores destino. Claro está que
no puede ser perfecta una función cuyo espacio de imágenes
sea más pequeño que el de actuación.



Las funciones hash utilizan habitualmente una serie de operaciones
combinadas y aplicadas reiteradamente sobre el valor de entrada:


  • truncamiento o extracción: elimnando una serie de bits
    del principio o el final del mensaje

  • modúlo: actuación en un grupo finito de las operaciones
    para conseguir el hash

  • selección: tomcar una serie de posiciones fijas del
    mensaje, despreciando el resto

  • cambio de base: representación del mensaje en una
    base diferente a la original.

  • doblamiento: división del mensaje en varias partes de igual
    tamaño y aplicación de determinada operación entre las partes
    para obtener como resultado algo de igual tamaño que los
    fragmentos tomados.

  • multiplicación: multiplicar el valor del mensaje truncándolo
    o reduciéndolo módulo una constante.


Esta lista está más bien obtenida a partir de los más representativos
mecanismos de cálculo de hashes; es decir, no excluyen que haya
algoritmos de hashing que no utilicen estos elementos.






Utilizaciones



El más elemental de los usos es para el almacenamiento y
localización de objetos:


  • arrays asociativos

  • buscadores en redes P2P: DHT


Para estas aplicaciones cuestiones tales como el número de
colisiones o el conocimiento o no de la inversa de la función hash
no son tan relevantes en tanto en cuanto no es la seguridad
el objetivo buscado.



En criptografía, hay ocasiones en las que cifrar el mensaje
entero es sumamente pesado y realmente innecesario. En estos
casos se utilizan funciones hash que convierten un mensaje de
tamaño ilimitado a priori en un valor acotado. Es importante
en este caso que no sea factible encontrar una inversa para la
función hash y que la probabilidad de colisiones se lo más baja
posible. También, para evitar reconstruir la inversa de la
función por fuerza bruta, las funciones hash utilizadas con esta
finalidad han de ser pesadas, es decir, que el tiempo empleado
en calcular no sea muy poco y no pueda ser reducido.



Para almacenar claves, en conformidad con los preceptos
expresados en la LOPD (artículo 93.3, BOE núm 17 de
19 de enero de 2008), por ejemplo, se requiere que no
puedan ser descubiertas aunque se acceda a lugar donde se guardan éstas. Aunque existen varias maneras, la forma más simplificada consiste en recordar el hash de la clave del
usuario. En este caso buscamos la minimización de colisiones
y una gran dispersión de valores próximos, frente a otras
caracterísitcas como reducción del espacio ocupado por
el valor hash frente al valor original o la velocidad
de cómputo.



La aplciación a criptografía para verificar la autenticida
d de un
mensaje se conoce con el nombre de HMAC (del inglés
keyed-Hash Message Authentication Code). Una HMAC utiliza una
clave privada y una función hash para demostrar que el mensaje
no ha sido alterado desde que lo generara el propietario de la clave privada.



Matemáticamente, un HMAC es


HMACk(msg) = h((K ^ opad) | (K ^ ipad) | msg)

siendo ipad=0x36363636...36 y
opad=0x5c5c5c...5c
(secuencia de bits impares y pares),
h una función hash criptográfica y K la clave privada.




Características deseables



Ante todo, la función hash de estar bien definida, es decir
el valor del hash ha de estar completamente determinado a partir de
los datos a resumir. Es la única manera de que la función hash
pueda ser utilizada para comprobar la integridad de los datos
resumidos.



La función hash ha de utilizar completamente todos los
datos de entrada. La pretensión es que no haya colisiones a
priori definidas por la propia función hash.



La distribución de valores sobre el espacio de imagen ha de ser
uniforme. Si siguiera alguna distribución no uniforme,
habría una mayor probabilidad de colisionar para determinados
valores de entrada.



Ha de generar valores suficientemente diferentes para
valores de entrada muy similares. Lo que sería característico
de un problema mal puesto en mecánica es algo deseable
por tanto en cuanto desviamos las probabilidades de encontrar
colisiones.



En general, cualquier función que cumpla estas caracterísitcas
podemos encuadrarlas en el conjunto de funciones hash. Por las
particulares características pedidas a las funciones hash y
dada la dificultad de demostrar que se cumplen es habitual
utilizar algoritmos ya implementados y probados.


Por ejemplo, a modo de muestra de la infinidad de lista de funciones
hash tenemos:


  • Unix ELF hash:


    unsigned long hash(char *text) {
    unsigned long h = 0, g;
    while(*name) {
    h = (h<<4) + *name;
    *name++;
    g = h & 0xF0000000;
    if(g!=0) h^=g > 24;
    h &= ~g;
    }
    return h;
    }

  • Hash XOR:


    char XORhash(char *text, int len) {
    char hash;
    int i;
    for(hash=0, i=0; i<len; i++) hash=hash^key[i];
    return hash % 101;
    }


El espacio destino de estas funciones es muy reducido (8 bits en un
caso y 32 en el otro), lo que hace que para firmado sea poco
útiles. Sin embargo, para comprobar la integridad de contenidos
pequeños, o para su utilización en tablas hash y cuando hay
restricciones de cómputo son perfectamente
utilizables, cumpliendo las caracterísitcas pedidas (es un
interesante ejercicio demostrarlo).






Colisión



Se llama así, para una función hash dada, a la situación en la
que dos valores diferentes tienen el mismo valor hash.



Evidentemente, como el objetivo de las funciones hash es el
resumen de contenidos, el espacio imagen es menor que el espacio
de origen de la función. Incluso, en el caso más general,
el espacio inicial es ilimitadamente grande (por ejemplo, todas
los posibles secuencias de caracteres) mientras que el
destino está acotado (por ejemplo, el espacio de valores de
160 bits). Así que es trivial que habrá colisiones
inevitablemente. La cuestión es si las colisiones son predecibles
y hasta que punto. Cuando, dada una función hash, se
descubre un mecanismo para provocar las colisiones, se
dice que el sistema hash ha sido vulnerado.






Aleatoriedad



Los algoritmos de hash son, por definición predecibles. Es decir,
para la misma entrada, producen la misma salida.


Sin embargo, a la hora de utilizarlos para el firmado de documentos
o para almacenamiento seguro de contraseñas, conviene provocar la
dispersión aleatoria del hash generado, añadiendo al contenido
original un valor o valores aleatorios.



Si el generador de números aleatorios es predecible en algún sentido,
como ha ocurrido con el generador de OpenSSL de Debian
en mayo de 2008, el almacenamiento de claves y generación de
valores para identificadores de sesión con los que cifrar
queda seriamente comprometido.