2009

Columnas publicadas en 2009

La seguridad en cómputo: ¿Con qué se come?

TitleLa seguridad en cómputo: ¿Con qué se come?
Publication TypeMagazine Article
Year of Publication2009
AuthorsWolf G
MagazineSoftware Gurú
FrequencyQuarterly
Issue Number23
Pagination47
Date Published02/2009
Type of ArticleColumn
ISSN1870-0888
KeywordsSeguridad
URLhttp://www.sg.com.mx/content/view/825
Full Text

La evolución del rol que cumplen los sistemas en las organizaciones ha cambiado por completo -afortunadamente- el punto de vista que la mayor parte de los desarrolladores tiene con respecto a la seguridad.
Hace una o dos décadas, el tema de la seguridad en cómputo era frecuentemente evitado. Y hasta cierto punto, esto era muy justificable: ¿Intrusos? ¿Integridad? ¿Validaciones? En la década de los 80 había muy poco software diseñado para su operación en red, y mucho menos para la idea de red que tenemos hoy en día. Y si bien es cierto que la mayor parte de los ataques se origina -y siempre se ha originado- dentro del perímetro de confianza de nuestra organización, hace 20 años sencillamente había menos información sensible alojada en medios electrónicos, menos gente con el conocimiento necesario para manipularla, e incluso la manipulación tenía efectos más nocivos: Si bien hace años la infraestructura de cómputo era el soporte facilitador, la copia maestra estaba en papel - Hoy en día estamos transitando hacia la situación opuesta, en que la versión electrónica es la primaria. Hoy, una intrusión en nuestros sistemas puede poner en jaque la integridad de la información primaria.
Mantener la seguridad en los sistemas que desarrollamos implica un alto costo: Los programadores tienen que aprender a evitar errores comunes; tenemos que concientizarnos y acostumbrarnos a dedicar recursos a implementar baterías de pruebas; tienen que entrar en juego validaciones y conversiones sobre los datos que manipulamos, con costos en tiempos de procesamiento de cada solicitud... Pero, afortunadamente, ha crecido también la conciencia de la importancia de destinar a la seguridad la atención y recursos que requiere.
El problema sigue siendo, coloquialmente... /¿con qué se come?/ La seguridad en cómputo sigue siendo un campo dificil de entender, con muchas aristas ocultas. Es por esto que, en las siguientes ediciones de SoftwareGurú, iré abordando algunos temas fundamentales en esta columna.
Para iniciar con esta serie, ataquemos lo fundamental: ¿Qué debemos entender por seguridad en cómputo?
A riesgo de que parezca que me limito a perogrulladas, un /sistema seguro/ es sencillamente un sistema /que responde como debe/. Claro, a esta pregunta hay que verla a la luz de varios criterios para que en verdad signifique algo. Intentemos nuevamente. Un sistema seguro presenta:

Consistencia:
Ante las mismas circunstancias, debe presentar el mismo comportamiento. Ante un sistema /seguro/, el tradicional remedio "¿ya intentaste reniciarlo?" no surte efecto. Si nos hemos acostumbrado a que un reinicio resuelve las cosas, no es sino porque el ambiente de ejecución se ensucia con elementos que debería haber descartado.
Protección y separación:
Los datos, instrucciones y espacio de memoria de un programa, componente o usuario no deben interferir ni permitir interferencia de otros. Las condiciones anormales ocurridas en uno de los componentes -sean accidentales o expresas- deben tener un impacto mínimo en el sistema como un conjunto.
Autenticación:
El sistema debe poseer los mecanismos necesarios para asegurarse que un usuario es realmente quien dice ser
Control de acceso:
Nuestro sistema debe poder controlar con toda la granularidad necesaria los permisos de acceso a sus datos - Quién tiene acceso a qué recursos, y qué tipo de acceso tiene.
Auditoría:
El sistema debe ser capaz de registrar, así como de notificar a quien sea necesario de cualquier anomalía o evento importante.

Claro está, todos estos atributos deben ir matizados, priorizándolos al nivel /adecuado/ a nuestras necesidades. Ir demasiado lejos en uno de estos objetivos puede ser de hecho perjudicial para los fines de nuestro sistema - Por poner un ejemplo, es de todos bien conocido que el tradicional esquema de autenticación basado en usuario y contraseña es fácil de engañar; basta adivinar (o conseguir) un pedazo de información, típicamente de muy débil complejidad, para estar autenticado como determinado usuario. En México, los bancos -por poner un ejemplo- ahora exigen la identificación del cliente a través de dispositivos que presenten una mucho mayor complejidad, generando cadenas de números que cambian periódicamente. Pero, obviamente, poca gente requerirá un nivel de seguridad similar a éste, o basado en parámetros biométricos, para abrir su cuenta de correo.
Y otra anotación: Nos es natural aspirar a la perfección, al 100%. Sin embargo, dice el refrán que "lo perfecto es enemigo de lo bueno". Es importante que, en toda etapa de la planeación, desarrollo, implantación y tiempo de vida de un sistema recordemos que un 100% de seguridad es una utopía, un objetivo que sólo puede servir para guiar nuestro trabajo diario.
Los programas son escritos por humanos, y son también humanos quienes administran los sistemas en que corren. Hay una gran cantidad de interacciones entre los elementos de un programa y el sistema, y un cambio en cualquiera de ellos puede tener consecuencias inesperadas si no se hace con cuidado y conciencia. Constantemente aparecen nuevas categorías de errores capaces de llevar a problemas de seguridad. Parte fundamental de nuestra actuación como profesionales en nuestro campo debe ser el mantenernos al día con los últimos desarrollos y las últimas amenazas.
Un par de días antes de la entrega de esta columna, dos de las organizaciones más influyentes en la investigación y respuesta a incidentes de seguridad informática, SANS y MITRE, publicaron su lista de los 25 errores de seguridad más importantes:
http://www.sans.org/top25errors/
Este listado incluye muchos temas de fundamental relevancia, algunos de los cuales abordaré en mis posteriores columnas. Vienen explicados con un buen nivel de detalle, detallando cómo evitar o mitigar sus efectos. Les recomiendo fuertemente familiarizarse con estos temas.

AttachmentSize
200902_softwareguru.jpg598.21 KB

Evitando las Inyecciones de SQL

TitleEvitando las Inyecciones de SQL
Publication TypeMagazine Article
Year of Publication2009
AuthorsWolf G
MagazineSoftware Gurú
FrequencyQuarterly
Issue Number24
Pagination50-51
Date Published05/2009
Type of ArticleColumn
ISSN1870-0888
Keywordsinyección, Seguridad, SQL
URLhttp://www.sg.com.mx/content/view/874
Full Text

En la edición anterior de SoftwareGurú prometí que en esta columna trataría temas relativos a la seguridad en cómputo, a cómo escribir código más confiable y más robusto.
Las vulnerabilidades más comunes son también las más fáciles de explotar para un atacante - Y utilizando algunas prácticas base, son también las más fáciles de evitar o corregir: Casi todas ellas se originan en la falta de validación (o exceso de confianza) en los datos que nos proporciona el usuario.
Prácticamente la totalidad de los sistemas que desarrollemos procesarán datos provenientes de terceros. Ya sea mostrando o grabando lo expresado en formas HTML, determinando el flujo de nuestra aplicación a través de rutas y parámetros o «galletas» HTTP, o incluso -considerando la tendencia de migración hacia un esquema de «cloud computing»- tomando resultados de procedimientos remotos en sistemas no controlados por nosotros, a cada paso debemos emplear datos en los que no confiamos.
Esta puerta de entrada permite a un atacante una amplia variedad de modalidades de intrusión. En general, podemos hablar de ellas como inyección de código interpretado - Y en esta ocasión hablaremos específicamente de inyección de SQL.
En el desarrollo de sistemas debemos partir siempre del principio de mínima confianza: No debemos confiar en ningún dato proveniente de fuera de nuestro sistema, independientemente de quién sea el usuario. Esto es especialmente importante cuando requerimos que un elemento cruce entre las principales barreras de las diversas capas de nuestro sistema.
Tomemos como primer ejemplo al sistema de gestión de contenido que usa SoftwareGurú. Si quisieran leer la edición anterior de esta columna, a la que hice referencia hace algunas líneas, pueden encontrarla en:
http://www.sg.com.mx/content/view/825
Todos hemos analizado URLs, y resultará obvio que «825» corresponda al ID de la nota en la base de datos, y que los componentes «content» y «view» indiquen la operación que el sistema debe realizar ante una solicitud. Ahora bien, ¿a qué me refiero a que cruzamos las barreras entre las capas? ¿Y cuáles son las principales?
Enfoquémonos en el ID. Al analizar el URL, el ID es un pedazo de texto (formalmente es una cadena que es recibida como parte del método GET, uno de los métodos definidos para el protocolo HTTP). El servidor Web Apache que recibe mi solicitud interpreta este método GET y encuentra -utilizando mod_rewrite, indicado por el archivo htaccess- que el contenido indicado por la ruta /content/view/* debe ser procesado por el archivo index.php, que a su vez es manejado por el lenguaje PHP. El archivo index.php corresponde en este caso al sistema Joomla, que reconoce la ruta, convierte al ID en su representación numérica y lo utiliza para pedir a la base de datos le entregue los datos relacionados con determinado artículo. Entonces, aquí podemos reconocer los siguientes puntos principales de manipulación de la solicitud:

  • Apache recibe una solicitud HTTP, y (via mod_rewrite) la reescribe, indicando «content», «view» y «825» como parámetros a index.php
  • PHP analiza, separa y estructura los parámetros recibidos para ser utilizados por Joomla
  • Joomla solicita el artículo 825 a la base de datos

La variabilidad de los primeros pasos es en realidad menor - Pero al solicitar a la base de datos el artículo «825» (y este es el caso base, el más sencillo de todos) deben pasar muchas cosas. Primero que nada, «825» es una cadena de caracteres. PHP es un lenguaje débilmente tipificado (los números se convierten en cadenas y viceversa automáticamente según sea requerido), pero una base de datos maneja tipos estrictamente. Como atacante, puedo buscar qué pasa si le pido al sistema algo que no se espere - Por ejemplo, «825aaa». En este caso (¡felicidades!), el código PHP que invoca a la base de datos sí verifica que el tipo de datos sea correcto: Hace una conversión a entero, y descarta lo que sobra. Sin embargo (y no doy URLs por razones obvias), en muchas ocasiones esto me llevaría a recibir un mensaje como el siguiente:

Warning: pg_execute() [function.pg-execute]: Query failed: ERROR:
invalid input syntax for integer: "825aaa" in
/home/(...)/index.php on line 192

Esto indica que uno de los parámetros fue pasado sin verificación de PHP al motor de base de datos, y fue éste el que reconoció al error.
Ahora, esto no califica aún como inyección de SQL (dado que el motor de bases de datos supo reaccionar ante esta situación), pero estamos prácticamente a las puertas. Podemos generalizar que cuando un desarrollador no validó la entrada en un punto, habrá muchos otros en que no lo haya hecho. Este error en particular nos indica que el código contiene alguna construcción parecida a la siguiente:

"SELECT * FROM articulo WHERE id = $id_art"

La vulnerabilidad aquí consiste en que el programador no tomó en cuenta que $id_art puede contener cualquier cosa enviada por el usuario - Por el atacante en potencia. ¿Cómo puedo aprovecharme de esto? La imaginación es lo único que me limita.
Presentaré a continuación algunos ejemplos, evitando enfocarme a ningún lenguaje en específico - Lo importante es cómo tratamos al SQL generado.
Para estos ejemplos, cambiemos un poco el caso de uso: En vez de ubicar recursos, hablemos acerca de una de las operaciones más comunes: La identificación de un usuario vía login y contraseña. Supongamos que el mismo sistema del código recién mencionado utiliza la siguiente función para validar a sus usuarios:

$data = $db->fetch("SELECT id FROM usuarios WHERE login = '$login' AND passwd = '$passwd'");
if ($data) { $uid = $data[0];
} else { print "<h1>Usuario inválido!</h1>";
}

Aquí pueden apreciar la práctica muy cómoda y común de interpolar variables dentro de una cadena - Muchos lenguajes permiten construir cadenas donde se expande el contenido de determinadas variables. En caso de que su lenguaje favorito no maneje esta característica, concatenar las sub-cadenas y las variables nos lleva al mismo efecto.
Sin embargo... ¿Qué pasaría aquí si el usuario nos jugara un pequeño truco? Si nos dijera, por ejemplo, que el login es «fulano';--», esto llevaría al sistema a ignorar lo que nos diera por contraseña: Estaríamos ejecutando la siguiente solicitud:

SELECT id FROM usuarios WHERE login = 'fulano';--' AND PASSWD = ''

La clave de este ataque es confundir a la base de datos para aceptar comandos generados por el usuario - El ataque completo se limita a cuatro caracteres: «';--». Al cerrar la comilla e indicar (con el punto y coma) que termina el comando, la base de datos entiende que la solicitud se da por terminada y lo que siga es otro comando. Podríamos enviarle más de un comando consecutivo que concluyera de forma coherente, pero lo más sencillo es utilizar el doble guión indicando que inicia un comentario. De este modo, logramos vulnerar la seguridad del sistema, entrando como un usuario cuyo login conocemos, aún desconociendo su contraseña.
Pero podemos ir más allá - Siguiendo con este ejemplo, típicamente el ID del administrador de un sistema es el más bajo. Imaginen el resultado de los siguientes nombres de usuario falsos:

  • ninguno' OR id = 1;--
  • '; INSERT INTO usuarios (login, passwd) VALUES ('fulano', 'de tal'); --
  • '; DROP TABLE usuarios; --

No puedo dejar de sugerirles visitar al ya famoso «Bobby Tables»1.
¿Y qué podemos hacer? Protegerse de inyección de SQL es sencillo, pero hay que hacerlo en prácticamente todas nuestras consultas, y convertir nuestra manera natural de escribir código en una segura.
La regla de oro es nunca cruzar fronteras incorporando datos no confiables - Y esto no sólo es muy sencillo, sino que muchas veces (específicamente cuando iteramos sobre un conjunto de valores, efectuando la misma consulta para cada uno de ellos) hará los tiempos de respuesta de nuestro sistema sensiblemente mejores. La respuesta es separar preparación y ejecución de las consultas. Al preparar una consulta, nuestro motor de bases de datos la compila y prepara las estructuras necesarias para recibir los parámetros a través de «placeholders», marcadores que serán substituídos por los valores que indiquemos en una solicitud posterior. Volvamos al ejemplo del login/contraseña:

$query = $db->prepare('SELECT id FROM usuarios WHERE login = ? AND PASSWD = ?');
$data = $query->execute($login, $passwd);

Los símbolos de interrogación son enviados como literales a nuestra base de datos, que sabe ya qué le pediremos y prepara los índices para respondernos. Podemos enviar contenido arbitrario como login y password, ya sin preocuparnos de si el motor lo intentará interpretar.
Revisar todas las cadenas que enviamos a nuestra base de datos puede parecer una tarea tediosa, pero ante la facilidad de encontrar y explotar este tipo de vulnerabilidades, bien vale la pena. En las referencias a continuación podrán leer mucho más acerca de la anatomía de las inyecciones SQL, y diversas maneras de explotarlas incluso cuando existe cierto grado de validación.
El tema de la inyección de código da mucho más de qué hablar - Lo abordaremos en la próxima columna, desde otro punto de vista.
Referencias:

AttachmentSize
200905_softwareguru_1.jpg697.55 KB
200905_softwareguru_2.jpg674.18 KB

Manteniendo el estado en nuestras aplicaciones Web: Una historia de galletas y de resúmenes

TitleManteniendo el estado en nuestras aplicaciones Web: Una historia de galletas y de resúmenes
Publication TypeMagazine Article
Year of Publication2009
AuthorsWolf G
MagazineSoftware Gurú
FrequencyQuarterly
Issue Number25
Date Published08/2009
Type of ArticleColumn
ISSN1870-0888
Keywordscookies, firma criptográfica, galletas, http, Seguridad, Web
URLhttp://www.sg.com.mx/content/view/916
Full Text

Una grandísima proporción de los sistemas desarrollados hoy en día, siguen el paradigma cliente-servidor. Y si bien hay muy diferentes maneras de implementar sistemas cliente-servidor, indudablemente la más difundida hoy por hoy es la de los sistemas Web.
La conjunción de un protocolo verdaderamente simple para la distribución de contenido (HTTP) con un esquema de marcado suficientemente simple pero suficientemente rico para presentar una interfaz de usuario con la mayor parte de las funciones requeridas por los usuarios (HTML) crearon el entorno ideal para el despliegue de aplicaciones distribuídas.
Desde sus principios, el estándar de HTTP menciona cuatro verbos por medio de los cuales se puede acceder a la información: GET (solicitud de información sin requerir cambio de estado), POST (interacción por medio de la cual el cliente manda información compleja y que determinará la naturaleza de la respuesta), PUT (creación de un nuevo objeto en el servidor) y DELETE (destrucción de un determinado objeto en el servidor). Sin embargo, por muchos años, éstos fueron mayormente ignorados — La mayor parte de los sistemas hace caso omiso a través de qué verbo llegó una solicitud determinada; muchos navegadores no implementan siquiera PUT y DELETE, dado su bajísimo nivel de uso — Aunque con la popularización del paradigma REST, esto probablemente esté por cambiar.
El protocolo HTTP, sin embargo, junto con su gran simplicidad aportó un gran peligro — No una vulnerabilidad inherente a los sistemas Web, sino que un peligro derivado de que muchos programadores no presten atención a un aspecto fundamental de los sistemas Web: Cómo manejar la interacción repetida sobre de un protocolo que delega el mantener el estado o sesión a una capa superior. Esto es, para un servidor HTTP, toda solicitud es única. En especial, un criterio de diseño debe ser que toda solicitud GET sea idempotente — Esto significa que un GET no debe alterar de manera significativa1 el estado de los datos.
HTTP fue concebido [1] como un protocolo a través del cual se solicitaría información estática. Al implementar las primeras aplicaciones sobre HTTP2, nos topamos con que cada solicitud debía incluir la totalidad del estado. Es por esto que los muchos sistemas Web hacen un uso extensivo de los campos ocultos (hidden) en todos sus formularios y ligas internas, transportando los valores de interacciones previas que forman parte conceptualmente de una sóla interacción distribuída a lo largo de varios formularios, o números mágicos que permiten al servidor recordar desde quién es el usuario en cuestión hasta todo tipo de preferencias que ha manifestado a lo largo de su interacción.
Sin embargo, éste mecanismo resulta no sólo muy engorroso, sino que muy frágil: Un usuario malicioso o curioso (llamémosle genéricamente «atacante») puede verse tentado a modificar estos valores; es fácil capturar y alterar los campos de una solicitud HTTP a través de herramientas muy útiles para la depuración. E incluso sin estas herramientas, el protocolo HTTP es muy simple, y puede "codificarse" a mano, sin más armas que un telnet abierto al puerto donde escucha nuestro sistema. Cada uno de los campos y sus valores se indican en texto plano, y modificar el campo «user_id» es tan fácil como decirlo.
En 1994, Netscape introdujo un mecanismo denominado galletas (cookies) que permite al sistema almacenar valores arbitrarios en el cliente3. Un año más tarde, Microsoft lo incluye en su Internet Explorer; el mecanismo fue estandarizado en 1997 y extendido en el 2000 con los RFCs 2109 y 2965 [2]. El uso de las galletas libera al desarrollador del engorro antes mencionado, y le permite implementar fácilmente un esquema verdadero de manejo de sesiones — Pero, ante programadores poco cuidadosos, abre muchas nuevas maneras de —adivinaron— cometer errores.
Dentro del cliente (típicamente un navegador) las galletas están guardadas bajo una estructura de doble diccionario — En primer término, toda galleta pertenece a un determinado servidor (esto es, al servidor que la envió). La mayor parte de los usuarios tienen configurados a sus navegadores, por privacidad y por seguridad, para entregar el valor de una galleta únicamente a su dominio origen (de modo que al entrar a un determinado sitio hostil éste no pueda robar nuestra sesión en el banco); sin embargo, nuestros sistemas pueden solicitar galletas arbitrarias guardadas en el cliente. Para cada servidor, podemos guardar varias galletas, cada una con una diferente llave, un nombre que la identifica dentro del espacio del servidor. Además de estos datos, cada galleta guarda la ruta a la que ésta pertenece, si requiere seguridad en la conexión (permitiendo sólo su envío a través de conexiones cifradas), y su periodo de validez, pasado el cual serán consideradas "rancias" y ya no se enviarán. El periodo de validez se mide según el reloj del cliente.
Guardar la información de estado del lado del cliente es riesgoso, especialmente si es sobre un protocolo tan simple como HTTP. No es dificil para un atacante modificar la información que enviaremos al servidor, y si bien en un principio los desarrolladores guardaban en las galletas la información de formas parciales, llegamos a una regla de oro: Nunca guardar información real en ellas. En vez, guardemos algo que apunte a la información. Esto es, por ejemplo, en vez de guardar el ID de nuestro usuario, una cadena criptográficamente fuerte [3] que apunte a un registro en nuestra base de datos. ¿A qué me refiero con esto? A que tampoco grabe directamente el ID de la sesión (dado que siendo sencillamente un número, sería para un atacante trivial probar con diferentes valores hasta "aterrizar" en una sesión interesante), sino una cadena aparentemente aleatoria, creada con un algoritmo que garantice una muy baja posibilidad de colisión y un espacio de búsqueda demasiado grande como para que un atacante lo encuentre a través de la fuerza bruta.
Los algoritmos más comunes para este tipo de uso son los llamados funciones de resumen (digest) [3]. Estos generan una cadena de longitud fija; dependiendo del algoritmo hoy en día van de los 128 a los 512 bits. Las funciones de resumen más comunes hoy en día son las variaciones del algoritmo SHA desarrollado por el NIST y publicado en 1994; usar las bibliotecas que los implementan es verdaderamente trivial. Por ejemplo, usando Perl:

  1. use Digest::SHA1;
  2. print Digest::SHA1->sha1_hex("Esta es mi llave");

nos entrega la cadena:
c3b6603b8f841444bca1740b4ffc585aef7bc5fa
Pero, ¿qué valor usar para enviar como llave? Definitivamente no queremos enviar, por ejemplo, el ID de la sesión - Esto nos pondría en una situación igual de riesgosa que incluir el ID del usuario. Un atacante puede fácilmente crear un diccionario del resultado de aplicar SHA1 a la conversión de los diferentes números en cadenas. La representacíon hexadecimal del SHA1 de '1' siempre será d688d9b3d3ba401b25095389262a3ecd2ad5ad68, y del de 100 siempre será daaaa8121aa28fca0edb4b3e1f7b7c23d6152eed. El identificador de nuestra sesión debe contener elementos que varíen según algún dato no adivinable por el atacante (como la hora exacta del día, con precisión a centésimas de segundo) o, mejor aún, con datos aleatorios.
Este mecanismo nos lleva a asociar una cadena suficientemente aleatoria como para que asumamos que las sesiones de nuestros usuarios no serán fácilmente "secuestradas" (esto es, que un atacante no le atinará al ID de la sesión de otro usuario), permitiéndonos dormir tranquilos sabiendo que el sistema de manejo de sesiones en nuestro sistema es prácticamente inmune al ataque por fuerza bruta.
Como último punto: Recuerden que algunas personas, por consideraciones de privacidad, han elegido desactivar el uso de galletas en su navegación diaria, a excepción de los sitios que expresamente autoricen. Tomen en cuenta que una galleta puede no haber sido guardada en el navegador cliente, y esto desembocará en una experiencia de navegación interrumpida y errática para dichos usuarios. Es importante detectar si, en el momento de establecer una galleta, ésta no fue aceptada, para dar la información pertinente al usuario, para que sepa qué hacer y no se encuentre frente a un sistema inoperativo más.
REFERENCIAS

  • 1. Es aceptable que a través de un GET, por ejemplo, aumente el contador de visitas, esto es, que haya un cambio no substantivo — Pero muchos desarrolladores han sufrido por enlazar a través de un GET (generado por una liga HTML estándar), por ejemplo, el botón para eliminar cierto objeto. Toda acción que genere un cambio en el estado substantivo de nuestra base debe llevarse a cabo a través de POST. ¿Qué pasa en caso contrario? Que diversas aplicaciones, desde los robots indexadores de buscadores como Google y hasta aceleradores de descargas ingenuos que buscan hacer más ágil la navegación de un usuario (siguiendo de modo preventivo todas las ligas GET de nuestro sistema para que el usuario no tenga que esperar en el momento de seleccionar alguna de las acciones en la página) van a disparar éstos eventos de manera inesperada e indiscriminada.
  • 2. En términos de redes, HTTP implementa exclusivamente la capa 4 del modelo OSI, y si bien TCP mantiene varios rasgos que nos permiten hablar tambien de sesiones a nivel conexión, estas son sencillamente descartadas. Las capas 5 y superiores deben ser implementadas a nivel aplicación.
  • 3. La galleta será enviada en cada solicitud que se haga, por lo que se recomienda mantenerla corta. Varias implementaciones no soportan más de 4KB.
AttachmentSize
Versión impresa en SG (primer página)614.01 KB
Versión impresa en SG (segunda página)544.73 KB

Codificación de Caracteres

TitleCodificación de Caracteres
Publication TypeMagazine Article
Year of Publication2009
AuthorsWolf G
MagazineSoftware Gurú
FrequencyQuarterly
Issue Number26
Pagination48-49
Date Published11/2009
Type of ArticleColumn
ISSN1870-0888
KeywordsASCII, caracteres, codificiación, Unicode, UTF
URLhttp://www.sg.com.mx/content/view/954
Full Text

NOTA: Al preparar este texto, excedí el límite de espacio que me asignan, por lo cual después de terminar de escribir tuve que ver qué párrafos eran más prescindibles. Acá reproduzco el artículo completo, y adjunto como archivo la versión tal cual apareció impresa.

Hace un par de días recibí uno de esos correos que parecen venir de una época ya superada y olvidada hace años: Un correo que comenzaba con la frase:

Para evitar problemas de compatibilidad, este correo no incluye acentos ni enies

En efecto, este problema casi ha desaparecido del correo, y ese pretexto sencillamente ya no resulta ni siquiera una excusa adecuada para quien le da flojera escribir el español correctamente.
Sin embargo, no en todos los campos podemos hablar de un éxito tan grande como en el manejo del correo electrónico — Y es de esperarse. La naturaleza misma del correo lo requiere. Cada mensaje individual debe ser transportado, sin que le sean inflingidas modificaciones por agentes externos, entre los equipos de los participantes en una conversación — que pueden estar configuradas con referentes culturales completamente distintos.
Y si bien para los hispanoparlantes, especialmente para aquellos que sienten que el uso de símbolos internacionales (acentos, tildes y signos de apertura de interrogación/exclamación) es opcional, el correo funcionó casi bien desde un principio (y a continuación detallaremos el por qué), cuando el uso de Internet dio su gran salto cuantitativo en los 1990s y llegó a la población en general, para los nativos de muchas otras culturas alrededor del mundo se hizo imperativo encontrar cómo comunicarse confiablemente.
Pero el problema viene de mucho más atrás. Repasemos rápidamente la historia de los esquemas de codificación de caracteres.

Repaso histórico

La codificación a partir de la cual se originan todos los esquemas en uso hoy en día nació en 1963, revisado/ratificado en 1967 con el nombre de ASCII: Código Estándar Americano para el Intercambio de Información. ASCII es un código de 7 bits, permitiendo la representación de hasta 128 caracteres, y en su versión definitiva incluye 32 caracteres de control, 34 símbolos, 52 caracteres de texto (mayúsculas y minúsculas) y 10 dígitos.
Sobra decir que el ámbito cómputo ha cambiado drásticamente desde 1963. La computadora debía representar apenas la información indispensable para ser comprendida por sus operadores, y en la propuesta original, ASCII no incluía ni siquiera letras minúsculas . Pero ya en 1964 aparecieron las máquinas de escribir IBM MT/ST: Una máquina de escribir electrónica, con la capacidad de guardar (y corregir) páginas en cinta magnética. Fue sólo cuestión de tiempo (y del necesario paso de popularización que siguió a la revolución de las computadoras personales hacia fines de los 1970) para que estas capacidades quedaran al alcance de todo mundo.
Y es ahí donde se hizo obvio que haría falta extender ASCII: Todos los idiomas europeos que utilizan el alfabeto latino a excepción del inglés requieren de diferentes tipos de diacríticos para ser representados; tras varias ideas descartadas, se aprovechó el hecho de que hacia fines de los 1970 todas las computadoras ampliamente desplegadas tenían un tamaño de palabra de 8 bits para utilizar un ASCII ampliado que daría 128 caracteres adicionales. Sin embargo, la idea resonó rápidamente… Y no surgió un estándar para su uso. Además, muchos de estos caracteres fueron empleados para incluir caracteres gráficos, para permitir construir interfaces amigables al usuario.
En 1981, IBM puso a la venta su primer computadora personal - La IBM 5051, o como se popularizó, la PC. Entre sus características contaba con una tarjeta de video con páginas de códigos reprogramables — La mitad superior del espacio de caracteres podía ser definida por software; los caracteres cargados por omisión eran los de la página de códigos 437 (CP437), con soporte parcial para algunos lenguajes europeos, pero –debido al espacio empleado por los caracteres semigráficos para representar interfaces al usuario– nunca fueron suficientes, por lo que en general era necesario activar una página de código alternativa — Para el español, la CP850. La situación mejoró al popularizarse los entornos gráficos y dejar de depender de los caracteres semigráficos; varias hojas de código relacionadas pudieron agruparse en un menor número — En este caso, para lenguajes europeos occindentales, la ISO-8859-1.
El problema se presenta al intercambiar archivos con usuarios de otras páginas: Los datos que usan una página son indistinguibles que los que usan otra. Si compartiera un archivo ISO-8859-1 con una persona de Europa oriental (ISO-8859-2), los caracteres acentuados aparecerían modificados, aparentemente corruptos. La situación de los lenguajes de Asia oriental era mucho peor aún, dado que por la cantidad de glifos, plantear el uso de un alfabeto de 256 caracteres resultó imposible — y por muchos años, la interoperabilidad fue meramente un sueño.
En 1988, Joe Becker, Lee Collins y Mark Davis, de Xerox y Apple, se reunieron para atacar este problema de raiz: Lanzaron la iniciativa del sistema Unicode, buscando aprovechar los grandes avances de más de 20 años del cómputo para lograr un conjunto de caracteres apto para todo el mundo. Pronto su iniciativa logró captar la atención y el respaldo de otros líderes del desarrollo del cómputo.
El desarrollo de Unicode no está libre de desaciertos y peleas políticas, pero el resultado bien lo valió: Para 1996 se publicó la especificación Unicode 2.0, permitiendo un espacio de más de un millón (216+220) de puntos de código independientes, reteniendo compatibilidad hacia atrás completa con el principal esquema heredado (ISO-8859-1), derivado de CP850.
Unicode es tan grande que su representación interna no está libre de polémica e interpretaciones. Sin entrar en detalles técnicos, las dos principales representaciones son UTF-8 (utilizada en sistemas basados en Unix, y en general, para toda transmisión sobre redes de datos) y UTF-16, descendiente de UCS-2 (en uso principalmente en sistemas Windows).
No entraré mucho en detalles respecto a estas dos representaciones — Es sólo importante estar conscientes de que una cadena Unicode puede estar representada internamente de diferentes maneras; UTF-8 está basado en elementos individuales de 8 bits, mientras que el átomo en UTF-16 es de 16 bits, por lo que –especialmente con idiomas basados en el alfabeto latino– UTF-8 es más compacto (con UTF-16, el byte superior consistirá sólamente de ceros) y es sensiblemente más robusto para transmisiones sobre la red (una corrupción de datos afecta un punto mínimo, mientras que con UTF-16 puede hacer ilegible todo el texto a partir de ese punto).
Un punto importante a mantener en cuenta es que la representación binaria de texto ASCII heredado es idéntica a su representación en UTF-8 — No así con UTF-16. Para más detalles respecto a estas dos representaciones, así como las otras varias posibles, sugiero leer (Wikipedia Comparación).

¿Qué hace diferente al tratamiento de Unicode?

Hasta antes de Unicode, todo lo que teníamos que saber respecto a un esquema de codificación se limitaba a elegir la salida adecuada, o interpretar la entrada como debía ser. Y Unicode agrega muchos puntos a la mezcla. algunos de ellos:

Longitud de una cadena
Como programadores, estamos acostumbrados a que la longitud caracteres de una cadena es igual a su tamaño entes. Unicode rompe con este supuesto — Para medir langitud ahora tenemos que evaluar la cadena completa ycontrar cuáles partículas son de qué tamaño — Un caracterede medir entre uno y seis bytes, y hay además caracteresm>combinantes, que no ocupan un espacio por sí sólosno que modifican al anterior.
No todas las cadenas son válidas
Con los esquemas tradicionales de 8 bits, todo conjunto deracteres es válido. Al hablar de las representaciones deicode, hay cadenas que resultan inválidas.
Si han visto una página Web donde los caracteres con diacríticos aparecen substituídos por caracteres en forma de rombo con un signo de interrogación dentro (�, punto de código U+FFFD), éste caracter denota que el procesamiento de Unicode no pudo completarse. Sin embargo, no todas las aplicaciones son tan benignas como un navegador — Una base de datos, por ejemplo, debe negarse a guardar datos mal-formados, por lo cual debemos estar conscientes del tipo de excepciones que posiblemente recibiremos si nuestro usuario nos da datos inválidos.
Caracteres definidos semánticamente, no visualmente
Los caracteres en Unicode no buscan únicamente representar un grupo de grafías, sino que su significado. En tiempos del ASCII nos acostumbramos a utilizar la representación gráfica más cercana a un caracter. Por ejemplo, nos acostumbramos a representar a la multiplicación con el asterisco ya sea con la letra «x» o con el asterisco; con Unicode podemos usar –cual debe ser– el símbolo ✕ (U+2715). Los alemanes ya no se ven forzados a usar la β (beta griega, U+0392) en vez de la ß (Eszett o S fuerte, U+00DF).
Si bien esto es más una ventaja que nada, puede llevarnos a confusiones. Por ejemplo, si bien en ASCII con CodePage 850 sólo podemos representar a una letra acentuada de una manera (la letra á está en la posición 160), en Unicode puede representarse en este mismo punto, o como la combinación de una a minúscula (caracter 95, U+0061) y un caracter combinante de acento agudo (U+0300): á. Esto permite la gran expresividad que requieren algunos idiomas o alfabetos (ver (Wood 1999-2009) con una clara ejemplificación de la expresividad que otorgan), pero nos obliga a considerar más casos especiales al enfrentarnos a Unicode desde el punto de vista del desarrollador de sistemas.

Promesas, promesas…

Como ya lo hemos discutido en este mismo espacio, el punto que más debemos cuidar como desarrolladores son los puntos de cruce — Las barreras, las costuras en la realidad. Lo ideal sería que no hiciera falta que un componente le dijera explícitamente al otro que está listo y dispuesto para usar Unicode. Un sistema operativo instalado limpiamente el día de hoy, así como casi cualquier aplicación que instalemos sobre de éste, debe funcionar bien –y de manera predeterminada– con Unicode. Claro está, la realidad siempre se interpone — Por un lado, podemos tener que interactuar con componentes heredados. Por otro lado, no podemos olvidar el hecho de que muy rara vez podemos controlar el entorno de nuestros usuarios — Y por último, un punto donde nos toparemos ineludiblemente con problemas de codificación es al hablar con dispositivos externos.
Si bien es posible aplicar reglas heurísticas para determinar si una cadena es válida o no como UTF-8 (Dürst 1997), prácticamente cualquier protocolo que utilicemos hoy en día para transmitir contenido que deba ser interpretado como texto proporciona un campo en su encabezado para indicar el tipo de contenido a utilizar — Sin olvidar tomar en cuenta que muchos protocolos contemplan la transmisión de datos multipartes, ¡y cada una de las partes puede llevar codificaciones diferentes!

Conclusiones

Viendo sólamente estos puntos, puede que muchos de ustedes los vayan poniendo en la balanza, y les parezca que migrar a Unicode es demasiado dificil y no vale la pena. Sin embargo, sencillamente, no nos queda opción: El mundo entero va avanzando en ese sentido.
Si bien nos toca la parte más simple, por ser nativos de una cultura basada en el alfabeto dominante en Internet, no podemos ignorar que formamos parte de un mundo plenamente globalizado, y que los días de vivir en nuestra islita feliz de un sólo alfabeto han terminado.
Del mismo modo que sencillamente ya no es aceptable que una aplicación falle cuando le enviamos datos escritos correctamente acentuados, tampoco es aceptable que rechacemos registrar a personas cuyo nombre tiene origen extranjero, o que tienen una dirección de destinatario no representable con caracteres latinos. Ni es aceptable, en un mundo que tiende a la presentación de software como servicio «en la nube», no ofrecer una solución capaz de manejar los datos enviados por usuarios de todo el mundo es ofrecer una solución con baja calidad de servicio.

Referencias

AttachmentSize
Versión del artículo publicada en la revista1.3 MB