<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" gd:etag="W/&quot;C0QMRXc6fip7ImA9WhRUEEU.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233</id><updated>2012-01-20T14:43:04.916-03:00</updated><category term="linux" /><category term="xml" /><category term="i18n" /><category term="criptografía" /><category term="javascript" /><category term="php" /><category term="seguridad" /><category term="hmac" /><category term="hash" /><category term="openssl" /><category term="ssh" /><category term="tunneling" /><category term="pecl" /><category term="ntp" /><category term="rpm" /><category term="browsers" /><category term="optimizacion" /><category term="captcha" /><category term="respaldo" /><category term="css" /><category term="python" /><category term="estándares" /><category term="spam" /><category term="html" /><category term="xml signature" /><category term="accesibilidad" /><category term="gettext" /><category term="xss" /><category term="unicode" /><category term="gd" /><category term="zend" /><category term="subversion" /><category term="google" /><title>GMT-4</title><subtitle type="html">Blog sobre desarrollo web y tecnologías relacionadas.

Los temas principales a tratar son: programación en PHP, javascript, ajax, bases de datos, seguridad, apache, arquitectura de información y usabilidad.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>22</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/Gmt-4" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="gmt-4" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;C08AQHs4cCp7ImA9WxRTGEw.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-2832248745042465734</id><published>2008-09-07T12:45:00.006-04:00</published><updated>2008-09-07T14:24:01.538-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-07T14:24:01.538-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="gettext" /><category scheme="http://www.blogger.com/atom/ns#" term="i18n" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><category scheme="http://www.blogger.com/atom/ns#" term="accesibilidad" /><title>Internacionalización en PHP usando gettext</title><content type="html">En algunas oportunidades se hace necesario que nuestro sitio web tenga versiones en múltiples idiomas.&lt;br /&gt;&lt;br /&gt;El concepto de internacionalización o i18n en sitios web consiste no sólo en mostrar los contenidos en el idioma del usuario, sino que también implica ajustarse a sus configuraciones regionales tal como: formatos de fechas, formatos de números y reglas de ordenamiento alfabético. En la actualidad la mayoría de las base de datos y lenguajes de programación modernos tienen soporte para i18n.&lt;br /&gt;&lt;br /&gt;En este artículo me enfocaré en el problema de mostrar el contenido de un sitio web en múltiples idiomas, para ello les presentaré mi solución favorita basada en &lt;a href="http://www.gnu.org/software/gettext/"&gt;gettext&lt;/a&gt;, que es la biblioteca GNU para internacionalización. Los ejemplos que aquí daré están basados en PHP, pero son totalmente aplicables a otros lenguajes de programación como por ejemplo C.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Codificación&lt;/span&gt;&lt;br /&gt;Como primera medida en el proceso de internacionalización, nuestros sistemas debieran ser desarrollados utilizando codificación UTF-8 en vez de la tradicional ISO-8859-1 también conocida como LATIN1.&lt;br /&gt;&lt;br /&gt;La codificación UTF-8 prácticamente soporta la mayoría de los simbolos (y caracteres) de escritura a nivel mundial. La codificación LATIN1 es mucho más reducida en la cantidad de simbolos (o caracteres) de escritura, pero es suficiente en caso que queramos mantener nuestro sitio en español e inglés.&lt;br /&gt;&lt;br /&gt;En caso que optemos por desarrollar un sistema con codificación LATIN1 o si tenemos que dar soporte multi-idioma a sistemas heredados que ya fueron desarrollados con LATIN1, se deberán realizar algunos pasos adicionales que iré comentando en los ejemplos.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Módulo gettext&lt;/span&gt;&lt;br /&gt;Cuando generamos contenidos hacia el web, usualmente extraemos la información de dos fuentes: base de datos y textos estáticos.&lt;br /&gt;&lt;br /&gt;La traducción del contenido de la base de datos se debe realizar caso a caso de acuerdo a las características de cada sistema y no es cuerto en este artículo.&lt;br /&gt;&lt;br /&gt;La traducción de los textos estáticos se realizará usando gettext que usualmente ya está habilitada en sistemas LINUX. En Windows basta con editar el archivo de configuración de PHP y descomentar (o escribir) la siguiente linea:&lt;br /&gt;&lt;pre&gt;extension=php_gettext.dll&lt;/pre&gt;&lt;br /&gt;Adicionalmente, para generar las traducciones es necesario tener instalado gettext, el cual normalmente ya viene instalado en LINUX y puede ser descargado para Windows en la página del proyecto &lt;a href="http://sourceforge.net/projects/gnuwin32/"&gt;GNU para Windows&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Manos a la obra&lt;/span&gt;&lt;br /&gt;Usualmente en nuestro código tenemos instrucciones como las siguientes:&lt;br /&gt;&lt;pre&gt;echo "Hola mundo";&lt;br /&gt;print "Hola mundo";&lt;br /&gt;printf("Hola %s", $nombre);&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Todas esas instrucciones deberán ser modificadas para ser procesadas por la función &lt;code&gt;gettext()&lt;/code&gt; o mejor aún por un alias de esa función llamado simplemente &lt;code&gt;_()&lt;/code&gt;, con lo que tendremos los siguientes reemplazos:&lt;br /&gt;&lt;pre&gt;echo &lt;span style="font-weight: bold;"&gt;_(&lt;/span&gt;"Hola mundo"&lt;span style="font-weight: bold;"&gt;)&lt;/span&gt;;&lt;br /&gt;print &lt;span style="font-weight: bold;"&gt;_(&lt;/span&gt;"Hola mundo"&lt;span style="font-weight: bold;"&gt;)&lt;/span&gt;;&lt;br /&gt;printf(&lt;span style="font-weight: bold;"&gt;_(&lt;/span&gt;"Hola %s"&lt;span style="font-weight: bold;"&gt;)&lt;/span&gt;, $nombre);&lt;/pre&gt;&lt;br /&gt;Esta función no realiza ningún cambio en los textos. Deberemos aplicar estos cambios a todo el contenido de nuestro sistema lo que va a implicar cambiar el estilo de programación, como por ejemplo en los siguientes ejemplos:&lt;br /&gt;&lt;pre&gt;echo "Bienvenido $nombre";&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;pre&gt;echo "&lt;br /&gt;&amp;lt;h1&amp;gt;Página principal&amp;lt;/h1&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;Bienvenido $nombre&amp;lt;/p&amp;gt;&lt;br /&gt;";&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;hacemos uso de una de las características más amigables de PHP que consiste en incrustar variables directamente en un string. Para trabajar con gettext, deberemos reescribir este trozo de código. Como recomendación, les suguiero utilizar las funciones &lt;code&gt;&lt;a href="http://php.net/printf"&gt;printf()&lt;/a&gt;&lt;/code&gt; y &lt;code&gt;&lt;a href="http://php.net/sprintf"&gt;sprintf()&lt;/a&gt;&lt;/code&gt;:&lt;br /&gt;&lt;pre&gt;printf(_("Bienvenido %s"), $nombre);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;pre&gt;printf("&amp;lt;h1&amp;gt;%s&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;%s $nombre&amp;lt;/p&amp;gt;",&lt;br /&gt;_("Página principal"), _("Bienvenido"));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Generar traducciones&lt;/span&gt;&lt;br /&gt;Una vez que se hayan intervenido todos los textos del sistema, se deberá ejecutar un programa que escanea todo el código fuente buscando las intervenciones que realizamos, esto crea un archivo de traducciones donde se consolidan todos los textos encontrados, dicho archivo de texto deberá ser traducido (existen programas que ayudan a hacerlo) y luego se generará un archivo binario que contiene todas las traducciones.&lt;br /&gt;&lt;br /&gt;El primer paso es escanear todos los archivos involcados, por ejemplo para buscar todos los archivos con extensión .php y .inc se ejecuta el siguiente comando:&lt;br /&gt;&lt;pre&gt;find . -name \*.php -o -name \*.inc &gt; messages.txt&lt;/pre&gt;&lt;br /&gt;A continuación realizamos el proceso de escaneo de textos a traducir mediante el comando &lt;code&gt;xgettext&lt;/code&gt;:&lt;br /&gt;&lt;pre&gt;xgettext --language=PHP --from-code=&lt;span style="font-weight: bold;"&gt;ISO-8859-1&lt;/span&gt; -o messages.po -f messages.txt&lt;/pre&gt;&lt;br /&gt;Por defecto, este comando trabaja en UTF-8, por lo que si nuestro código está en LATIN1 (ISO-8859-1) debemos utilizar el parámetro &lt;code&gt;--from-code=&lt;span style="font-weight: bold;"&gt;ISO-8859-1&lt;/span&gt;&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;La salida de este comando nos generará un archivo de texto que deberá ser traducido, por ejemplo al inglés. La edición puede ser realizada manualmente o por medio de algún programa como por ejemplo &lt;a href="http://www.poedit.net/"&gt;Poedit&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Una vez que tengamos nuestro archivo de traducción, deberemos convertirlo (compilarlo) a formato &lt;code&gt;.mo&lt;/code&gt; mediante el comando &lt;code&gt;msgfmt&lt;/code&gt;:&lt;br /&gt;&lt;pre&gt;msgfmt messages.po -o messages.mo&lt;/pre&gt;&lt;br /&gt;Y con esto ya hemos generado nuestro archivo de traducción. Eventualmente podemos generar muchos archivos de idiomas. Ahora sólo nos falta decirle a PHP que reconozca estos archivos de idioma.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Habilitar traducciones&lt;/span&gt;&lt;br /&gt;Debemos hacerle saber al sistem dónde encontrar nuestro archivo &lt;code&gt;.mo&lt;/code&gt;. Los sistemas operativos tienen directorios predefinidos para estos fines, sin embargo, para evitar problemas de permisos y privilegios, prefiero configurar el sistema para indicarle manualmente dónde buscarlos.&lt;br /&gt;&lt;br /&gt;Como en este ejemplo hemos traducido los textos al idioma inglés, pondremos el archivo de traducción en la siguiente ubicación:&lt;br /&gt;&lt;pre&gt;/tmp/locale/en_US/LC_MESSAGES/messages.mo&lt;/pre&gt;&lt;br /&gt;Para habilitar la traducción de los textos utilizaremos el siguiente código donde la variable &lt;code&gt;$locale&lt;/code&gt; define el idioma que será utilizado que en este caso será en_US:&lt;br /&gt;&lt;pre&gt;$locale = 'en_US';&lt;br /&gt;if (!defined('LC_MESSAGES')) define('LC_MESSAGES', 6);&lt;br /&gt;setlocale(LC_MESSAGES, $locale);&lt;br /&gt;bindtextdomain('facturanet', '/tmp/locale');&lt;br /&gt;textdomain('messages');&lt;/pre&gt;&lt;br /&gt;En caso de que &lt;code&gt;$locale&lt;/code&gt; indique un idioma no definido, el sistema mostrará los textos en su idioma original. Por supuesto que deberás cambiar el directorio &lt;code&gt;/tmp/locale&lt;/code&gt; por uno más apropiado.&lt;br /&gt;&lt;br /&gt;Los archivos binarios &lt;code&gt;.mo&lt;/code&gt; son guardados en cache, por lo que luego de actualizar el archivo se recomienda reiniciar el servidor web.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Selección de idioma&lt;/span&gt;&lt;br /&gt;Debido a que la variable &lt;code&gt;$locale&lt;/code&gt; es la que determina el idioma que se presentará al usuario, les recomiendo que sea una variable de sesión de manera que el sistema recuerde el idioma del usuario entre cada página. Se debería crear una página PHP en que el usuario pueda cambiar de idioma.&lt;br /&gt;&lt;br /&gt;Para mejorar la experiencia del usuario, es posible autodetectar el idioma del usuario consultando  la variable del servidor &lt;code&gt;$_SERVER['HTTP_ACCEPT_LANGUAGE']&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Otras características&lt;/span&gt;&lt;br /&gt;Hay otras características que no alcanzaré a revisar ahora y que permiten un mejor trabajo con la boblioteca gettext:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;En caso de que modifiquemos nuestro sistema y tengamos que realizar nuevas traducciones, no es necesario repetir todo el proceso, gettext provee comandos para incorporar nuevas palabras a un archivo ya existente mediante &lt;code&gt;&lt;a href="http://www.gnu.org/software/automake/manual/gettext/msgmerge-Invocation.html"&gt;msgmerge&lt;/a&gt;&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Además de la función _() existe otras &lt;a href="http://php.net/gettext"&gt;funciones gettext&lt;/a&gt; para realizar traducciones más complejas, como por ejemplo &lt;a href="http://php.net/ngettext"&gt;diferenciación de singuales y plurales&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-2832248745042465734?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/2832248745042465734/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=2832248745042465734" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/2832248745042465734?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/2832248745042465734?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/09/internacionalizacion-en-php-usando.html" title="Internacionalización en PHP usando gettext" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;A0MMQnwzfCp7ImA9WxdQGEk.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-1740736716928579539</id><published>2008-06-19T00:24:00.000-04:00</published><updated>2008-06-19T00:24:43.284-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-19T00:24:43.284-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="google" /><category scheme="http://www.blogger.com/atom/ns#" term="optimizacion" /><title>El performance de Google App Engine</title><content type="html">Hace unos días estuve probando la nueva plataforma &lt;a href="http://code.google.com/appengine/"&gt;Google App Engine&lt;/a&gt; la cual permite hostear en los servidores de Google aplicaciones WEB desarrolladas en Python.&lt;br /&gt;&lt;br /&gt;El modelo de negocio de Google es similar al ofrecido por Amazon, en el cual se cobra de acuerdo al tráfico, almacenamiento de datos y tiempo de CPU efectivamente utilizados. Sin embargo, Google va más allá (o provee un servicio más acotado, según el punto de vista) al proveer de una plataforma de desarrollo basada en Python.&lt;br /&gt;&lt;br /&gt;Además de la estabilidad y disponibidad de las aplicaciones desarrolladas sobre Google App Engine, uno de las características importantes es la escalabilidad de su plataforma.&lt;br /&gt;&lt;br /&gt;Construí una aplicación básica que hace un par de consultas a la base de datos y que genera un documento HTML de aproximadamente 1KB de información. Luego, con la herramienta &lt;a href="http://httpd.apache.org/docs/2.0/programs/ab.html"&gt;Apache Benchmark (ab)&lt;/a&gt; realicé pruebas de carga utilizando un enlace a Internet dedicado. La prueba consistió en cargar dicha página de manera concurrente durante 5 segundos. En cada prueba modifiqué el nivel de concurrencia desde 1 hasta 100 requests en paralelo.&lt;br /&gt;&lt;br /&gt;En promedio el tiempo de carga de la página completa demoraba 500 milisegundos, lo cual es bastante tiempo para una página liviana y que no hace más de 10 consultas SQL. Sin embargo, las pruebas de concurrencia me dejaron impresionado ya que el nivel de servicio prácticamente no se vió afectado aunque haya aumentado a 100 conexiones en paralelo, tal como lo muestro en el siguiente gráfico.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_6K2axIH8bMw/SFm1G6mX2xI/AAAAAAAAAJM/MZ_xch6vCNE/s1600-h/requests_concurrentes.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_6K2axIH8bMw/SFm1G6mX2xI/AAAAAAAAAJM/MZ_xch6vCNE/s400/requests_concurrentes.png" alt="" id="BLOGGER_PHOTO_ID_5213397174072105746" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Sin concurrencia se alcanzó un nivel de 2.33 requests por segundo, al utilizar una concurrencia de 10 conexiones simultaneas se logró un nivel de 23.98 requests por segundo, finalmente, al alcanzar un nivel de 100 conexiones simultaneas se logró un nivel del 192.38 requests por segundo, es decir el servicio proveyó de un escalamiento lineal prácticamente de 1:1. Hasta ahora, disponer de enlace y hardware capaces de brindar dicho nivel de escalabilidad era extremadamente caro.&lt;br /&gt;&lt;br /&gt;Sin duda, este nuevo producto de Google será un gran producto para los desarrolladores de software, brindando un gran nivel de escalabilidad a un costo bastante bajo.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-1740736716928579539?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/1740736716928579539/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=1740736716928579539" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1740736716928579539?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1740736716928579539?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/06/el-performance-de-google-app-engine.html" title="El performance de Google App Engine" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_6K2axIH8bMw/SFm1G6mX2xI/AAAAAAAAAJM/MZ_xch6vCNE/s72-c/requests_concurrentes.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;Ck4DQXY6fyp7ImA9WxdQEkk.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-7170265852662251599</id><published>2008-06-11T23:18:00.002-04:00</published><updated>2008-06-11T23:22:50.817-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-11T23:22:50.817-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="captcha" /><category scheme="http://www.blogger.com/atom/ns#" term="seguridad" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>Cool-php-captcha</title><content type="html">En vista que había dedicado varias horas a implementar un sistema de captcha, aproveché de invertir un par de horas más y crear un proyecto que llamé &lt;a href="http://code.google.com/p/cool-php-captcha/"&gt;cool-php-captcha&lt;/a&gt; publicado en Google Code bajo licencia GPLv3.&lt;br /&gt;&lt;br /&gt;Los invito a visitar la página del proyecto &lt;a href="http://code.google.com/p/cool-php-captcha/"&gt;http://code.google.com/p/cool-php-captcha&lt;/a&gt; y darme sus opiniones.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-7170265852662251599?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/7170265852662251599/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=7170265852662251599" title="2 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/7170265852662251599?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/7170265852662251599?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/06/cool-php-captcha.html" title="Cool-php-captcha" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;D08BQHwyfip7ImA9WxdRE0U.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-5805338166762092289</id><published>2008-06-02T01:32:00.003-04:00</published><updated>2008-06-02T01:50:51.296-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-02T01:50:51.296-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="gd" /><category scheme="http://www.blogger.com/atom/ns#" term="captcha" /><category scheme="http://www.blogger.com/atom/ns#" term="seguridad" /><category scheme="http://www.blogger.com/atom/ns#" term="google" /><title>Crear un CAPTCHA como el de Google</title><content type="html">Esta es la segunda parte de mi artículo sobre la creación de CAPTCHAS. En &lt;a href="http://gmt-4.blogspot.com/2008/05/creacion-de-un-captha-o-test-de-turing.html"&gt;el artículo anterior&lt;/a&gt; vimos el procedimiento para crear un CAPTCHA básico al cual le agregamos algunas mejoras para aumentar su complejidad y así disminuir su vulnerabilidad.&lt;br /&gt;&lt;br /&gt;En esta segunda parte vamos a realizar algunas mejoras en la generación de imágenes, incorporaremos nuevas tipografías (la mayoría son OpenSource o Freeware) y optimizaremos el código.&lt;br /&gt;&lt;br /&gt;Con estos cambios obtendremos un CAPTCHA de muy buena calidad sin recurrir al uso de "artefactos" o imágenes de fondo que conviertan el CAPTCHA en algo horrible, como los siguientes que son utilizados por algunos de los grandes sitios de internet: Delicious, Facebook, Fotolog, Live Journal y Microsoft Live:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_6K2axIH8bMw/SENmXXTi0TI/AAAAAAAAAH0/NJXeNB_1Vx8/s1600-h/captchas.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_6K2axIH8bMw/SENmXXTi0TI/AAAAAAAAAH0/NJXeNB_1Vx8/s400/captchas.jpg" alt="" id="BLOGGER_PHOTO_ID_5207118145749438770" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Más bien, el resultado final que vamos a obtener va a ser un CAPTCHA muy parecido a los generados por Google:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_6K2axIH8bMw/SENnL3Ti0UI/AAAAAAAAAH8/IDTKm-TV098/s1600-h/c1.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_6K2axIH8bMw/SENnL3Ti0UI/AAAAAAAAAH8/IDTKm-TV098/s400/c1.jpg" alt="" id="BLOGGER_PHOTO_ID_5207119047692570946" border="0" /&gt;&lt;/a&gt;Si bien este tipo de imagen no es de las más seguros, es bastante mejor que la mayoría de los captchas que están disponibles en la red.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Texto a utilizar en el CAPTCHA&lt;/span&gt;&lt;br /&gt;En el ejemplo del artículo anterior utilizabamos un generador de textos aleatorios que intercalaba vocales y consonantes con el fin de generar palabras medianamente pronunciables.&lt;br /&gt;&lt;br /&gt;En esta versión final inicialmente desarrollé una mejora de dicha función ya que la original ponía las vocales siempre en las mismas posiciones lo cual hacía más fácil la decodificación del captcha.&lt;br /&gt;&lt;br /&gt;Esta nueva función genera textos aleatorios alternando consonantes y vocales  al azar:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;function getCaptchaText() {&lt;br /&gt;$length = rand(5,7);&lt;br /&gt;$consonants = "abcdefghijlmnopqrstvwyz";&lt;br /&gt;$vocals = "aeiou";&lt;br /&gt;$text = "";&lt;br /&gt;$vocal = rand(0,1);&lt;br /&gt;for ($i=0; $i&amp;lt;$length; $i++) {&lt;br /&gt;if ($vocal) {&lt;br /&gt;  $text .= substr($vocals, mt_rand(0, 4), 1);&lt;br /&gt;} else {&lt;br /&gt;  $text .= substr($consonants, mt_rand(0, 22), 1);&lt;br /&gt;}&lt;br /&gt;$vocal = !$vocal;&lt;br /&gt;}&lt;br /&gt;return $text;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Palabras de diccionario&lt;/span&gt;&lt;br /&gt;Sin embargo, al realizar algunas pruebas me dí cuenta que al utilizar palabras de diccionario disminuye considerablemente el tiempo de ingreso y la tasa de error de usuarios. Además, el hecho de utilizar palabras "conocidas por el usuario" permite generar caracteres con mayor deformación.&lt;br /&gt;&lt;br /&gt;Para la generación de palabras de diccionario,  descargué un archivo de palabras en español (también puede ser en inglés u otro idioma según el público objetivo) desde &lt;a href="http://www.word-list.com/"&gt;http://www.word-list.com&lt;/a&gt;. Solamente mantuve las palabras entre 5 y 7 caracteres de longitud lo cual me dejó con solo 20.645 palabras diferentes.&lt;br /&gt;&lt;br /&gt;No debemos olvidar quitar del diccionario palabras que podrían ser ofensivas para nuestros usuarios como por ejemplo: "zorra", "tonto", "mierda", "violador", "suicida" o "asesino".&lt;br /&gt;&lt;br /&gt;En principio, para escoger la palabra de diccionario a utilizar, se debiera escoger un número al azar y luego recorrer el archivo de palabras línea por línea mediante &lt;code&gt;fgets()&lt;/code&gt; hasta llegar a la línea buscada. Dicho procesamiento es altamente ineficiente por lo que hice algunos ajustes para hacer uso de &lt;code&gt;fseek()&lt;/code&gt;: Cada una de las lineas del archivo de palabras las rellené con espacios en blanco hasta totalizar 7 caracteres más el caracter de salto de linea (&lt;code&gt;\n&lt;/code&gt;), con lo cual quedan 8 bytes por cada palabra. Por lo tanto,  si quisiera obtener la palabra de la posición 388, se debería hacer &lt;code&gt;fseek(387*8)&lt;/code&gt; para luego leer la linea &lt;code&gt;fgets()&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;function getDictionaryCaptchaText() {&lt;br /&gt;   $fp = fopen("words-es.txt", "r");&lt;br /&gt;   $linea = rand(0, (filesize("words-es.txt")/8)-1);&lt;br /&gt;   fseek($fp, 8*$linea);&lt;br /&gt;   $text = trim(fgets($fp));&lt;br /&gt;   fclose($fp);&lt;br /&gt;   return $text;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;La desventaja de utilizar textos de diccionario es que el universo de palabras a utilizar disminuye de millones (entre 2 y 40 millones al utilizar hasta 7 caracteres) a miles (20 mil al utilizar palabras entre 5 y 7 caracteres).&lt;br /&gt;&lt;br /&gt;Como una opción para solucionar este problema introduje una modificación que cambia al azar algunas de las vocales de una palabra, por ejemplo la palabra "caviar" puede cambiar a "cuvier", "covoar", "cavour", etc.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function getDictionaryCaptchaText($extended = true) {&lt;br /&gt;   $fp = fopen("words-es.txt", "r");&lt;br /&gt;   $linea = rand(0, (filesize("words-es.txt")/8)-1);&lt;br /&gt;   fseek($fp, 8*$linea);&lt;br /&gt;   $text = trim(fgets($fp));&lt;br /&gt;   fclose($fp);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   // Cambio vocales al azar&lt;br /&gt;   if ($extended) {&lt;br /&gt;       $text = str_split($text, 1);&lt;br /&gt;       $vocals = array('a','e','i','o','u');&lt;br /&gt;       foreach ($text as $i =&amp;gt; $char) {&lt;br /&gt;           if (mt_rand(0,1) &amp;amp;&amp;amp; in_array($char, $vocals)) {&lt;br /&gt;               $text[$i] = $vocals[mt_rand(0,4)];&lt;br /&gt;           }&lt;br /&gt;       }&lt;br /&gt;       $text = implode('', $text);&lt;br /&gt;   }&lt;br /&gt;   return $text;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Como opción personal voy a utilizar simplemente las palabras de diccionario. Además, como mejora podría modificarse el script para que utilice como diccionario palabras en el idioma del usuario. Esto se puede realizar determinando el país del usuario de acuerdo a su dirección IP o mediante el parámetro HTTP HTTP_ACCEPT_LANGUAGE el cual indica el idioma de preferencia del usuario.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Generación de la imagen&lt;/span&gt;&lt;br /&gt;En la generación de la imagen se van a aplicar las siguientes transformaciones:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Disminuir el espaciado entre caracteres, incluso traslapando algunos caracteres.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Variación de tamaños de caracteres.&lt;/li&gt;&lt;li&gt;Diversidad de tipografías (pero utilizando una sola tipografía por cada imágen).&lt;/li&gt;&lt;li&gt;Alternación de color de textos.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Ondulación del texto en los ejes X e Y.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;1. Disminuir el espaciado entre caracteres&lt;/span&gt;&lt;br /&gt;Las tipografías están construidas para mantener una sepación específica entre cada caracter para aumentar la legibilidad. Sin embargo, esto es precisamente lo que no se debe hacer al generar un captcha. Al juntar los caracteres y permitir su traslape se dificulta el proceso de separación caractar a caracter, esto es muy importante porque la extracción de cada uno de los caracteres es uno de los pasos escenciales en el proceso de decodificación de captchas.&lt;br /&gt;&lt;br /&gt;En las siguientes imagenes vemos cómo el texto de la derecha tiene sus caracteres muy juntos.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_6K2axIH8bMw/SEN1i3Ti0XI/AAAAAAAAAIU/hfz8m8UVToE/s1600-h/condensacion.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_6K2axIH8bMw/SEN1i3Ti0XI/AAAAAAAAAIU/hfz8m8UVToE/s400/condensacion.jpg" alt="" id="BLOGGER_PHOTO_ID_5207134835992351090" border="0" /&gt;&lt;/a&gt;Para lograr disminuir el espaciado entre cada caracter tuve que imprimir en la imagen caracter por caracter con el siguiente código:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;// Texto a imprimir&lt;br /&gt;$text = getDictionaryCaptchaText($extended);&lt;br /&gt;// Definiciones de latipografía a utilizar&lt;br /&gt;// - font: archivo TTF&lt;br /&gt;// - condensation: Cantidad de pixeles que se quitará entre cada caracter&lt;br /&gt;$fontcfg = $fonts[mt_rand(0, sizeof($fonts)-1)];&lt;br /&gt;$fontsize = 32;&lt;br /&gt;$x = 20;&lt;br /&gt;for ($i=0; $i&amp;lt;=6; $i++) {&lt;br /&gt;   $coords = imagettftext($im, $fontsize, 0,&lt;br /&gt;           $x, 47, $fg_color, 'fonts/'.$fontcfg['font'], substr($text, $i, 1));&lt;br /&gt;   $x += ($coords[2]-$x)-$fontcfg['condensation'];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;2. Variación de tamaños de caracteres&lt;/span&gt;&lt;br /&gt;La primera alteración a realizar es el cambio del tamaño de cada caracter como se muestra a continuación:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_6K2axIH8bMw/SENy8nTi0VI/AAAAAAAAAIE/lxSQkU8r4Vw/s1600-h/size.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_6K2axIH8bMw/SENy8nTi0VI/AAAAAAAAAIE/lxSQkU8r4Vw/s400/size.png" alt="" id="BLOGGER_PHOTO_ID_5207131979839099218" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;pre&gt;// Texto a imprimir&lt;br /&gt;$text = getDictionaryCaptchaText($extended);&lt;br /&gt;&lt;br /&gt;// Definiciones de latipografía a utilizar&lt;br /&gt;// - font: archivo TTF&lt;br /&gt;// - minSize: tamaño de fuente mínimo a utilizar&lt;br /&gt;// - maxSize: tamaño de fuente máximo a utilizar&lt;br /&gt;// - condensation: Cantidad de pixeles que se quitará entre cada caracter&lt;br /&gt;$fontcfg = $fonts[mt_rand(0, sizeof($fonts)-1)];&lt;br /&gt;&lt;br /&gt;$x = 20;&lt;br /&gt;for ($i=0; $i&amp;lt;=6; $i++) {&lt;br /&gt;   $coords = imagettftext($im, rand($fontcfg['minSize'],$fontcfg['maxSize']), 0,&lt;br /&gt;           $x, 47, $fg_color, 'fonts/'.$fontcfg['font'], substr($text, $i, 1));&lt;br /&gt;   $x += ($coords[2]-$x)-$fontcfg['condensation'];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;3. Diversidad de tipografías&lt;/span&gt;&lt;br /&gt;Escogí un conjunto de tipografías que alterno entre cada generación. En principio quise alternar la tipografía al generar cada caracter, pero el resultado no era muy vistoso, por lo tanto ahora utilizo la misma tipografía para todos los caracteres:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_6K2axIH8bMw/SENzonTi0WI/AAAAAAAAAIM/ugs2-KJELCc/s1600-h/font.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_6K2axIH8bMw/SENzonTi0WI/AAAAAAAAAIM/ugs2-KJELCc/s400/font.jpg" alt="" id="BLOGGER_PHOTO_ID_5207132735753343330" border="0" /&gt;&lt;/a&gt;No debemos olvidar que la mayoría de las tipografías que están instaladas en nuestros computadores están protegidas por copyright, por lo que buscando en internet descargué varias tipografías OpenSource o Freeware desde el sitio &lt;a href="http://www.urbanfonts.com/"&gt;http://www.urbanfonts.com&lt;/a&gt;. Con un poco de paciencia se pueden encontrar tipografías que permiten generar captchas más originales:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_6K2axIH8bMw/SEN-DXTi0bI/AAAAAAAAAI0/Rw9qYJfODD8/s1600-h/fonts3.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_6K2axIH8bMw/SEN-DXTi0bI/AAAAAAAAAI0/Rw9qYJfODD8/s400/fonts3.jpg" alt="" id="BLOGGER_PHOTO_ID_5207144190431121842" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;4. Alternación de color de textos&lt;/span&gt;&lt;br /&gt;Principalmente por un tema estético introduje alternación entre 3 colores posibles para el captcha. Algunos podrían tentarse de poner cada caracter en diferente color, pero esto sería un error porque facilitaría al decodificador separar cada caracter.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_6K2axIH8bMw/SEN25HTi0YI/AAAAAAAAAIc/Old3TGV--3U/s1600-h/color.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_6K2axIH8bMw/SEN25HTi0YI/AAAAAAAAAIc/Old3TGV--3U/s400/color.jpg" alt="" id="BLOGGER_PHOTO_ID_5207136317756068226" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Para definir el color, defino un arreglo con los colores RGB y luego obtengo el color escogido al azar:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$colors = array(&lt;br /&gt; array(27,78,181), // azul&lt;br /&gt; array(22,163,35), // verde&lt;br /&gt; array(214,36,7),  // rojo&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;$color = $colors[mt_rand(0, sizeof($colors)-1)];&lt;br /&gt;$fg_color = imagecolorallocate($im, $color[0], $color[1], $color[2]);&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;5. Ondulación del texto en los ejes X e Y&lt;/span&gt; Finalmente aplicamos una transformación al texto para en base a una función senoidal. Esto lo hacemos en el eje X e Y. Como una imágen vale más que mil palabras, en el siguiente ejemplo muestro el proceso de transformación: se comienza con una imagen plana (en color rojo) a la cual le aplico un filtro "wave" en el eje X (color verde) y en el eje Y (color azul). Una vez que apliquemos este filtro en ambos ejes obtenemos como resultado la imagen de la derecha (en color verde):&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_6K2axIH8bMw/SEN6E3Ti0ZI/AAAAAAAAAIk/rS84mX4Srbw/s1600-h/wave.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_6K2axIH8bMw/SEN6E3Ti0ZI/AAAAAAAAAIk/rS84mX4Srbw/s400/wave.jpg" alt="" id="BLOGGER_PHOTO_ID_5207139818154414482" border="0" /&gt;&lt;/a&gt;La generación de ondulación se realiza mediante la función &lt;code&gt;imagecopy()&lt;/code&gt; que va desplazando linea por linea la imagen de acuerdo a una función senoidal. Esto se realiza en el eje X y luego en el eje Y. Se define una variable aleaoria &lt;code&gt;$k&lt;/code&gt; para definir la "fase" o ángulo inicial de la ondulación. La forma de la onda senoidal se define por la amplitud (valor mas alto y bajo de la onda) y el período que define la "frecuencia":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;// Genero ondas verticales (eje X)&lt;br /&gt;$period    = $scale*$periodoX;&lt;br /&gt;$amplitude = $scale*$amplitudX;&lt;br /&gt;$k         = rand(0,100);&lt;br /&gt;for ($i = 0;$i &amp;lt; ($width*$scale);$i++) {&lt;br /&gt; imagecopy($im,$im,&lt;br /&gt;           $i-1, sin($k+$i/$period) * $amplitude,&lt;br /&gt;           $i,0,&lt;br /&gt;           1,$height*$scale);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Genero ondas horizontales (eje Y)&lt;br /&gt;$period    = $scale*$periodoY;&lt;br /&gt;$amplitude = $scale*$amplitudY;&lt;br /&gt;$k         = rand(0,100);&lt;br /&gt;for ($i = 0;$i &amp;lt; ($height*$scale);$i++) {&lt;br /&gt;   imagecopy($im,$im,&lt;br /&gt;           sin($k+$i/$period) * $amplitude, $i-1,&lt;br /&gt;           0,$i,&lt;br /&gt;          $width*$scale,1);&lt;br /&gt;}  &lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Juntando las piezas&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;Una vez que se junten todos estos pasos y parametrizando &lt;/span&gt;algunas cosas obtenemos el siguiente resultado:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_6K2axIH8bMw/SEOHEHTi0dI/AAAAAAAAAJE/WPKabRqc0L4/s1600-h/captchas-final2.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_6K2axIH8bMw/SEOHEHTi0dI/AAAAAAAAAJE/WPKabRqc0L4/s400/captchas-final2.jpg" alt="" id="BLOGGER_PHOTO_ID_5207154098920673746" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Como se pueden dar cuenta -por pura coincidencia- se generaron varias palabras potencialmente ofensivas que idealmente no debieran presentarse al usuario.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;El programa final es el siguiente:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;(recuerda descargar los archivos de tipografía desde algún sitio como por ejemplo&lt;span style="text-decoration: underline;"&gt; &lt;/span&gt;&lt;a href="http://www.urbanfonts.com/"&gt;http://www.urbanfonts.com&lt;/a&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;?php&lt;br /&gt;/**&lt;br /&gt;* Script para la generación de CAPTCHAS&lt;br /&gt;*&lt;br /&gt;* Autor: José Rodríguez jose.rodriguez at exec.cl&lt;br /&gt;* http://joserodriguez.cl&lt;br /&gt;* http://www.exec.cl&lt;br /&gt;*&lt;br /&gt;* En caso que hagas uso de este código o una variación,&lt;br /&gt;* te pido que me lo hagas saber.&lt;br /&gt;*&lt;br /&gt;* Esta obra está licenciada bajo Creative Commons&lt;br /&gt;* Reconocimiento - No comercial - Compartir bajo la misma licencia 3.0 Unported License.&lt;br /&gt;* http://creativecommons.org/licenses/by-nc-sa/3.0/&lt;br /&gt;*&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Alto y ancho de la imagen&lt;br /&gt;$width  = 200;&lt;br /&gt;$height = 66;&lt;br /&gt;&lt;br /&gt;// Nombre de la variable de sesion&lt;br /&gt;$session_var = "captcha";&lt;br /&gt;&lt;br /&gt;// Colores&lt;br /&gt;$colors = array(&lt;br /&gt;   array(27,78,181), // azul&lt;br /&gt;   array(22,163,35), // verde&lt;br /&gt;   array(214,36,7),  // rojo&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Configuración de tipografías&lt;br /&gt;* - font: archivo TTF&lt;br /&gt;* - condensation: cantidad de pixeles que se juntará cada caracter&lt;br /&gt;* - minSize: tamaño minimo del texto&lt;br /&gt;* - maxSize: tamaño máximo del texto&lt;br /&gt;*/&lt;br /&gt;$fonts = array(&lt;br /&gt;   array('font' =&amp;gt; 'Danoisemedium.ttf','condensation' =&amp;gt; 2,   'minSize' =&amp;gt; 28, 'maxSize' =&amp;gt; 40),&lt;br /&gt;   array('font' =&amp;gt; 'HEINEKEN.TTF',     'condensation' =&amp;gt; 2.5, 'minSize' =&amp;gt; 24, 'maxSize' =&amp;gt; 40),&lt;br /&gt;   array('font' =&amp;gt; 'VeraSeBd.ttf',     'condensation' =&amp;gt; 3.5, 'minSize' =&amp;gt; 20, 'maxSize' =&amp;gt; 33),&lt;br /&gt;   array('font' =&amp;gt; 'VeraSe.ttf',       'condensation' =&amp;gt; 4,   'minSize' =&amp;gt; 26, 'maxSize' =&amp;gt; 40),&lt;br /&gt;   array('font' =&amp;gt; 'CrazyHarold.ttf',  'condensation' =&amp;gt; 2,   'minSize' =&amp;gt; 20, 'maxSize' =&amp;gt; 28),&lt;br /&gt;   array('font' =&amp;gt; 'Duality.ttf',      'condensation' =&amp;gt; 2,   'minSize' =&amp;gt; 28, 'maxSize' =&amp;gt; 48),&lt;br /&gt;   /*&lt;br /&gt;   // Otras tipografías&lt;br /&gt;   array('font' =&amp;gt; 'BeyondWonderland.ttf', 'condensation' =&amp;gt; 3, 'minSize' =&amp;gt; 28, 'maxSize' =&amp;gt; 39),&lt;br /&gt;   array('font' =&amp;gt; 'BennyBlanco.ttf',      'condensation' =&amp;gt; 1, 'minSize' =&amp;gt; 24, 'maxSize' =&amp;gt; 30),&lt;br /&gt;   array('font' =&amp;gt; 'freak.ttf',            'condensation' =&amp;gt; 2, 'minSize' =&amp;gt; 32, 'maxSize' =&amp;gt; 54),&lt;br /&gt;   */&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;// Configuración de ondulacion del texto&lt;br /&gt;// Periodo y amplitud en ejes X e Y&lt;br /&gt;$periodoY  = 15;&lt;br /&gt;$amplitudY = 16;&lt;br /&gt;$periodoX  = 12;&lt;br /&gt;$amplitudX = 4;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Factor de resolución con que se trabajará internamente&lt;br /&gt;* Se prefiere manipular la imagen al doble de su tamaño&lt;br /&gt;* para evitar pérdida de calidad al aplicar filtro wave.&lt;br /&gt;* Valores posibles: 1, 2 o 3.&lt;br /&gt;*/&lt;br /&gt;$scale = 2;&lt;br /&gt;&lt;br /&gt;// Utilizar palabras inexistentes?&lt;br /&gt;$extended = false;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Permite habilitar depurado&lt;br /&gt;$debug = false;&lt;br /&gt;$ini = microtime(true);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;session_start();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Creo la imagen&lt;br /&gt;$im = imagecreatetruecolor($width*$scale, $height*$scale);&lt;br /&gt;$bg_color = imagecolorallocate($im, 255, 255, 255);&lt;br /&gt;$color = $colors[mt_rand(0, sizeof($colors)-1)];&lt;br /&gt;$fg_color = imagecolorallocate($im, $color[0], $color[1], $color[2]);&lt;br /&gt;imagefilledrectangle($im, 0, 0, $width*$scale, $height*$scale, $bg_color);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Genero el texto, caracter por caracter&lt;br /&gt;$text = getDictionaryCaptchaText($extended);&lt;br /&gt;$fontcfg = $fonts[mt_rand(0, sizeof($fonts)-1)];&lt;br /&gt;$x = 20*$scale;&lt;br /&gt;for ($i=0; $i&amp;lt;=6; $i++) {&lt;br /&gt;  $coords = imagettftext($im, rand($fontcfg['minSize'],$fontcfg['maxSize'])*$scale, 0,&lt;br /&gt;             $x, 47*$scale, $fg_color, 'fonts/'.$fontcfg['font'], substr($text, $i, 1));&lt;br /&gt;  $x += ($coords[2]-$x)-$fontcfg['condensation']*$scale;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Genero ondas verticales (eje X)&lt;br /&gt;$period    = $scale*$periodoX;&lt;br /&gt;$amplitude = $scale*$amplitudX;&lt;br /&gt;$k         = rand(0,100);&lt;br /&gt;for ($i = 0;$i &amp;lt; ($width*$scale);$i++) {&lt;br /&gt;   imagecopy($im,$im,&lt;br /&gt;             $i-1, sin($k+$i/$period) * $amplitude,&lt;br /&gt;             $i,0,&lt;br /&gt;             1,$height*$scale);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Genero ondas horizontales (eje Y)&lt;br /&gt;$period    = $scale*$periodoY;&lt;br /&gt;$amplitude = $scale*$amplitudY;&lt;br /&gt;$k         = rand(0,100);&lt;br /&gt;for ($i = 0;$i &amp;lt; ($height*$scale);$i++) {&lt;br /&gt;   imagecopy($im,$im,&lt;br /&gt;             sin($k+$i/$period) * $amplitude, $i-1,&lt;br /&gt;             0,$i,&lt;br /&gt;             $width*$scale,1);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Reduzco el tamaño de la imagen&lt;br /&gt;$imResampled = imagecreatetruecolor($width, $height);&lt;br /&gt;imagecopyresampled ($imResampled,$im,0,0,0,0,$width, $height,$width*$scale,$height*$scale);&lt;br /&gt;imagedestroy($im);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Guardo el texto en sesión&lt;br /&gt;$_SESSION[$session_var] = $text;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if ($debug) {&lt;br /&gt;   imagestring($imResampled, 1, 1, $height-8, "$text k:$k ".$fontcfg['font'].' '.round((microtime(true)-$ini)*1000), $fg_color);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;header("Content-type: image/jpeg");&lt;br /&gt;imagejpeg($imResampled, null, 80);&lt;br /&gt;&lt;br /&gt;// Limpieza&lt;br /&gt;imagedestroy($imResampled);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Retorna un texto de diccionario aleatorio&lt;br /&gt;*/&lt;br /&gt;function getDictionaryCaptchaText($extended = false) {&lt;br /&gt;   $fp = fopen("words-es.txt", "r");&lt;br /&gt;   $linea = rand(0, (filesize("words-es.txt")/8)-1);&lt;br /&gt;   fseek($fp, 8*$linea);&lt;br /&gt;   $text = trim(fgets($fp));&lt;br /&gt;   fclose($fp);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   // Cambio vocales al azar&lt;br /&gt;   if ($extended) {&lt;br /&gt;       $text = str_split($text, 1);&lt;br /&gt;       $vocals = array('a','e','i','o','u');&lt;br /&gt;       foreach ($text as $i =&amp;gt; $char) {&lt;br /&gt;           if (mt_rand(0,1) &amp;amp;&amp;amp; in_array($char, $vocals)) {&lt;br /&gt;               $text[$i] = $vocals[mt_rand(0,4)];&lt;br /&gt;           }&lt;br /&gt;       }&lt;br /&gt;       $text = implode('', $text);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   return $text;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Retorna un texto aleatorio&lt;br /&gt;*/&lt;br /&gt;function getCaptchaText() {&lt;br /&gt;   $length = rand(5,7);&lt;br /&gt;   $consonants = "abcdefghijlmnopqrstvwyz";&lt;br /&gt;   $vocals = "aeiou";&lt;br /&gt;   $text = "";&lt;br /&gt;   $vocal = rand(0,1);&lt;br /&gt;   for ($i=0; $i&amp;lt;$length; $i++) {&lt;br /&gt;       if ($vocal) {&lt;br /&gt;           $text .= substr($vocals, mt_rand(0, 4),  1);&lt;br /&gt;       } else {&lt;br /&gt;           $text .= substr($consonants, mt_rand(0, 22), 1);&lt;br /&gt;       }&lt;br /&gt;       $vocal = !$vocal;&lt;br /&gt;   }&lt;br /&gt;   return $text;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;?&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-5805338166762092289?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/5805338166762092289/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=5805338166762092289" title="5 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/5805338166762092289?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/5805338166762092289?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/06/crear-un-captcha-como-el-de-google.html" title="Crear un CAPTCHA como el de Google" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_6K2axIH8bMw/SENmXXTi0TI/AAAAAAAAAH0/NJXeNB_1Vx8/s72-c/captchas.jpg" height="72" width="72" /><thr:total>5</thr:total></entry><entry gd:etag="W/&quot;DEICRX85eip7ImA9WxdRE0s.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-1884346475966379303</id><published>2008-05-28T00:15:00.008-04:00</published><updated>2008-06-01T20:29:24.122-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-01T20:29:24.122-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="captcha" /><category scheme="http://www.blogger.com/atom/ns#" term="spam" /><category scheme="http://www.blogger.com/atom/ns#" term="seguridad" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>Creación de un CAPTCHA (o test de turing)</title><content type="html">A esta altura, la mayoría de nosotros nos hemos encontrado en situaciones que cuando ingresamos información en un formulario para registrarnos en algún servicio -como por ejemplo crear una casilla de correo o descargar un archivo de servicios como rapidshare- el sitio nos pide que escribamos el texto de una imagen que vemos en pantalla.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_6K2axIH8bMw/SDy46HTi0HI/AAAAAAAAAGU/_wWjQkcGWZk/s1600-h/captcha2.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_6K2axIH8bMw/SDy46HTi0HI/AAAAAAAAAGU/_wWjQkcGWZk/s400/captcha2.jpg" alt="" id="BLOGGER_PHOTO_ID_5205238577866395762" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Estos mecanismos de validación permiten -en teoría- distinguir entre humanos y computadores, y así prevenir abusos como por ejemplo la publicación de spams en los comentarios de blogs y la creación masiva de casillas de correo para spamming.&lt;br /&gt;&lt;br /&gt;En los últimos años han proliferado diversos mecanismos de &lt;a href="http://es.wikipedia.org/wiki/Captcha"&gt;CAPTCHA&lt;/a&gt; (&lt;span style="font-style: italic;"&gt;Completely Automated Public Turing test to tell Computers and Humans Apart&lt;/span&gt;), como por ejemplo resolver un problema matemático, indicar el animal que ves es una fotografía o -el más popular- identificar el texto que ves en una imágen.&lt;br /&gt;&lt;br /&gt;Una de las desventajas de los CAPTCHAs es que tienen serias deficiencias de accesibilidad. Además existen algunos casos extremadamente molestos para el usuario como el siguiente ejemplo utilizado en Rapidshare:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_6K2axIH8bMw/SDy5yXTi0II/AAAAAAAAAGc/1ehcTxZ1eOg/s1600-h/captcha-rapidshare.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_6K2axIH8bMw/SDy5yXTi0II/AAAAAAAAAGc/1ehcTxZ1eOg/s400/captcha-rapidshare.jpg" alt="" id="BLOGGER_PHOTO_ID_5205239544234037378" border="0" /&gt;&lt;/a&gt;Debido a que la mayoria de los desarrollos de software en que he participado son sistemas cerrados o que no son un potencial blanco de ataques de spamming, no me he visto en la necesidad de implementar mecanismos de CAPTCHA, sin embargo, haciendo algunos experimentos he llegado a implementar un sistema sencillo en PHP que les describo a continuación.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Implementación de CAPTCHA en PHP&lt;/span&gt;&lt;br /&gt;En este ejercicio voy a generar un CAPTCHA que le presente al usuario una imagen con letras que el usuario deberá escribir en un formulario. Debido al uso de imágenes, me apoyaré en la biblioteca &lt;a href="http://www.libgd.org/Main_Page"&gt;GD&lt;/a&gt;, por lo que para estos ejemplos PHP debe tener soporte para GD.&lt;br /&gt;&lt;br /&gt;Mi objetivo es generar un captcha que nos brinde un nivel de seguridad mínima y en ningún caso desarrollar un sistema infalible. Detrás de la generación (y decodificación) de los captchas existen teorías de procesamiento de imágenes y reconocimiento de caracteres que no son parte de este artículo.&lt;br /&gt;&lt;br /&gt;El modelo general consiste en construir un script PHP que retorne una imagen con un texto aleatorio. El texto original será almacenado en una variable de sesión. Al procesar el formulario se deberá comparar el texto ingresado por el usuario con el texto almacenado en la variable de sesión.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Versión 1&lt;/span&gt;&lt;br /&gt;Mi primera versión consiste en una implementación básica sólo para desarrollar la funcionalidad mínima del sistema:&lt;br /&gt;&lt;pre&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;// Alto y ancho de la imagen&lt;br /&gt;$width  = 250;&lt;br /&gt;$height = 60;&lt;br /&gt;// Tipografia&lt;br /&gt;$font = "arial.ttf";&lt;br /&gt;// Nombre de la variable de sesion&lt;br /&gt;$session_var = "captcha";&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Genero la imagen&lt;br /&gt;$im = imagecreatetruecolor($width, $height);&lt;br /&gt;$bg_color = imagecolorallocate($im, 255, 255, 255);&lt;br /&gt;$fg_color = imagecolorallocate($im, 0, 0, 0);&lt;br /&gt;imagefilledrectangle($im, 0, 0, $width, $height, $bg_color);&lt;br /&gt;&lt;br /&gt;// Genero el texto&lt;br /&gt;$text = "secret";&lt;br /&gt;imagettftext($im, 20, 0, 11, 21, $fg_color, $font, $text);&lt;br /&gt;&lt;br /&gt;// Guardo el texto en sesión&lt;br /&gt;session_start();&lt;br /&gt;$_SESSION[$session_var] = $text;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Genero la imagen&lt;br /&gt;header("Content-type: image/png");&lt;br /&gt;imagepng($im);&lt;br /&gt;&lt;br /&gt;// Limpieza&lt;br /&gt;imagedestroy($im);&lt;br /&gt;&lt;br /&gt;?&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Este script genera una imagen de 250x60 pixeles, con color de fondo blango y texto arial negro. El texto utilizado en la imagen se guarda en la variable de sesión &lt;code&gt;captcha&lt;/code&gt;. El resultado de este script es la siguiente imagen:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_6K2axIH8bMw/SDy_P3Ti0JI/AAAAAAAAAGk/e6Ll6nXICWU/s1600-h/captcha.1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_6K2axIH8bMw/SDy_P3Ti0JI/AAAAAAAAAGk/e6Ll6nXICWU/s200/captcha.1.png" alt="" id="BLOGGER_PHOTO_ID_5205245548598317202" border="0" /&gt;&lt;/a&gt;En el script que procesa el formulario, basta comparar el valor de &lt;code&gt;$_SESSION['captcha']&lt;/code&gt; con el texto ingresado en el formulario para confirmar que el captcha haya sido ingresado satisfactoriamente. Para reducir la tasa de error, es posible quitar los eventuales espacios en blanco y convertir el texto a minúculas para hacer una comparación &lt;span style="font-style: italic;"&gt;case-insensitive&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Ahora que contamos con el código base podemos continuar mejorando nuestro script.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Versión 2&lt;/span&gt;&lt;br /&gt;Las mejoras que introduciremos en esta versión son las siguientes:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Generación de un texto aleatorio.&lt;/li&gt;&lt;li&gt;Generar texto con distintas tipografías.&lt;/li&gt;&lt;li&gt;Agregar transformaciones simples al texto.&lt;/li&gt;&lt;li&gt;Aumentar el tamaño y darle un color más amigable.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;La generación de un texto aleatorio puede ser algo tan sencillo como extraer los primeros 6 caracteres de un valor MD5 como el siguiente: &lt;code&gt;substr(md5(uniqid(),0,6)&lt;/code&gt; el cual genera un string con números y letras entre la "a" y la "f".&lt;br /&gt;&lt;br /&gt;Sin embargo construiremos algo más elaborado donde podamos controlar los caracteres a generar e intentaremos que el texto sea medianamente pronunciable con el fin de ser más amigable con el usuario. Una manera simple de generar texto pronunciable es asegurándonos de tener suficientes vocales y quitando algunas letras poco usadas en el español como la "x" y la "k". Definí arbitrariamente la longitud del texto a 6 caracteres lo cual minimiza la generación de palabras mal vistas como "pene", "puta" o "malo" y da una cantidad de combinaciones de letras apropiada.&lt;br /&gt;&lt;br /&gt;Para ello elaboré una función que retorna textos aleatorios intercalando una consonante y una vocal:&lt;br /&gt;&lt;pre&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;function getCaptchaText($length = 6) {&lt;br /&gt;$consonants = "bcdfghjlmnpqrstvwyz";&lt;br /&gt;$vocals     = "aeiou";&lt;br /&gt;&lt;br /&gt;$text = "";&lt;br /&gt;$imax = $length/2;&lt;br /&gt;for ($i=0; $i&lt;$imax; $i++) {&lt;br /&gt;    $text .= substr($consonants, mt_rand(0, 18), 1);&lt;br /&gt;    $text .= substr($vocals,     mt_rand(0, 4),  1);&lt;br /&gt;}&lt;br /&gt;return substr($text, 0, $length);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;?&amp;gt;&lt;/pre&gt;Por lo tanto, en el script original reemplazamos:&lt;br /&gt;&lt;pre&gt;$text = "secret";&lt;br /&gt;&lt;/pre&gt;por:&lt;br /&gt;&lt;pre&gt;$text = getCaptchaText(6);&lt;br /&gt;&lt;/pre&gt;Con este cambio ahora generamos captchas como los siguientes:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_6K2axIH8bMw/SDzKlnTi0KI/AAAAAAAAAGs/jrCbRz3r2Ls/s1600-h/captcha.2..jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_6K2axIH8bMw/SDzKlnTi0KI/AAAAAAAAAGs/jrCbRz3r2Ls/s200/captcha.2..jpg" alt="" id="BLOGGER_PHOTO_ID_5205258016888377506" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Para hacer el captcha más complejo vamos a generar el texto con distintas tipografías, agregarle rotación independiente a cada letra y juntar (o condensar) las letras para que se produzca un traslape que hará más dificil su decodificación.&lt;br /&gt;&lt;br /&gt;Extraje algunos archivos .ttf desde mi directorio C:\WINDOWS\Fonts y luego reemplacé:&lt;br /&gt;&lt;pre&gt;$font = "arial.ttf";&lt;br /&gt;&lt;/pre&gt;por:&lt;br /&gt;&lt;pre&gt;$fonts = array('timesbi.ttf', 'calibriz.ttf', 'cambriaz.ttf');&lt;br /&gt;$font = $fonts[mt_rand(0, sizeof($fonts)-1)];&lt;br /&gt;&lt;/pre&gt;Aumentamos el tamaño, le agregamos color y ahora obtenemos las siguientes imagenes donde podemos apreciar el cambio de tipografía en cada ejecución:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_6K2axIH8bMw/SDzMDnTi0MI/AAAAAAAAAG8/SVk4lxE4Izg/s1600-h/captcha.3..jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_6K2axIH8bMw/SDzMDnTi0MI/AAAAAAAAAG8/SVk4lxE4Izg/s400/captcha.3..jpg" alt="" id="BLOGGER_PHOTO_ID_5205259631796080834" border="0" /&gt;&lt;/a&gt;Hasta este punto hemos generado un captcha básico que es extremadamente fácil de decodificar.&lt;br /&gt;&lt;br /&gt;Para hacer la imágen mas compleja vamos a generar letra por letra. Cada letra tendrá una rotación distinta y acercaremos las letras para lograr un traslape.&lt;br /&gt;&lt;br /&gt;Modificamos el código:&lt;br /&gt;&lt;pre&gt;imagettftext($im, 20, 0, 11, 21, $fg_color, $font, $text);&lt;br /&gt;&lt;/pre&gt;por:&lt;br /&gt;&lt;pre&gt;$x = 3;&lt;br /&gt;for ($i=0; $i&lt;=6; $i++) {&lt;br /&gt;  $angulo = rand(-12, 12);&lt;br /&gt;  $coords = imagettftext($im, 38, $angulo, $x, 47, $fg_color, $font, substr($text, $i, 1));&lt;br /&gt;  $x += ($coords[2]-$x) - 4;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Este código va generando letra por letra y en cada iteración va aumentando la coordenada X restandole algunos pixeles para lograr que el texto se traslape. La rotación de cada letra tendrá un valor comprendido entre el intervalo [-12,12].&lt;br /&gt;&lt;br /&gt;Se deberá buscar el valor exacto de traslape con el fin de hacer difícil su decodificación, pero permitiendo que el texto sea legible. Un refinamiento de la generación de estos textos podría ser que cada letra se genere con una tipografía distinta.&lt;br /&gt;&lt;br /&gt;Al aplicar estos cambios obtenemos las siguientes imagenes:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_6K2axIH8bMw/SDzOJ3Ti0OI/AAAAAAAAAHM/Vuu-xGgQMcY/s1600-h/captcha.4.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_6K2axIH8bMw/SDzOJ3Ti0OI/AAAAAAAAAHM/Vuu-xGgQMcY/s400/captcha.4.jpg" alt="" id="BLOGGER_PHOTO_ID_5205261938193518818" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Una vez que juntemos todos estos cambios obtendremos el siguiente código:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;php&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Alto y ancho de la imagen&lt;br /&gt;$width  = 250;&lt;br /&gt;$height = 65;&lt;br /&gt;// Tipografia&lt;br /&gt;$fonts = array('timesbi.ttf', 'calibriz.ttf', 'cambriaz.ttf');&lt;br /&gt;// Nombre de la variable de sesion&lt;br /&gt;$session_var = "captcha";&lt;br /&gt;// Condensacion entre cada letra&lt;br /&gt;$condensacion = 4;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Genero la imagen&lt;br /&gt;$im = imagecreatetruecolor($width, $height);&lt;br /&gt;$bg_color = imagecolorallocate($im, 255, 255, 255);&lt;br /&gt;$fg_color = imagecolorallocate($im, 33, 67, 165);&lt;br /&gt;imagefilledrectangle($im, 0, 0, $width, $height, $bg_color);&lt;br /&gt;&lt;br /&gt;// Genero el texto&lt;br /&gt;$font = $fonts[mt_rand(0, sizeof($fonts)-1)];&lt;br /&gt;$text = getCaptchaText(6);&lt;br /&gt;$x = 3;&lt;br /&gt;for ($i=0; $i&lt;=6; $i++) {&lt;br /&gt;  $angulo = rand(-12, 12);&lt;br /&gt;  $coords = imagettftext($im, 38, $angulo, $x, 47, $fg_color, $font, substr($text, $i, 1));&lt;br /&gt;  $x += ($coords[2]-$x)-$condensacion;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Guardo el texto en sesión&lt;br /&gt;session_start();&lt;br /&gt;$_SESSION[$session_var] = $text;&lt;br /&gt;&lt;br /&gt;// Genero la imagen&lt;br /&gt;header("Content-type: image/png");&lt;br /&gt;imagepng($im);&lt;br /&gt;&lt;br /&gt;// Limpieza&lt;br /&gt;imagedestroy($im);&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Retorna un texto aleatorio&lt;br /&gt;*&lt;br /&gt;* @param int $length Longitud del texto&lt;br /&gt;* @return string Texto aleatorio&lt;br /&gt;*/&lt;br /&gt;function getCaptchaText($length = 6) {&lt;br /&gt;  $consonants = "bcdfghjlmnpqrstvwyz";&lt;br /&gt;  $vocals     = "aeiou";&lt;br /&gt;  $text = "";&lt;br /&gt;  $imax = $length/2;&lt;br /&gt;  for ($i=0; $i&lt;$imax; $i++) {&lt;br /&gt;      $text .= substr($consonants, mt_rand(0, 18), 1);&lt;br /&gt;      $text .= substr($vocals,     mt_rand(0, 4),  1);&lt;br /&gt;  }&lt;br /&gt;  return substr($text, 0, $length);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;?&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Versión 3&lt;/span&gt;&lt;br /&gt;Para dificultar aún más la decodificación le aplicaremos a la imagen un filtro "wave" que genere ondulaciones al texto.&lt;br /&gt;&lt;br /&gt;Adicionalmente para aumentar el "ruido" de la imagen la generaré como JPEG en vez de PNG.&lt;br /&gt;&lt;br /&gt;Existen otras técnicas bastante eficientes para la mayoría de los casos que consiste en introducir imágenes de fondo y ruido a la imágen como por ejemplo líneas aleatorias o pintar pixeles al alzar. Sin embargo dichas técnicas disminuyen en gran medida la estética del captcha por lo que las descartaré para estos ejemplos.&lt;br /&gt;&lt;br /&gt;Para la generación de ondas en la imágen, existen excelentes biblitecas gráficas que permiten hacer esto como por ejemplo ImageMagick. Sin embargo, para continuar con la simplicidad del script utilizaremos una combinación de funciones de GD y funciones trigonométricas. La función de a continuación está basada en un ejemplo publicado en el manual de PHP:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/**&lt;br /&gt;* Filtro wave&lt;br /&gt;*&lt;br /&gt;* Obtenido desde ejemplo publicado en el manual de PHP:&lt;br /&gt;* http://www.php.net/manual/en/function.imagecopy.php#72393&lt;br /&gt;*&lt;br /&gt;*/&lt;br /&gt;function wave_region($img, $x, $y, $width, $height,$amplitude = 4.5,$period = 30) {&lt;br /&gt;// Make a copy of the image twice the size&lt;br /&gt;$mult = 2;&lt;br /&gt;$img2 = imagecreatetruecolor($width * $mult, $height * $mult);&lt;br /&gt;imagecopyresampled ($img2,$img,0,0,$x,$y,$width * $mult,$height * $mult,$width, $height);&lt;br /&gt;&lt;br /&gt;// Wave it&lt;br /&gt;$k = rand(-60,60);&lt;br /&gt;for ($i = 0;$i &lt; ($width * $mult);$i += 2) {&lt;br /&gt;    imagecopy($img2,$img2,&lt;br /&gt;              $x + $i - 2,$y + sin($k+$i / $period) * $amplitude,&lt;br /&gt;              $x + $i,$y,&lt;br /&gt;              2,($height * $mult));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Resample it down again&lt;br /&gt;imagecopyresampled ($img,$img2,$x,$y,0,0,$width, $height,$width * $mult,$height * $mult);&lt;br /&gt;imagedestroy($img2);&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Esta función la llamaremos inmediatamente después de generar el texto con los siguientes argumentos:&lt;br /&gt;&lt;pre&gt;// Aplico filtros&lt;br /&gt;wave_region($im, 0, 0, $width, $height, mt_rand(7, 22), mt_rand(25,40));&lt;br /&gt;&lt;/pre&gt; Una vez que apliquemos este cambio se generarán las siguientes imágenes de referencia:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_6K2axIH8bMw/SDzWYHTi0PI/AAAAAAAAAHU/XJaih-vNVqE/s1600-h/captcha.6.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_6K2axIH8bMw/SDzWYHTi0PI/AAAAAAAAAHU/XJaih-vNVqE/s400/captcha.6.jpg" alt="" id="BLOGGER_PHOTO_ID_5205270979099676914" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Al juntar estos últimos cambios y ajustar el tamaño definitivo de la imágen obtendremos el siguiente código:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;php&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Alto y ancho de la imagen&lt;br /&gt;$width  = 180;&lt;br /&gt;$height = 65;&lt;br /&gt;// Tipografia&lt;br /&gt;$fonts = array('timesbi.ttf', 'calibriz.ttf', 'cambriaz.ttf');&lt;br /&gt;// Nombre de la variable de sesion&lt;br /&gt;$session_var = "captcha";&lt;br /&gt;// Condensacion entre cada letra&lt;br /&gt;$condensacion = 4;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// Genero la imagen&lt;br /&gt;$im = imagecreatetruecolor($width, $height);&lt;br /&gt;$bg_color = imagecolorallocate($im, 255, 255, 255);&lt;br /&gt;$fg_color = imagecolorallocate($im, 33, 67, 165);&lt;br /&gt;imagefilledrectangle($im, 0, 0, $width, $height, $bg_color);&lt;br /&gt;&lt;br /&gt;// Genero el texto&lt;br /&gt;$font = $fonts[mt_rand(0, sizeof($fonts)-1)];&lt;br /&gt;$text = getCaptchaText(6);&lt;br /&gt;&lt;br /&gt;//$text = "captcha"; $font="cambriaz.ttf"; $angulo = 0;&lt;br /&gt;&lt;br /&gt;$x = 3;&lt;br /&gt;for ($i=0; $i&lt;=6; $i++) {&lt;br /&gt;  $angulo = rand(-12, 12);&lt;br /&gt;  $coords = imagettftext($im, 38, $angulo, $x, 47, $fg_color, $font, substr($text, $i, 1));&lt;br /&gt;  $x += ($coords[2]-$x)-$condensacion;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Aplico filtros&lt;br /&gt;wave_region($im, 0, 0, $width, $height, mt_rand(7, 22), mt_rand(25,40));&lt;br /&gt;&lt;br /&gt;// Guardo el texto en sesión&lt;br /&gt;session_start();&lt;br /&gt;$_SESSION[$session_var] = $text;&lt;br /&gt;&lt;br /&gt;// Genero la imagen&lt;br /&gt;header("Content-type: image/jpeg");&lt;br /&gt;imagejpeg($im);&lt;br /&gt;&lt;br /&gt;// Limpieza&lt;br /&gt;imagedestroy($im);&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Retorna un texto aleatorio&lt;br /&gt;*&lt;br /&gt;* @param int $length Longitud del texto&lt;br /&gt;* @return string Texto aleatorio&lt;br /&gt;*/&lt;br /&gt;function getCaptchaText($length = 6) {&lt;br /&gt;   $consonants = "bcdfghjlmnpqrstvwyz";&lt;br /&gt;  $vocals     = "aeiou";&lt;br /&gt;  $text = "";&lt;br /&gt;  $imax = $length/2;&lt;br /&gt;  for ($i=0; $i&lt;$imax; $i++) {&lt;br /&gt;      $text .= substr($consonants, mt_rand(0, 18), 1);&lt;br /&gt;      $text .= substr($vocals,     mt_rand(0, 4),  1);&lt;br /&gt;  }&lt;br /&gt;  return substr($text, 0, $length);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Filtro wave&lt;br /&gt;*&lt;br /&gt;* Obtenido desde ejemplo publicado en el manual de PHP:&lt;br /&gt;* http://www.php.net/manual/en/function.imagecopy.php#72393&lt;br /&gt;*&lt;br /&gt;*/&lt;br /&gt;function wave_region($img, $x, $y, $width, $height,$amplitude = 4.5,$period = 30) {&lt;br /&gt;// Make a copy of the image twice the size&lt;br /&gt;$mult = 2;&lt;br /&gt;$img2 = imagecreatetruecolor($width * $mult, $height * $mult);&lt;br /&gt;imagecopyresampled ($img2,$img,0,0,$x,$y,$width * $mult,$height * $mult,$width, $height);&lt;br /&gt;&lt;br /&gt;// Wave it&lt;br /&gt;$k = rand(-60,60);&lt;br /&gt;for ($i = 0;$i &lt; ($width * $mult);$i += 2) {&lt;br /&gt;    imagecopy($img2,$img2,&lt;br /&gt;              $x + $i - 2,$y + sin($k+$i / $period) * $amplitude,&lt;br /&gt;              $x + $i,$y,&lt;br /&gt;              2,($height * $mult));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Resample it down again&lt;br /&gt;imagecopyresampled ($img,$img2,$x,$y,0,0,$width, $height,$width * $mult,$height * $mult);&lt;br /&gt;imagedestroy($img2);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;?&amp;gt; &lt;/pre&gt;&lt;br /&gt;La evolución del captcha construido fue la siguiente:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_6K2axIH8bMw/SDzZ2HTi0QI/AAAAAAAAAHc/0rc8jsi8sek/s1600-h/captcha-evolucion.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_6K2axIH8bMw/SDzZ2HTi0QI/AAAAAAAAAHc/0rc8jsi8sek/s400/captcha-evolucion.png" alt="" id="BLOGGER_PHOTO_ID_5205274793030635778" border="0" /&gt;&lt;/a&gt;Como mejoras a este captcha se podría realizar una seleccón mas exuastiva de las tipografías utilizadas, se le podría añadir una imagen de fondo aleatoria, lineas aleatorias o ruido.&lt;br /&gt;&lt;br /&gt;En el &lt;a href="http://gmt-4.blogspot.com/2008/06/crear-un-captcha-como-el-de-google.html"&gt;siguiente artículo&lt;/a&gt; vamos a optimizar el código y aumentar la complejidad del captcha sin perder su estética (es decir no le incorporaré lineas aleatorias, imágenes de fondo, etc.) de una manera similar a los &lt;a href="http://www.google.com/addurl/"&gt;captchas utilizados por Google&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Actualizado el día domingo 1 de junio de 2008.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-1884346475966379303?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/1884346475966379303/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=1884346475966379303" title="1 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1884346475966379303?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1884346475966379303?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/05/creacion-de-un-captha-o-test-de-turing.html" title="Creación de un CAPTCHA (o test de turing)" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_6K2axIH8bMw/SDy46HTi0HI/AAAAAAAAAGU/_wWjQkcGWZk/s72-c/captcha2.jpg" height="72" width="72" /><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;A0IERH45eyp7ImA9WxdSFE8.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-2725031916764488647</id><published>2008-05-21T23:21:00.000-04:00</published><updated>2008-05-22T00:11:45.023-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-22T00:11:45.023-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="xss" /><category scheme="http://www.blogger.com/atom/ns#" term="seguridad" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><title>Protección básica contra Cross-site scripting (XSS)</title><content type="html">Los ataques&lt;a href="http://en.wikipedia.org/wiki/Cross-site_scripting"&gt; Cross-site scripting&lt;/a&gt; (XSS) o de HTML injection pueden ser prevenidos de manera bastante simple tomando algunas medidas de seguridad básicas que debemos tener siempre presentes.&lt;br /&gt;&lt;br /&gt;Este tipo de ataque consiste en incrustar código HTML en un sitio web con el objetivo de poder ejecutar código Javascript arbitraro o incluir código HTML con el fin de -por ejemplo- realizar ataques de &lt;a href="http://www.znep.com/%7Emarcs/passport/"&gt;phishing&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;La preveción de este tipo de ataque se realiza filtrando la infomación que se ingresa y publica en nuestro sitio, por lo que tenemos dos puntos de control:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Filtrar datos ingresados&lt;/li&gt;&lt;li&gt;Escapar datos publicados en la WEB&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Filtrar datos ingresados&lt;/span&gt;&lt;br /&gt;La media de prevención más sencilla es impedir que los usuarios del sitio puedan publicar información, como por ejemplo cuando se hacen comentarios en un blog o en formularos de contacto.&lt;br /&gt;&lt;br /&gt;En caso que el sitio deba permitir el ingreso de información, se debe definir si se aceptará código HTML. En caso que se opte por no aceptar código HTML será necesario filtrar todo el contenido recibido desde formularios para quitar eventuales tags HTML que ingrese el usuario. Esto se puede realizar con la siguiente función:&lt;br /&gt;&lt;pre&gt;$comentario = strip_tags($comentario);&lt;/pre&gt;La función &lt;code&gt;strip_tags() &lt;/code&gt;no realiza validación de que el código HTML esté bien formado, por lo que potencialmente se podría perder información -mal- ingresada por el usuario. Se puede realizar una validación más apropiada por medio de &lt;a href="http://php.net/tidy"&gt;tidy&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;En caso que sea necesario permitir el ingreso de código HTML, se deberán restringir los tags HTML que serán permitidos. Usualmente se permiten los tags de texto básico como &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;blockquote&amp;gt;&lt;/code&gt; y tags de listas como &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt; y &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;. Especialmente se debe evitar la inclusión de tags HTML que permitan la inclusión de contenidos externos como por ejemplo &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;frame&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt; y &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;El filtrado de los tags HTML permitidos se puede realizar con el siguiente comando:&lt;br /&gt;&lt;pre&gt;$comentario = strip_tags($comentario, "&amp;lt;p&amp;gt;&amp;lt;br&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;em&amp;gt;");&lt;/pre&gt;Sin embargo, nuevamente la función &lt;code&gt;strip_tags() &lt;/code&gt;puede presentar problemas porque los tags admitidos podrían contener atributos peligrosos como &lt;code&gt;onload&lt;/code&gt; y &lt;code&gt;&lt;/code&gt;&lt;code&gt;onmouseover&lt;/code&gt;. Por lo que se debería recurrir a una combinación de  &lt;a href="http://php.net/tidy"&gt;tidy&lt;/a&gt; y &lt;a href="http://php.net/preg_replace"&gt;expresiones regulares&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Las tareas de filtrado se pueden enfrentar de dos maneras: Manteniendo una lista negra de tags y atributos no permitidos o manteniendo una lista blanca con los tags y atributos permitidos. En vista de evitar problemas de seguridad, es más recomendable mantener una lista blanca que contenga los elementos admitidos ya que es mucho menos grave restringir el ingreso de un dato inofensivo (falso positivo) que permitir el ingreso de un dato potencialmente peligroso.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Escapar datos ingresados&lt;/span&gt;&lt;br /&gt;Como segunda medida de contención se puede agregar validación antes de publicar información en la WEB.&lt;br /&gt;&lt;br /&gt;Se debe considerar que la información que ingresa a nuestras bases de datos (y archivos de caché) es introducida por diversos medios, como por ejemplo WebServices, migraciones de datos o SQL Injection, por lo tanto aunque se mantenga absoluta seguridad en nuestros formularios WEB, siempre existe la posibilidad de contar con datos peligrosos. Debido a esto, seimpre es recomendable filtrar la información que se publica en nuestro sitio.&lt;br /&gt;&lt;br /&gt;En primer lugar debemos distinguir si la información que publicamos puede contener HTML o no.&lt;br /&gt;&lt;br /&gt;En general la gran mayoría de la información que se publica en la web -como por ejemplo nombres, fechas, títulos- no contiene código HTML. En estos casos la información a imprimir en una página web debe ser escapada para reemplazar los caracteres especiales por sus entidades respectivas, esto se realiza con el siguiente comando:&lt;br /&gt;&lt;pre&gt;echo htmlspecialchars($nombre);&lt;/pre&gt;Esta función tiene la ventaja que imprime correctamente textos como "Barnes &amp;amp; Noble" y "A &amp;lt; B &amp;lt; C" que en caso de no ser escapados no se visualizarían correctamente.&lt;br /&gt;&lt;br /&gt;En el caso de publicar contenidos con HTML, se debe aplicar la misma función de filtrado que se utiliza al momento de ingresar datos.&lt;br /&gt;&lt;br /&gt;Con estas simples medidas se aumenta en gran medida el nivel de seguridad ante ataques XSS.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-2725031916764488647?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/2725031916764488647/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=2725031916764488647" title="1 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/2725031916764488647?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/2725031916764488647?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/05/proteccin-bsica-contra-cross-site.html" title="Protección básica contra Cross-site scripting (XSS)" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;A0cGSHg-eSp7ImA9WxdTEEw.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-1454941470193883816</id><published>2008-05-05T15:00:00.001-04:00</published><updated>2008-05-05T16:23:49.651-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-05T16:23:49.651-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="accesibilidad" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><title>Javascript dentro de HTML</title><content type="html">Para incrustar código Javascript dentro de HTML se debe utilizar la siguiente etiqueta HTML:&lt;br /&gt;&lt;pre&gt;&amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;// Aquí va el código...&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/pre&gt;Sin embargo, en la actualidad es mejor no utilizar Javascript incrustado sino que más bien utilizar archivos Javascript externos. Esto permite separar el contenido de la programación y además permite una navegación más rápida ya que se pueden guardar en cache los archivos .js externos. El código para referenciar archivos Javascript es el siguiente:&lt;br /&gt;&lt;pre&gt;&amp;lt;script type="text/javascript" src="filename.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/pre&gt;El sentido común nos dice que las referencias a archivos Javascript debieran realizarse en el encabezado del documeno HTML, es decir, dentro de los tags &lt;code&gt;HEAD&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Es mejor hacer lo contrario: por un tema de performance se recomienda -en la medida de lo posible- poner dichas referencias al final del documento HTML ya que esto acelera el proceso de rendering del documento HTML.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-style: italic;"&gt;Graceful degradation&lt;/span&gt; y &lt;span style="font-style: italic;"&gt;Unobtrusive Javascript&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Cuando se construyen sitios que utilicen Javascript es importante tener en mente mantener la semántica del sitio web y ser conscientes que no todos nuestros visitantes tendrán Javascript habilitado, por ejemplo &lt;span style="font-style: italic;"&gt;bots &lt;/span&gt;de los motores de búsqeda, usuarios que han deshabilitado javascript por motivos de seguridad, usuarios de algunos dispositivos móviles, microformatos, herramientas semánticas como &lt;a href="http://pipes.yahoo.com/pipes/"&gt;Yahoo pipes&lt;/a&gt;, clientes en modo de sólo texto o -el problema más común- usuarios que visitan nuestro sitio con un navegador que es pacialmente incompatible con nuestro sitio ya sea porque es un navegador antiguo o una versión más moderna. Todos estos usuarios debieran poder navegar por nuestro sitio sin mayores problemas.&lt;br /&gt;&lt;br /&gt;El concepto de &lt;a style="font-style: italic;" href="http://en.wikipedia.org/wiki/Unobtrusive_JavaScript"&gt;Unobtrusive Javascript&lt;/a&gt; consiste en seguir las buenas prácticas en la programación de Javascript y poder separar el comportamiento (código javascript) del contenido del sitio, es decir que el código Javascript sea lo menos invasivo posible.&lt;br /&gt;&lt;br /&gt;El concepto de &lt;span style="font-style: italic;"&gt;Graceful degradation&lt;/span&gt; consiste en que un sitio web tenga la capacidad de continuar operando con clientes incompatibles proveyendo un nivel de servicios reducido en vez de que este no pueda operar.&lt;br /&gt;&lt;br /&gt;Por ejemplo, a continuación se muestran algunos links que hacen uso de javascript de manera incorrecta:&lt;br /&gt;&lt;pre&gt;&amp;lt;a href="javascript:link('products')"&amp;gt;Productos&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;a href="javascript:submitForm('form1')"&amp;gt;Enviar formulario&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;a href="products.html" onclick="check()"&amp;gt;Productos&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;/pre&gt;El primer ejemplo utiliza un llamado a la función &lt;code&gt;link()&lt;/code&gt;. Este código no funcionará en navegadores sin soporte a  Javascript y hace que sea muy dificil que un robot pueda navegar por el sitio para indexarlo, además atenta contra la usabilidad como por ejemplo hacer clic con el botón central del mouse para abrir el link en una nueva pestaña no funcionará. Este es el caso de &lt;a href="http://www.falabella.com/webapp/commerce/command/ExecMacro/falabella/macros/list_prod.d2w/report?sprod=0&amp;amp;ConFoto=1&amp;amp;cgmenbr=1891&amp;amp;nivel=1&amp;amp;cgrfnbr=2542756&amp;amp;sfot=0&amp;amp;cgpadre=2457964&amp;amp;cgnieto=2542756&amp;amp;cghijo1=2458460&amp;amp;division=0"&gt;Falabella&lt;/a&gt; donde no puedo abrir las fichas de productos en distintas pestañas para poder compararlos.&lt;br /&gt;&lt;br /&gt;El segundo ejemplo realiza el envío de un formulario mediante código Javascript cuando lo semánticamente correcto habría sido utilizar el elemento HTML &lt;code&gt;&amp;lt;input type="submit"&amp;gt;&lt;/code&gt; y en caso de requerir validación del formulario agregar el evento &lt;code&gt;onsubmit&lt;/code&gt; al formulario.&lt;br /&gt;&lt;br /&gt;El tercer ejemplo ejecuta una función de validación al momento de hacer clic en el link mediante el atributo &lt;code&gt;onclick&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;El código correcto para estos 3 ejemplos sería:&lt;br /&gt;&lt;pre&gt;&amp;lt;a href="/show/products"&amp;gt;Productos&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;input type="submmit" id="submitForm" value="Enviar formulario"/&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;pre&gt;&amp;lt;a href="products.html" id="linkProducts"&amp;gt;Productos&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;/pre&gt;En el primer ejemplo simplemente se reemplazó el código javascript por un link. Se pueden utilizar técnicas como mod_rewrite de Apache para mantener links amigables.  En el segundo y tercer caso se utiliza HTML estándar y se referencian los elementos mediante el atributo id mediante el cual podrán ser manipulados por javascript.&lt;br /&gt;&lt;br /&gt;Finalmente, agregamos los &lt;span style="font-style: italic;"&gt;event handler&lt;/span&gt; mediante el siguiente código Javascript:&lt;br /&gt;&lt;pre&gt;document.getElementById('linkProducts').onclick = check;&lt;br /&gt;document.getElementById('form1').onsubmit = checkForm;&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-style: italic;font-size:85%;" &gt;(NOTA: estos son ejemplos Javascript simplificados que contienen errores de malas práctias como por ejemplo definir directamente los eventos en vez de utilizar una &lt;a href="http://developer.yahoo.com/yui/event/"&gt;función para agregar eventos en cascada&lt;/a&gt;)&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-1454941470193883816?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/1454941470193883816/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=1454941470193883816" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1454941470193883816?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1454941470193883816?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/05/javascript-dentro-de-html.html" title="Javascript dentro de HTML" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEEMQ3szfSp7ImA9WxZaGE4.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-4121819141288240932</id><published>2008-05-03T13:30:00.001-04:00</published><updated>2008-05-03T13:44:42.585-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-03T13:44:42.585-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tunneling" /><category scheme="http://www.blogger.com/atom/ns#" term="ssh" /><category scheme="http://www.blogger.com/atom/ns#" term="seguridad" /><title>SSH Tunneling</title><content type="html">&lt;span style="font-weight: bold;"&gt;Un problema común&lt;/span&gt;&lt;br /&gt;Uno de los grandes problema con que &lt;del&gt;lidio&lt;/del&gt; lidiaba en los servidores de producción, es que comúnmente tienen reglas de firewall estrictas que sólo permiten el tráfico TCP/IP por el puerto del servicio que están ejecutando (servidor web, base de datos, FTP, etc.) y el puerto de SSH (22) con fines de administración.&lt;br /&gt;&lt;br /&gt;Además en la mayoría de los casos el acceso SSH está limitado sólo desde algunos de los equipos de la red local y en ningún caso permiten el acceso SSH desde Internet.&lt;br /&gt;&lt;br /&gt;Esta situación hace muy difícil los procesos de administración, actualización y solución de contingencias ya que la única manera de acceder al servidor era ir físicamente al datacenter o intermediar telefónicamente con un operador que con suerte sabe ejecutar los comandos de shell &lt;code&gt;cd&lt;/code&gt; y &lt;code&gt;ls&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Otro problema que se nos puede presentar es la necesidad de encriptar conexiones de protocolos inseguros, como por ejemplo una conexión FTP.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;La solución&lt;/span&gt;&lt;br /&gt;Una solución para poder acceder a sistemas remotos es estableciendo un Tunnleing SSH, el cual consiste en redirigir (forward) las conexiones TCP dirigidas hacia un puerto específico hacia otro host por medio de la conexión encriptada de SSH. No soporta redirección de paquetes UDP.&lt;br /&gt;&lt;br /&gt;En los siguientes ejemplos utilizaremos como ejemplo las siguientes direcciones IP:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Servidor (con acceso restringido): 1.1.1.1&lt;/li&gt;&lt;li&gt;Equipo de administración (tiene acceso al servidor): 1.1.1.2&lt;/li&gt;&lt;li&gt;Equipo local (sin acceso al servidor): 1.1.1.3&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;El tunneling SSH tiene dos modalidades de redirección: &lt;span style="font-style: italic;"&gt;remote port forwarding&lt;/span&gt; que permite redirigir conexiones TCP desde el equipo remoto  y &lt;span style="font-style: italic;"&gt;local port forwarding&lt;/span&gt; que permite redirigir las conexiones TCP desde el equipo local.&lt;br /&gt;&lt;br /&gt;En shell, la sintaxis tradicional para establecer una conexión SSH es la siguiente:&lt;br /&gt;&lt;pre&gt;ssh user@host&lt;/pre&gt;La sintaxis para establecer un tunneling consiste en indicar adicionalmente:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;El tipo de redirección: "R" para &lt;span style="font-style: italic;"&gt;remote port forwarding &lt;/span&gt;y "L" para &lt;span style="font-style: italic;"&gt;local port forwarding&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;El puerto que se redireccionará.&lt;/li&gt;&lt;li&gt;El host y puerto hacia el cual se redigirán los paquetes TCP&lt;/li&gt;&lt;/ol&gt;Quedando en:&lt;br /&gt;&lt;pre&gt;ssh user@host -R port:host:hostport&lt;br /&gt;ssh user@host -L port:host:hostport&lt;/pre&gt;En los siguientes ejemplos se utilizará para redirigir conecciones SSH, pero esta técnica sirve para redireccionar cualquier servicio basado en TCP.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Local port forwarding&lt;br /&gt;&lt;/span&gt;S&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;i quisieramos acceder por SSH al servidor 1.1.1.1 desde nuestro equipo local 1.1.1.3, el cual no tiene acceso directo, podríamos establecer inicialmente una conexión hacia el equipo de administración 1.1.1.2 y desde ahí establecer un port forwarding hacia el servidor 1.1.1.1 tal como se muestra en el siguiente esquema:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_6K2axIH8bMw/SBycxXBh1lI/AAAAAAAAAFk/27CzHdfCg9I/s1600-h/topologia.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_6K2axIH8bMw/SBycxXBh1lI/AAAAAAAAAFk/27CzHdfCg9I/s320/topologia.png" alt="" id="BLOGGER_PHOTO_ID_5196200441886660178" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Para eso, desde nuestro equipo local 1.1.1.1 ejecutamos el siguiente comando:&lt;br /&gt;&lt;pre&gt;ssh user@1.1.1.2 -L 2222:1.1.1.1:22&lt;/pre&gt;Una vez conectado al equipo remoto 1.1.1.2 todas las conecciones desde nuestro equipo local al puerto 2222 serán redirigidas al puerto 22 del servidor 1.1.1.1, con lo cual hemos logrado tener acceso al servidor remoto mediante el siguiente comando:&lt;br /&gt;&lt;pre&gt;ssh user@localhost -p 2222&lt;/pre&gt;Incluso desde otro equipo que tenga acceso a 1.1.1.3 podrá conectarse al servidor 1.1.1.1 mediante SSH:&lt;br /&gt;&lt;pre&gt;ssh user@1.1.1.3 -p 2222&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Remote port forwarding&lt;br /&gt;&lt;/span&gt;Otra modalidad para acceder al servidor 1.1.1.1 es establecer la conexión opuesta desde el equipo de administración 1.1.1.2 hacia nuestro equipo local 1.1.1.3 como se ilustra a continuación:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_6K2axIH8bMw/SByeq3Bh1mI/AAAAAAAAAFs/zkgibStRf40/s1600-h/topologia2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_6K2axIH8bMw/SByeq3Bh1mI/AAAAAAAAAFs/zkgibStRf40/s320/topologia2.png" alt="" id="BLOGGER_PHOTO_ID_5196202529240766050" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;En este caso desde el equipo 1.1.1.2 se establecerá un &lt;span style="font-style: italic;"&gt;remote port forwarding&lt;/span&gt; mediante el siguiente comando:&lt;br /&gt;&lt;pre&gt;ssh user@1.1.1.3 -R 3333:1.1.1.1:22&lt;/pre&gt;Una vez conectado al nuestro equipo 1.1.1.3 todas las conecciones desde nuestro equipo local al puerto 3333 serán redirigidas al puerto 22 del servidor 1.1.1.1, con lo cual hemos logrado tener acceso al servidor remoto mediante el siguiente comando ejecutado desde 1.1.1.3:&lt;br /&gt;&lt;pre&gt;ssh user@localhost -p 3333&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;SSH desde Windows&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;/span&gt;Las técnicas de tunneling también están disponibles para Windows, por ejemplo se puede utilizar el programa gratuito &lt;a href="http://www.chiark.greenend.org.uk/%7Esgtatham/putty/download.html"&gt;putty&lt;/a&gt;. La configuración de tunneling se realiza en la siguiente ventana de configuración:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_6K2axIH8bMw/SByiU3Bh1nI/AAAAAAAAAF0/SfEohNa5sUU/s1600-h/putty-tunneling.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_6K2axIH8bMw/SByiU3Bh1nI/AAAAAAAAAF0/SfEohNa5sUU/s320/putty-tunneling.png" alt="" id="BLOGGER_PHOTO_ID_5196206549330155122" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Aplicaciones&lt;br /&gt;&lt;/span&gt;Esta técnica nos permite acceder a casi cualquier servicio remoto, como por ejemplo la base de datos del backend o a un sitio web disponible sólo para la intranet.&lt;br /&gt;&lt;br /&gt;Además como la conexión se realiza sobre SSH, todas las comunicaciones se realizan mediante un canal encriptado, lo que nos permite dar una mayor seguridad a conexiones vía FTP o a una base de datos.&lt;br /&gt;&lt;br /&gt;La técnica de &lt;span style="font-style: italic;"&gt;port forwarding&lt;/span&gt; es bastante flexible y poderosa, por ejemplo en algunas situcaciones he llegado a realizar hasta 3 tunnelings anidados para lograr alcanzar servidores que están extremadamente resguardados.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-4121819141288240932?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/4121819141288240932/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=4121819141288240932" title="2 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/4121819141288240932?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/4121819141288240932?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/05/ssh-tunneling.html" title="SSH Tunneling" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_6K2axIH8bMw/SBycxXBh1lI/AAAAAAAAAFk/27CzHdfCg9I/s72-c/topologia.png" height="72" width="72" /><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;CUUMRn0-eSp7ImA9WxZaEEs.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-1276955969552816423</id><published>2008-04-24T14:47:00.005-04:00</published><updated>2008-04-24T14:54:47.351-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-24T14:54:47.351-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="unicode" /><category scheme="http://www.blogger.com/atom/ns#" term="i18n" /><category scheme="http://www.blogger.com/atom/ns#" term="google" /><title>UNICODE</title><content type="html">Este es un interesante artículo publicado en el &lt;a href="http://programa-con-google.blogspot.com"&gt;blog oficial de Google para desarrolladores en español&lt;/a&gt; sobre la historia de la codificación UNICODE hasta llegar a UTF-8.&lt;br /&gt;&lt;blockquote&gt;El mayor problema con el que os vais a encontrar es la falta de conocimientos sobre internacionalización. Es increíble como muchos programadores, por lo demás perfectamente capacitados, parecen convertirse en Paris Hilton cuando hablan de internacionalización. Eso si llegan a hablar... Vamos a empezar sentando las bases de lo que es un juego de carácteres (charset) y una codificación (encoding). El juego de carácteres es la tabla que traduce de un número a un "carácter" (o para ser más precisos un "codepoint"), y la codificación es el algoritmo que hemos seguido para guardar ese número.&lt;br /&gt;&lt;/blockquote&gt;Los invito a seguir leyendo &lt;a href="http://programa-con-google.blogspot.com/2008/04/internacionalizacin-i-unicode.html"&gt;internacionalización I - Unicode&lt;/a&gt; en el blog Programa con Google.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-1276955969552816423?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/1276955969552816423/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=1276955969552816423" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1276955969552816423?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1276955969552816423?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/04/unicode.html" title="UNICODE" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DUEGRX4ycCp7ImA9WxZbGEU.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-482536130917507169</id><published>2008-04-20T20:33:00.004-04:00</published><updated>2008-04-22T14:07:04.098-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-22T14:07:04.098-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="hmac" /><category scheme="http://www.blogger.com/atom/ns#" term="hash" /><category scheme="http://www.blogger.com/atom/ns#" term="seguridad" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>Hashing en PHP</title><content type="html">Una de las técnicas más básicas y simples relativas a la seguridad de información es el hashing o codificación de datos sensibles (ej.: contraseñas).&lt;br /&gt;&lt;br /&gt;Los algoritmos de hashing permiten generar un hash (o "huella") a partir de un string y tienen la característica principal de que no es posible obtener el string original a partir del hash generado.&lt;br /&gt;&lt;br /&gt;La longitud del hash es fija para cada algoritmo por lo que no importa si la entrada tiene una longitud de 1 byte o de varios gigas, el hash tendá siempre la misma longitud. Una de las características básicas de todo hash es que el menor cambio en el string de entrada implica un gran cambio en el hash generado.&lt;br /&gt;&lt;br /&gt;Los algoritmos de hash más populares son: MD5, SHA-1 y SHA-2. En la siguiente tabla muestro la longitud de algunos hash y un ejemplo al cifrar el texto "123":&lt;br /&gt;&lt;br /&gt;&lt;table border="1" bordercolor="#000000" cellpadding="1" cellspacing="1"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Algoritmo&lt;/th&gt;&lt;th&gt;Bits&lt;/th&gt;&lt;th&gt;Ejemplo&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;CRC32&lt;/td&gt;&lt;td&gt;32&lt;/td&gt;&lt;td&gt;&lt;span style=";font-family:courier new;font-size:85%;"  &gt;-2008521774&lt;/span&gt; (representado como int32)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MD4&lt;/td&gt;&lt;td&gt;128&lt;/td&gt;&lt;td  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;c58cda49f00748a3bc0fcfa511d516cb&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MD5&lt;/td&gt;&lt;td&gt;128&lt;/td&gt;&lt;td  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;202cb962ac59075b964b07152d234b70&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SHA-1&lt;/td&gt;&lt;td&gt;192&lt;/td&gt;&lt;td  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;40bd001563085fc35165329ea1ff5c5ecbdbbeef&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SHA-256 (SHA-2)&lt;/td&gt;&lt;td&gt;256&lt;/td&gt;&lt;td  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;a665a45920422f9d417e4867efdc4fb8 a04a1f3fff1fa07e998e86f7f7a27ae3&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;SHA-512 (SHA-2)&lt;/td&gt;&lt;td&gt;512&lt;/td&gt;&lt;td  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;3c9909afec25354d551dae21590bb26e 38d53f2173b8d3dc3eee4c047e7ab1c1 eb8b85103e3be7ba613b31bb5c9c3621 4dc9f14a42fd7a2fdb84856bca5c44c2&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RIPEMD-160&lt;/td&gt;&lt;td&gt;160&lt;/td&gt;&lt;td  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;e3431a8e0adbf96fd140103dc6f63a3f8fa343ab &lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;HAVAL-256&lt;/td&gt;&lt;td&gt;256&lt;/td&gt;&lt;td  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;e3891cb6fd1a883a1ae723f13ba336f5 86fa8c10506c4799c209d10113675bc1&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TIGER-128&lt;/td&gt;&lt;td&gt;128&lt;/td&gt;&lt;td  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;fe14a796bb0768a83398e6935842229b &lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TIGER-160&lt;/td&gt;&lt;td&gt;160&lt;/td&gt;&lt;td  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;fe14a796bb0768a83398e6935842229bbef2eeb0 &lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Como información adicional puedes ver el siguiente sitio que permite generar &lt;a href="http://www.johnmaguire.us/tools/hashcalc/index.php?strtohash=123&amp;amp;mode=hash"&gt;hashes para múltiples algotirmos&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Debido a que la longitud del hash es de tamaño fijo, existe la posibilidad de que ocurran colisiones, las cuales son extremadamente improblables pero no imposibles, por ejemplo en MD5 teóricamente se pueden generar 3.4 × 10&lt;sup&gt;38&lt;/sup&gt; (2&lt;sup&gt;128&lt;/sup&gt;) valores diferentes de hash y en SHA-256 son casi 1.2 × 10&lt;sup&gt;77&lt;/sup&gt; (2&lt;sup&gt;256&lt;/sup&gt;) valores diferentes.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Aplicaciones&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Entre las aplicaciones más comunes de hash se pueden contar las siguientes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Almacenamiento de contraseñas.&lt;/li&gt;&lt;li&gt;Generación de tokens (ej: para sesión de usuarios, cookies).&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Implementación de arreglos asociativos.&lt;/li&gt;&lt;li&gt;Checksums (ej: al transmitir información como un torrent, un instalador o un archivo .ISO).&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Digest para firma electrónica (ej: xmlsignature).&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;A continuación me referiré al almacenamiento de contraseñas.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Almacenamiento de contraseñas mediante HASH&lt;/span&gt;&lt;br /&gt;Al guardar las contraseñas codificadas mediante un hash, se imposibilita conocer la contraseña original tanto para un eventual atacante como también para un administrador.&lt;br /&gt;&lt;br /&gt;Otro alcance de esta técnica es para que -por políticas de seguridad, cumplimiento de estándares de certificación o por cumplimiento de los términos de uso- la organización no maneje las contraseñas originales de sus usuarios.&lt;br /&gt;&lt;br /&gt;El mecanismo más común para autenticar usuarios es por medio de un login y password, lo que implica que en la base de datos exista una tabla que llamaremos &lt;code&gt;usuarios&lt;/code&gt; que contiene al menos las columnas &lt;code&gt;usua_login&lt;/code&gt; y &lt;code&gt;usua_password&lt;/code&gt;, dando como resultado la siguiente tabla de usuarios:&lt;br /&gt;&lt;br /&gt;&lt;table border="1"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;usua_login&lt;/th&gt;&lt;th&gt;usua_password&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;jessica&lt;/td&gt;&lt;td&gt;p7q,f2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;pedro&lt;/td&gt;&lt;td&gt;bonsai&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;rodrigo&lt;/td&gt;&lt;td&gt;ro123&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Como podemos darnos cuenta, cualquiera que tenga acceso a la base de datos podrá conocer la contraseña de los usuarios. Existen muchas técnicas para lograr tener acceso a la base de datos, como por ejemplo errores documentados de software, SQL injection, errores en páginas de inicio de sesión, programas como PhpMyAdmin que no estén lo suficientemente resguardados, usuarios al interior de la organización o ingeniería social.&lt;br /&gt;&lt;br /&gt;Por lo tanto el primer nivel de seguridad consiste en guardar las contraseñan cifradas con algún algoritmo de hashing. En la actualidad el algoritmo más popular es MD5, sin embargo desde hace algunos años se han reportado ataques satisfactorios a este algoritmo. Por lo tanto recomiendo utilizar algoritmos de mayor dureza como SHA-1 o SHA-256 los cuales ya están disponibles en muchas herramientas desarrollo. A continuación presento una tabla de las funciones en PHP para codificar en algunos de los algoritmos de hashing:&lt;br /&gt;&lt;br /&gt;&lt;table border="1"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;MD5&lt;/th&gt;&lt;td style="font-family: courier new;"&gt;md5($txt)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;SHA-1&lt;/th&gt;&lt;td style="font-family: courier new;"&gt;sha1($txt)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;sha-256&lt;/th&gt;&lt;td style="font-family: courier new;"&gt;mhash(MHASH_SHA256, $txt)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;tiger-128&lt;/th&gt;&lt;td style="font-family: courier new;"&gt;mhash(MHASH_TIGER128, $txt)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;haval-256&lt;/th&gt;&lt;td style="font-family: courier new;"&gt;mhash(MHASH_HAVAL256, $txt)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;span style="font-style: italic;"&gt;La función &lt;/span&gt;&lt;code style="font-style: italic;"&gt;hash()&lt;/code&gt;&lt;span style="font-style: italic;"&gt; requiere tener PHP con el módulo mhash. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;En estos ejemplos utilizaré SHA-1, por lo que la tabla &lt;code&gt;usuarios&lt;/code&gt; quedará con los siguientes datos:&lt;br /&gt;&lt;br /&gt;&lt;table border="1"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;usua_login&lt;/th&gt;&lt;th&gt;usua_password&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;jessica&lt;/td&gt;&lt;td&gt;1f9004142372e078dbda694238d82a192760c170&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;pedro&lt;/td&gt;&lt;td&gt;9562384bbab4d406ee06012638ca65f403d1bd1f&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;rodrigo&lt;/td&gt;&lt;td&gt;0f8342a2ed2797cb3d19b926d5c98db79b5a8708&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Al guardar los datos codificados se debe considerar que las rutinas de inicio de sesión y de gestión de usuarios deben ser intermediadas por un proceso que convierta la contraseña original en la contraseña codificada, como por ejemplo el siguiente código:&lt;br /&gt;&lt;pre&gt;$password = "bonsai";&lt;br /&gt;$hash     = sha1($password);&lt;br /&gt;&lt;/pre&gt;A este nivel hemos logrado que los password se guarden cifrados en la base de datos y sea (teóricamente) imposible conocer los passwords originales. Sin embargo, un atacante que logre acceder a la base de datos aún tendrá posibilidad de conocer algunas de las contraseñas.&lt;br /&gt;&lt;br /&gt;Si buscamos en google por la &lt;a href="http://www.google.cl/search?q=9562384bbab4d406ee06012638ca65f403d1bd1f"&gt;contraseña codificada de Pedro&lt;/a&gt; encontraremos inmediatamente que dicho hash corresponde al texto "bonasi". La contraseña de Pedro (bonsai) es conocida como una palabra de diccionario, es decir una palabra común. En internet existen &lt;a href="http://md5.xpzone.de/"&gt;muchos&lt;/a&gt; &lt;a href="http://gdataonline.com/seekhash.php"&gt;sitios&lt;/a&gt; &lt;a href="http://md5decryption.com/"&gt;donde &lt;/a&gt;tienen indexados millones de palabras con su correspondiente hash -principalmente MD5- por lo que mediante ataques por diccionario es posible obtener las contraseñas de los usuarios más descuidados.&lt;br /&gt;&lt;br /&gt;Jessica y en menor medida Rodrigo utilizaron contraseñas que no son palabras de diccionario ("p7q,f2" y "ro123" respectivamente) por lo que es más dificil descubrir sus contraseñas originales.&lt;br /&gt;&lt;br /&gt;Lamentablemente, muchos desarrolladores llegan sólo hasta este nivel y descuidan agregar un mayor nivel de seguridad el cual se puede realizar con un cambio muy simple que describo a continuación.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Almacenamiento de contraseñas mediante HMAC&lt;/span&gt;&lt;br /&gt;Un &lt;a href="http://en.wikipedia.org/wiki/HMAC"&gt;algoritmo HMAC&lt;/a&gt; no es más que un algoritmo HASH al cual -antes de codificar el texto original- se le añade un texto adicional (key o clave secreta).&lt;br /&gt;&lt;br /&gt;En un principio realizabamos la codificacion de la contraseña mediante el siguiente código:&lt;br /&gt;&lt;pre&gt;$password = "bonsai";&lt;br /&gt;$hash     = sha1($password);&lt;br /&gt;&lt;/pre&gt;En este ejemplo vamos a escoger la clave secreta "xy45" y vamos a codificar las contraseñas utilizando la siguiente variación:&lt;br /&gt;&lt;pre&gt;$password = "bonsai";&lt;br /&gt;$key      = "xy45";&lt;br /&gt;$hash     = sha1($key.$password);&lt;br /&gt;&lt;/pre&gt;Es importante que la clave secreta sea lo suficientemente larga y compleja. Recomiendo que la longitud sea de 8 caracteres y se utilicen combinaciones de mayusculas, minusculas, números y caracteres de puntuación. Opcionalmente, en vez de una simple concatenación, PHP provee la función &lt;code&gt;hash_hmac()&lt;/code&gt; especialmente diseñada para estos efectos.&lt;br /&gt;&lt;br /&gt;Gracias a esta técnica obtendremos la siguiente tabla de usuarios:&lt;br /&gt;&lt;br /&gt;&lt;table border="1"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;usua_login&lt;/th&gt;&lt;th&gt;usua_password&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;jessica&lt;/td&gt;&lt;td&gt;25062347f18d9e8a85849aade94b38beaea54653&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;pedro&lt;/td&gt;&lt;td&gt;742cc2861f5c611452bf7e78c8bcbc3899851a56&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;rodrigo&lt;/td&gt;&lt;td&gt;44f789c9642a64513e8682a7dea072dedae85cd9&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Estas contraseñas ahora son invulnerables a un ataque directo de diccionario y por lo tanto brindan un mayor nivel de seguridad. Sin embargo, aún es posible obtener las contraseñas originales por medio de un ataque de colisión.&lt;br /&gt;&lt;br /&gt;Si el atacante conoce la clave secreta HMAC, le bastaría con crear un pequeño programa que genere las contraseñas en HMAC a partir de un &lt;a href="http://www.word-list.com/"&gt;diccionario de palabras&lt;/a&gt; para luego compararlas con las contraseñas almacendas en la base de datos. Si no conoce la clave secreta HMAC, basta con cambiar -por medio de la aplicación- varias veces la contraseña de algún usuario del sistema y luego comparar el string HMAC generado con el del resto de usuarios.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Almacenamiento de contraseñas mediante HMAC con distinta clave secreta&lt;/span&gt;&lt;br /&gt;La solución a este tipo de ataque consiste en que la contraseña de cada usuario se codifique con una clave secreta HMAC diferente, como por ejemplo el username, la casilla de correos del usuario o agún otro dato variable. Nuestra nueva función de codificación quedaría de la siguiente manera:&lt;br /&gt;&lt;pre&gt;$username = "pedro";&lt;br /&gt;$password = "bonsai";&lt;br /&gt;$key      = "xy45";&lt;br /&gt;$hash     = sha1($key.$username.$password);&lt;br /&gt;&lt;/pre&gt;En este nuevo escenario aún es posible que un atacante obtenga algún password por colisión pero tendrá que generar un diccionario de claves usuario por usuario.&lt;br /&gt;&lt;br /&gt;Otra ventaja -o desventaja según cómo se le mire- es que si se cambia el login del usuario, el password se invalidará. Por lo que en la eventualidad de cambiar el login, se deberá volver a generar el password.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Medidas adicionales de seguridad&lt;/span&gt;&lt;br /&gt;Se recomienda mantener oculta la clave secreta HMAC e implementar mecanismos para limitar el registro masivo de usuarios y cambios masivos de contraseña.&lt;br /&gt;&lt;br /&gt;También se pueden implementar -a nivel de software- políticas de seguridad a la gestión de contraseñas para no permitir que los usuarios utilicen palabras de diccionario o contraseñas demasiado cortas.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-482536130917507169?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/482536130917507169/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=482536130917507169" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/482536130917507169?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/482536130917507169?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/04/hashing-en-php.html" title="Hashing en PHP" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;AkIMSHo4eyp7ImA9WxZUGEU.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-1417167507699258211</id><published>2008-04-10T23:35:00.006-04:00</published><updated>2008-04-11T00:36:29.433-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-11T00:36:29.433-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="unicode" /><category scheme="http://www.blogger.com/atom/ns#" term="xml" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>Conversión de UNICODE y LATIN1 en PHP 5</title><content type="html">La principal innovación de PHP 6 será el soporte nativo a UNICODE, sin embargo aún queda un largo camino.&lt;br /&gt;&lt;br /&gt;Algunos módulos de PHP 5 internamente ya operan con UNICODE como es el caso de &lt;a href="http://php.net/dom"&gt;DOM&lt;/a&gt; que está desarrollado sobre &lt;a href="http://xmlsoft.org/"&gt;libxml2&lt;/a&gt;, esta característica brinda un gran potencial pero a la vez da muchos dolores de cabeza ya que hay que hacer convivir datos codificados usualmente en ISO-8859-1 (LATIN1) y UTF-8.&lt;br /&gt;&lt;br /&gt;Para poder realizar conversiones de codificación PHP provee de las funciones &lt;code&gt;utf8_encode()&lt;/code&gt; y &lt;code&gt;utf8_decode()&lt;/code&gt;. Sin embargo, es bastante común equivocarse y convertir a UTF-8 datos que ya están en dicha codificación o cometer el mismo error con datos codificados ISO-8859-1 lo cual nos corrompe algunos caracteres y puede causar errores en documentos XML y WebServices.&lt;br /&gt;&lt;br /&gt;Este es un ejemplo de errores típicos de codificación:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_6K2axIH8bMw/R_7qfDcsFAI/AAAAAAAAAFE/TvRWh3sTIpg/s1600-h/codificacion.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_6K2axIH8bMw/R_7qfDcsFAI/AAAAAAAAAFE/TvRWh3sTIpg/s320/codificacion.jpg" alt="" id="BLOGGER_PHOTO_ID_5187841639999869954" border="0" /&gt;&lt;/a&gt;En otras ocasiones, cuando hay que procesar datos de entrada (ej: leer archivos), uno no sabe de antemano si los datos a procesar vendrán codificados en UTF-8 o ISO-8859-1, o en un peor escenario podrían recibirse datos en ambas codificaciones.&lt;br /&gt;&lt;br /&gt;Para solucionar estos problemas he construido unas funciones que detectan la codificación de un string y de esta manera realizan la conversión de codificación sólo si es necesario. Las funciones la he llamado &lt;code&gt;latin1()&lt;/code&gt; que convierte los datos a ISO-8859-1 y &lt;code&gt;utf8()&lt;/code&gt; que convierte a UTF-8.&lt;br /&gt;&lt;br /&gt;Debido a que utilizan la función &lt;code&gt;mb_detect_encoding()&lt;/code&gt;, es necesario que PHP tenga habilitado el módulo mbstring.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Función que converte un string a ISO-8859-1 (LATIN1)&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;function latin1($txt) {&lt;br /&gt; $encoding = mb_detect_encoding($txt, 'ASCII,UTF-8,ISO-8859-1');&lt;br /&gt; if ($encoding == "UTF-8") {&lt;br /&gt;     $txt = utf8_decode($txt);&lt;br /&gt; }&lt;br /&gt; return $txt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Función que converte un string a UTF-8&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;function utf8($txt) {&lt;br /&gt; $encoding = mb_detect_encoding($txt, 'ASCII,UTF-8,ISO-8859-1');&lt;br /&gt; if ($encoding == "ISO-8859-1") {&lt;br /&gt;     $txt = utf8_encode($txt);&lt;br /&gt; }&lt;br /&gt; return $txt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-1417167507699258211?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/1417167507699258211/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=1417167507699258211" title="10 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1417167507699258211?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1417167507699258211?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/04/conversion-de-unicode-y-latin1-en-php-5.html" title="Conversión de UNICODE y LATIN1 en PHP 5" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_6K2axIH8bMw/R_7qfDcsFAI/AAAAAAAAAFE/TvRWh3sTIpg/s72-c/codificacion.jpg" height="72" width="72" /><thr:total>10</thr:total></entry><entry gd:etag="W/&quot;CkECSX4yfCp7ImA9WxZUF0w.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-1449571008245095296</id><published>2008-04-08T20:15:00.015-04:00</published><updated>2008-04-08T23:11:08.094-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-08T23:11:08.094-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="criptografía" /><category scheme="http://www.blogger.com/atom/ns#" term="openssl" /><category scheme="http://www.blogger.com/atom/ns#" term="seguridad" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><category scheme="http://www.blogger.com/atom/ns#" term="xml signature" /><title>Criptografía en PHP</title><content type="html">La criptografía nos permite cifrar información para que pueda ser accedida sólo por las personas adecuadamente autorizadas.&lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;En informática la criptografía se puede dividir en dos grupos:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Criptografía simétrica: &lt;/span&gt;la encriptación y desencriptación se realiza con la misma llave.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Criptografía asimétrica:&lt;/span&gt; la encriptación se realiza con una llave y la desencriptación con otra diferente.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Muchos modelos de transmisión de información segura utilizan una combinación de ambos métodos para combinar sus ventajas.&lt;br /&gt;&lt;br /&gt;El término "llave", "clave" o en ingles "&lt;span style="font-style: italic;"&gt;key&lt;/span&gt;" se utiliza para identificar la secuencia de bits utilizada como "clave secreta" para el proceso de cifrar/descifrar la información.&lt;br /&gt;&lt;br /&gt;Una regla estándar en la criptografía es que la seguridad debe enfocarse en proteger las llaves de encriptación y no en la ocultación del algoritmo criptográfico (o &lt;a href="http://en.wikipedia.org/wiki/Cipher"&gt;cipher&lt;/a&gt;). De hecho es muy usual que los algoritmos sean públicos.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Criptografía simétrica&lt;/span&gt;&lt;br /&gt;La criptografía simétrica se basa en que la misma llave de encriptación se utiliza para desencriptar.&lt;br /&gt;&lt;br /&gt;Es relativamente rápido, fácil de implementar e implementable en hardware de bajo costo.&lt;a href="javascript:void(0)" tabindex="11" onclick="return false;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt; Presenta el problema que el emisor y receptor deben conocer previamente la llave de encriptación. Esto nos lleva al problema de cómo transmitirle al receptor la llave de encriptación y este es precisamente el principal problema de este modelo.&lt;br /&gt;&lt;br /&gt;Antiguamente el algoritmo más popular era &lt;a href="http://en.wikipedia.org/wiki/Data_Encryption_Standard"&gt;DES&lt;/a&gt; (&lt;span style="font-style: italic;"&gt;Data Encryption Standard&lt;/span&gt;), sin embargo en la actualidad es considerado un algoritmo inseguro ya que la llave de encriptación tiene una longitud de sólo 56 bits, una vez que se declaró este algoritmo como inseguro, se comenzó a utilizar Triple DES  que tiene una llave de 158 bits.&lt;br /&gt;&lt;br /&gt;En la actualidad el algortimo de cifrado simétrico más popular es &lt;a href="http://en.wikipedia.org/wiki/Advanced_Encryption_Standard"&gt;AES&lt;/a&gt; (Advanced Encryption Standard) también conocido como Rijndael (que es una combinación de lo nombre de sus autores) y la llave puede tener una longitud de 128, 196 y 256 bits.&lt;br /&gt;&lt;br /&gt;A continuación incluyo un ejemplo de crifrado utilizando Rijndael-256, es decir utilizamos una llave de 256 bits que es equivalente a los 32 caracteres del ejemplo:&lt;br /&gt;&lt;pre&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;// Datos de entrada&lt;br /&gt;$texto = 'frase secreta';&lt;br /&gt;$key   = '12345678901234567890123456789012';&lt;br /&gt;&lt;br /&gt;// Proceso de cifrado&lt;br /&gt;$iv    = 'abcdefghijklmnopqrstuvwxyz012345';&lt;br /&gt;$td = mcrypt_module_open('rijndael-256', '', 'ecb', '');&lt;br /&gt;mcrypt_generic_init($td, $key, $iv);&lt;br /&gt;$texto_cifrado = mcrypt_generic($td, $texto);&lt;br /&gt;mcrypt_generic_deinit($td);&lt;br /&gt;mcrypt_module_close($td);&lt;br /&gt;&lt;br /&gt;// Opcionalmente codificamos en base64&lt;br /&gt;$texto_cifrado = base64_encode($texto_cifrado);&lt;br /&gt;&lt;br /&gt;echo "$texto_cifrado\n";&lt;br /&gt;&lt;br /&gt;?&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-size:85%;"&gt;Este ejemplo requiere que PHP tenga habilitado el módulo mcrypt.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;A continuación muestro un ejemplo del proceso inverso donde se desencripta la información previamente encriptada:&lt;br /&gt;&lt;pre&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;// Opcionalmente descodificamos en base64&lt;br /&gt;$texto_cifrado = base64_decode($texto_cifrado);&lt;br /&gt;&lt;br /&gt;// Proceso de descifrado&lt;br /&gt;$td = mcrypt_module_open('rijndael-256', '', 'ecb', '');&lt;br /&gt;mcrypt_generic_init($td, $key, $iv);&lt;br /&gt;$texto = mdecrypt_generic($td, $texto_cifrado);&lt;br /&gt;$texto = trim($texto, "\0");&lt;br /&gt;&lt;br /&gt;echo "$texto\n";&lt;br /&gt;&lt;br /&gt;?&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Criptografía asimétrica&lt;/span&gt;&lt;br /&gt;La criptografía asimétrica tiene la característica que utiliza una llave para cifrar y otra para descifrar. Usualmente a una de estas llaves se le denomina clave privada y a la otra clave pública. La clave privada es conocida solo por el propietario de la misma, la clave pública puede ser conocida por todos. Gracias a este modelo se soluciona el principal problema de la criptografía simétrica.&lt;br /&gt;&lt;br /&gt;Sin embargo tiene el inconveniente que los algoritmos criptográficos son más complejos,  requieren mayor tiempo de procesamiento y el mensaje cifrado requiere de mayor longitud que el mensaje original. Además para proveer el mismo nivel de dureza que la criptografía simétrica (con respecto a ataques de fuerza bruta) requiere de llaves de mayor longitud, como por ejemplo una llave simétrica de 128 bits es aproximadamente equivalente a una llave asimétrica de 1024 bits.&lt;br /&gt;&lt;br /&gt;Gracias a la criptografía asimétrica es posible garantizar -entre otros- lo siguiente:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Confidencialidad: &lt;/span&gt;Codificar información que solo la pueda descifrar el receptor.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Autenticación: &lt;/span&gt;Validar que la persona sea quien dice ser.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Integridad: &lt;/span&gt;Asegurar que la información no haya sido alterada (ej: firma electrónica).&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;No repudio:&lt;/span&gt; Asegurar quién es el autor de cierta información.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Más adelante presentaré un ejemplo de cifrado asimétrico, pero antes de eso publicaré un artículo de openssl para enseñar a generar certificados digitales y llaves.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Firma electrónica&lt;/span&gt;&lt;br /&gt;La firma electrónica (que es derivada de la criptografía asimétrica) se ha convertido en una herramienta escencial en el desarrollo de aplicaciones y es un estándar de la industria y de gobierno electrónico (e-gov) para el tratamiento de información.&lt;br /&gt;&lt;br /&gt;La firma electrónica está construida sobre la infraestructura de llave pública (&lt;a href="http://en.wikipedia.org/wiki/Public_key_infrastructure"&gt;PKI&lt;/a&gt;) y permite asegurar la autoría e integridad de los documentos.&lt;br /&gt;&lt;br /&gt;Usualmente la implementación de firma electrónica se realiza en base a la especificación de XML Signature &lt;sup&gt;[&lt;a href="http://www.w3.org/TR/xmldsig-core/"&gt;w3c&lt;/a&gt;]&lt;/sup&gt; también conocida como xmldsig.&lt;br /&gt;&lt;br /&gt;Actualmente PHP no implementa XML Signature de manera nativa, sin embargo desde PHP 5.2.1 es posible implementarla (planeo a futuro dar algunos ejemplos de su implementación). Además &lt;a href="http://www.cdatazone.org/"&gt;Rob Richards&lt;/a&gt; que es miembro del equipo de desarollo de PHP (autor -entre otros- del módulo DOM) está trabajando en un módulo para PHP que ojalá algún día vea la luz.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-1449571008245095296?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/1449571008245095296/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=1449571008245095296" title="8 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1449571008245095296?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/1449571008245095296?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/04/criptografa-en-php.html" title="Criptografía en PHP" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>8</thr:total></entry><entry gd:etag="W/&quot;C0AFQn45cSp7ImA9WxZVFko.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-8852577376626788292</id><published>2008-03-27T21:39:00.007-03:00</published><updated>2008-03-27T23:35:13.029-03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-27T23:35:13.029-03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="linux" /><category scheme="http://www.blogger.com/atom/ns#" term="subversion" /><category scheme="http://www.blogger.com/atom/ns#" term="seguridad" /><category scheme="http://www.blogger.com/atom/ns#" term="respaldo" /><title>Haciendo un mirror de subversion</title><content type="html">&lt;a href="http://es.wikipedia.org/wiki/Subversion"&gt;Subversion&lt;/a&gt; -al igual que &lt;a href="http://es.wikipedia.org/wiki/CVS"&gt;CVS&lt;/a&gt;- es un software de &lt;a href="http://es.wikipedia.org/wiki/Control_de_versiones"&gt;control de versiones&lt;/a&gt;, todo desarrollador de software debe contar con una herramienta de este tipo y el no hacerlo simplemente es una gran irresponsabilidad (o estupidez).&lt;br /&gt;&lt;br /&gt;Una (pero no la principal) de las ventajas de utilizar subversion es que sirve como una herramienta de respaldo de código. Cuando uno mantiene respaldos, es importante que dichos respaldos sean almacenados en instalaciones diferentes a la fuente de información original, esto permite afrontar situaciones extremas como incendios o terremotos.&lt;br /&gt;&lt;br /&gt;Esta condición básica de seguridad no se da en mi caso ya que en mi empresa nuestro servidor subversion está en las mismas instalaciones donde trabajamos, por lo tanto nos vimos enfrentados al problema de respaldar en servidores remotos para lo cual implementé un mecanismo de mirroring de subversion mediante el comando &lt;code&gt;svnsync&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Modelo de operación&lt;/span&gt;&lt;br /&gt;El mecanismo que escogí para respaldar subversion es un mirroring que se actualiza cada vez que se hace COMMIT en el repositorio maestro, replicando los cambios en el servidor de respaldo. Este modelo de respaldo tiene las siguientes características:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;El mirror está siempre al día.&lt;/li&gt;&lt;li&gt;El mirror es actualizado sólo con los deltas enviados en el COMMIT por lo que la transferencia de datos es mínima.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;En caso de pérdida del servidor principal, basta con sacar una copia del mirror para poder restaurar el servidor principal.&lt;/li&gt;&lt;li&gt;El mirror presta las mismas funcionalidades del servidor principal con la excepción que es de sólo lectura.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;En términos resumidos la implementación del mirroring se realiza sacando una copia del repositorio, instalarla en un servidor de respaldo y finalmente se configura el servidor principal para que luego de cada COMMIT se ejecute el comando &lt;code&gt;svnsync&lt;/code&gt; que replicará todos los cambios en el servidor de respaldo (mirror).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Prerrequisitos&lt;/span&gt;&lt;br /&gt;Se asume que tienes conocimientos básicos de LINUX y de cómo administrar un servidor subversion, en caso que no seas muy experimentado puedes consultar el &lt;a href="http://svnbook.red-bean.com/"&gt;manual de subversion&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;IMPORTANTE: El procedimiento aquí descrito se enfoca en el proceso de creación y configuración del mirror. Debes asegurarte de realizar las configuraciones de control de acceso y seguridad necesarias.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Debemos contar con dos servidores (principal y respaldo) que tengan instalado subversion 1.4 o superior, es deseable que ambos servidores tengan la misma versión.&lt;br /&gt;&lt;br /&gt;El repositorio a respaldar lo vamos a denominar &lt;code&gt;proyecto&lt;/code&gt;. En los ejemplos, el servidor de mirror usará el nombre de dominio &lt;code&gt;mirror.com&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Procedimiento de configuración&lt;/span&gt;&lt;br /&gt;El primer paso es crear el repositorio principal (si ya tienes un repositorio que quieres respaldar, puedes omitir este paso).&lt;br /&gt;&lt;pre&gt;svnadmin create proyecto&lt;br /&gt;&lt;/pre&gt;Luego, hacemos una copia del repositorio recién creado, lo comprimimos y eliminamos la copia temporal. (Utilizamos hotcopy para evitar eventuales errores si justo al momento de hacer la copia alguien realizó un COMMIT en el repositorio):&lt;br /&gt;&lt;pre&gt;svnadmin hotcopy proyecto proyectobackup&lt;br /&gt;tar cfz proyectobackup.tgz proyectobackup&lt;br /&gt;rm -rf proyectobackup &lt;/pre&gt;A continuación transferimos el archivo comprimido al servidor de respaldo, donde lo descomprimimos y restauramos el nombre del repositorio a &lt;code&gt;proyecto&lt;/code&gt;:&lt;br /&gt;&lt;pre&gt;tar xfz proyectobackup.tgz&lt;br /&gt;mv proyectobackup proyecto&lt;br /&gt;cd proyecto&lt;br /&gt;&lt;/pre&gt;Ahora configuramos el repositorio mirror para no permitir que los usuarios hagan COMMIT, solamente se permitirá que haga COMMIT una cuenta de usuario especial que llamaremos &lt;code&gt;syncuser&lt;/code&gt;,  con contraseña &lt;code&gt;secret&lt;/code&gt; (debes definir escoger otra contraseña) y le damos permisos de escritura completos:&lt;br /&gt;&lt;pre&gt;echo "syncuser = secret" &gt;&gt; conf/passwd&lt;br /&gt;echo "[/]&lt;br /&gt;syncuser = rw" &gt;&gt; conf/authz&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-size:78%;"&gt;(las ultimas 2 lineas son un solo comando)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Para evitar que el resto de usuarios pueda hacer COMMIT creamos el archivo (con permisos de ejecución) &lt;code&gt;hooks/start-commit&lt;/code&gt; dentro del repositorio con el siguiente contenido:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;#!/bin/sh&lt;br /&gt;&lt;br /&gt;USER="$2"&lt;br /&gt;if [ "$USER" = "syncuser" ]; then exit 0; fi&lt;br /&gt;&lt;br /&gt;echo "No se permite hacer commit en el repositorio de respaldo" &gt;&amp;amp;2&lt;br /&gt;exit 1&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Luego debemos crear el script (con permisos de ejecución) &lt;code&gt;hooks/pre-revprop-change&lt;/code&gt; el cual es requerido por el comando &lt;code&gt;svnsync&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;#!/bin/sh&lt;br /&gt;&lt;br /&gt;USER="$3"&lt;br /&gt;&lt;br /&gt;if [ "$USER" = "syncuser" ]; then exit 0; fi&lt;br /&gt;&lt;br /&gt;echo "No se permite hacer commit en el repositorio de respaldo" &gt;&amp;amp;2&lt;br /&gt;exit 1&lt;br /&gt;&lt;/pre&gt;Con estos pasos hemos creado una copia del repositorio &lt;code&gt;proyecto&lt;/code&gt;, hemos creado el usuario &lt;code&gt;syncuser&lt;/code&gt; y configuramos el repositorio para que nadie más pueda hacer COMMIT. A continuación volvemos al servidor principal.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;En el servidor principal vamos a configurar el repositorio para indicarle que tiene un mirror (referenciado en el siguiente ejemplo con el nombre de dominio &lt;code&gt;mirror.com&lt;/code&gt;):&lt;br /&gt;&lt;pre&gt;svnsync init svn://mirror.com/proyecto svn://localhost/proyecto --username syncuser --password secret&lt;br /&gt;&lt;/pre&gt;Finalmente ejecutamos el proceso de sincronización, el cual copiará los datos hacia el servidor de respaldo:&lt;br /&gt;&lt;pre&gt;svnsync sync svn://mirror.com/proyecto --username syncuser --password secret&lt;br /&gt;&lt;/pre&gt;Con estos pasos hemos configurado el servidor principal para que apunte al mirror previamente creado. El paso final es automatizar el proceso de respaldo.&lt;br /&gt;&lt;br /&gt;Hay dos estrategias para sincronizar el mirror. La primera es realizar la sincronización en un proceso aparte, por ejemplo todas las noches. La segunda estrategia es que la sincronización se realice inmediatamente después de cada COMMIT.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Alternativa 1: Respaldo asincrónico&lt;/span&gt;&lt;br /&gt;Esta estrategia es útil cuando la velocidad del enlace entre el servidor principal y el mirror es muy lenta o cuando los COMMITs que se realizan son muy pesados.&lt;br /&gt;&lt;br /&gt;Basta con agregar una linea al &lt;a href="http://es.wikipedia.org/wiki/Cron_%28unix%29"&gt;cron&lt;/a&gt;, a continuación se muestra un ejemplo que realiza la sincronización todas las noches a las 04:15 hrs.:&lt;br /&gt;&lt;pre&gt;15 04 * * * svnsync sync svn://mirror.com/proyecto  --username syncuser --password secret&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;Alternativa 2: Respaldo&lt;/span&gt;&lt;span style="font-weight: bold;"&gt; en tiempo real&lt;/span&gt;&lt;br /&gt;Esta estrategia tiene la ventaja que el mirror permanecerá siempre actualizado cada vez que hagamos un COMMIT.&lt;br /&gt;&lt;br /&gt;Implica editar (o crear) el script ejecutable &lt;code&gt;hooks/post-commit &lt;/code&gt;y agregarle el siguiente código:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# Determino el nombre del repositorio&lt;br /&gt;NOMBREREPO=$(echo $REPOS | cut -d"/" -f4)&lt;br /&gt;&lt;br /&gt;svnsync sync svn://mirror.com/$NOMBREREPO  --username syncuser --password secret&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Luego de esto tendremos nuestro mirror de subversion configurado.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-8852577376626788292?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/8852577376626788292/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=8852577376626788292" title="5 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/8852577376626788292?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/8852577376626788292?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/haciendo-un-mirror-de-subversion.html" title="Haciendo un mirror de subversion" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>5</thr:total></entry><entry gd:etag="W/&quot;CkABQXs9cCp7ImA9WxZUF0w.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-240903121283330902</id><published>2008-03-27T12:12:00.001-03:00</published><updated>2008-04-08T23:12:30.568-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-08T23:12:30.568-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="xml" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><title>Usando el lenguaje HTML de manera correcta</title><content type="html">El lenguaje HTML es el que le da vida a la web; el requisito fundamental de todo desarrollador web es conocer como mínimo el lenguaje HTML, ya que sobre este se agregan otras tecnologías como CSS y Javascript.&lt;br /&gt;&lt;br /&gt;Actualmente la especificación de HTML mayormente recomendada es la XHTML 1.0, la cual en términos simples es HTML pero con las reglas del lenguaje XML. Otras versiones de HTML recomendadas por la W3C son XHTML 1.1, y la más antigua HTML 4.01.&lt;br /&gt;&lt;br /&gt;Podríamos resumir las principales reglas de XHTML en las siguientes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Todos los elementos no vacíos deben tener su tag correspondiente de cierre (&lt;code&gt;&amp;lt;p&amp;gt;hola&amp;lt;/p&amp;gt;&lt;/code&gt;)&lt;/li&gt;&lt;li&gt;Los tags que originalmente no consideraban tag de cierre deben utilizar la versión reducida de XML, es decir poniendo un slash al final del tag. (&lt;code&gt;&amp;lt;br/&amp;gt;&lt;/code&gt;)&lt;/li&gt;&lt;li&gt;Los valores de los atributos deben estar entre comillas simples. (&lt;code&gt;&amp;lt;a href="index.html"&amp;gt;&lt;/code&gt;)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Los nombres de los tags y atributos HTML deben estar en minúsculas.&lt;/li&gt;&lt;li&gt;Todos los atributos deben tener un valor. (&lt;code&gt;&amp;lt;option selected="selected"&amp;gt;&lt;/code&gt;)&lt;/li&gt;&lt;/ul&gt;Adicionalmente XHTML 1.0 incluye 3 variaciones:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;XHTML Strict&lt;/span&gt;: cumplimiento estricto de la especificación.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;XHTML Transitional&lt;/span&gt;: permite algunas excepciones de la especificación, es la más comúnmente utilizada.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;XHTML Frameset&lt;/span&gt;: soporte para frames.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;(HTML 4.01 también permite especificar &lt;span style="font-style: italic;"&gt;strict&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;transitional&lt;/span&gt; y &lt;span style="font-style: italic;"&gt;frameset&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;¿Cómo le decimos al browser la versión de HTML a utilizar?&lt;/span&gt;&lt;br /&gt;Para indicarle al navegador la versión de HTML, se utiliza una declaración al comienzo del archivo (en la primera línea) denominada DOCTYPE (&lt;span style="font-style: italic;"&gt;document type declaration&lt;/span&gt;) la cual indica las "reglas" del documento XML.&lt;br /&gt;&lt;br /&gt;Para el caso de XHTML 1.0 tenemos las siguientes alternativas &lt;sup&gt;[&lt;a href="http://www.w3.org/MarkUp/#flavors"&gt;w3c&lt;/a&gt;]&lt;/sup&gt;:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;XHTML 1.0 Strict&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;XHTML 1.0 Transitional&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;XHTML 1.0 Frameset&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Para la mayoría de los casos recomiendo utilizar &lt;span style="font-style: italic;"&gt;XHTML 1.0 Transitional&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Quirksmode&lt;/span&gt;&lt;br /&gt;Antiguamente no existía esta técnica para poder decirle al navegador la versión de HTML que se está utilizando, por lo que en los casos de las páginas HTML que no incluyan un DOCTYPE, los navegadores utilizan un modo de compatibilidad denominado &lt;a href="http://es.wikipedia.org/wiki/Quirks_Mode"&gt;quirksmode&lt;/a&gt; en el cual asumen que la página está construida con técnicas obsoletas y errores comunes del siglo pasado por lo que interpreta la página con reglas diferentes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-240903121283330902?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/240903121283330902/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=240903121283330902" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/240903121283330902?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/240903121283330902?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/usando-el-lenguaje-html-de-manera.html" title="Usando el lenguaje HTML de manera correcta" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CE4DRH85cCp7ImA9WxZVFEw.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-3825999520613055497</id><published>2008-03-24T23:18:00.003-03:00</published><updated>2008-03-24T23:42:55.128-03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-24T23:42:55.128-03:00</app:edited><title>WebKit alcanza un 95/100 en el Acid3 test</title><content type="html">WebKit (que es el core de &lt;a href="http://en.wikipedia.org/wiki/Safari_%28web_browser%29"&gt;Safari&lt;/a&gt;  y que está basado en el motor KHTML de &lt;a href="http://en.wikipedia.org/wiki/Konqueror"&gt;Konqueror&lt;/a&gt;) &lt;a href="http://webkit.org/blog/167/webkit-gets-an-a-on-acid3/"&gt;acaba de anunciar&lt;/a&gt; que su más reciente versión en desarrollo obtuvo un 95% de cumplimiento del test &lt;a href="http://en.wikipedia.org/wiki/Acid3"&gt;Acid3&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Esto demuestra que Internet Explorer 8 está a años luz del cumplimiento de los estándares ya que recién su versión beta 1 logró cumplir con el test Acid2. Del Acid3 aún ni hablan.&lt;br /&gt;&lt;br /&gt;El puntaje para otros motores de navegadores (en su versión de desarrollo) es el siguiente:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Presto (Opera): 77/100&lt;/li&gt;&lt;li&gt;Gecko (Mozilla Firefox): 71/100&lt;/li&gt;&lt;li&gt;KHTML (Konqueror): 62/100&lt;/li&gt;&lt;li&gt;Trident (Internet Explorer): 17/100&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;La imagen de referencia de Acid3 es la siguiente, que corresponde a un conjunto de pruebas de CSS3, DOM2 y SVG entre otras hierbas.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_6K2axIH8bMw/R-hkf-41HjI/AAAAAAAAAEk/CscpsZHO0vk/s1600-h/acid3.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_6K2axIH8bMw/R-hkf-41HjI/AAAAAAAAAEk/CscpsZHO0vk/s320/acid3.png" alt="" id="BLOGGER_PHOTO_ID_5181501871909838386" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Además debo decir que la &lt;a href="http://www.apple.com/safari/"&gt;versión Windows de Safari 3.1&lt;/a&gt; recién liberada  (&lt;a href="http://alt1040.com/2008/03/la-polemica-de-apple-safari-y-el-actualizador-de-software-en-windows/"&gt;con polémica incluida&lt;/a&gt;) me dejó gratamente impresionado por su velocidad para renderizar las páginas, tal como profesa su publicidad.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-3825999520613055497?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/3825999520613055497/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=3825999520613055497" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/3825999520613055497?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/3825999520613055497?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/webkit-alcanza-un-95100-en-el-acid3.html" title="WebKit alcanza un 95/100 en el Acid3 test" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_6K2axIH8bMw/R-hkf-41HjI/AAAAAAAAAEk/CscpsZHO0vk/s72-c/acid3.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;AkEHSX86fCp7ImA9WxZVEUQ.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-6993349876717278133</id><published>2008-03-22T10:34:00.008-03:00</published><updated>2008-03-22T12:10:38.114-03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-22T12:10:38.114-03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="estándares" /><category scheme="http://www.blogger.com/atom/ns#" term="browsers" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><category scheme="http://www.blogger.com/atom/ns#" term="css" /><title>El dilema de Internet Explorer 8</title><content type="html">Desde el momento en que se liberó Internet Explorer 7 -el cual fue un gran avance con respecto a su viejo antecesor IE6- ya se estaba trabajando en la versión 8 del popular navegador.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_6K2axIH8bMw/R-UcwO41HgI/AAAAAAAAAEM/XXt4Dy2lW0E/s1600-h/acid2.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://1.bp.blogspot.com/_6K2axIH8bMw/R-UcwO41HgI/AAAAAAAAAEM/XXt4Dy2lW0E/s320/acid2.jpg" alt="" id="BLOGGER_PHOTO_ID_5180578561315380738" border="0" /&gt;&lt;/a&gt;Una de las principales cualidades que promete esta nueva versión será el gran salto en el cumplimiento de los estándares W3C, específicamente en lo que respecta a XHTML y CSS2.1. La versión beta 1 ya fue liberada y cumple con el &lt;a href="http://www.webstandards.org/files/acid2/test.html"&gt;acid test 2&lt;/a&gt;. Sin embargo, el equipo de desarrollo se está topando con un grave dilema al respecto, digamos que el 99.9% de los sitios de internet no son fieles cumplidores de los estándares por lo que podrían (y van a) ser mal interpretados por Internet Explorer 8.&lt;br /&gt;&lt;br /&gt;Esta situación era de esperar ya que el ciclo de desarrollo de un sitio web realizado por desarrolladores "responsables" consiste en desarrollar basado en los estándares para luego probar, corregir y &lt;span style="font-weight: bold;"&gt;parchar &lt;/span&gt;el diseño en base a pruebas en Internet Explorer, Firefox y ocasionalmente Opera y Safari. Y en el caso de los desarrolladores &lt;del&gt;irresponsables&lt;/del&gt; "no-responsables" simplemente basta con que se vea bien en IE.&lt;br /&gt;&lt;br /&gt;El equipo de desarrollo de Redmond inicialmente tomó la decisión de que su nuevo producto por defecto utilice el modo "IE7 standard-compliant mode", es decir, interpretará el código de un modo que los sitios que actulamente se visualizan correctamente en IE7 sigan haciéndolo y los sitios que cumplan fielmente los estándares deberían poner un tag &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; para indicar que sean interpretados en "Full standard-compliant mode".&lt;br /&gt;&lt;br /&gt;Esta postura causó indignación en los miembros más fanáticos de la comunidad de los estándares web, pero finalmente &lt;a href="http://blogs.msdn.com/ie/archive/2008/03/03/microsoft-s-interoperability-principles-and-ie8.aspx"&gt;Microsoft cambió su postura&lt;/a&gt; y el nuevo IE8 por defecto interpretará las páginas en "Full  standard-compliant mode":&lt;br /&gt;&lt;blockquote&gt;We’ve decided that IE8 will, by default, interpret web content in the most standards compliant way it can. This decision is a change from what we’ve posted previously.&lt;/blockquote&gt;&lt;br /&gt;Es una postura que francamente no me esperaba de Microsoft:&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;We think that acting in accordance with principles is important, and IE8’s default is a demonstration of the interoperability principles in action. While we do not believe any current legal requirements would dictate which rendering mode a browser must use, this step clearly removes this question as a potential legal and regulatory issue. As stated above, we think it’s the better choice.&lt;/blockquote&gt; &lt;p&gt;&lt;/p&gt;Ahora bien, este cambio que se vendrá con IE8 augura un gran problema para los actuales sitios ya que deberán realizar correcciones a su código HTML para poder ser correctamente visualizados en IE8. La siguiente es una captura de Google Maps en IE8:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_6K2axIH8bMw/R-Ugcu41HiI/AAAAAAAAAEc/xBQgHSMXDOk/s1600-h/ie8-maps.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_6K2axIH8bMw/R-Ugcu41HiI/AAAAAAAAAEc/xBQgHSMXDOk/s400/ie8-maps.jpg" alt="" id="BLOGGER_PHOTO_ID_5180582624354442786" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;La solución más sencilla es indicarle a IE8 que la página debe ser interpretada como IE7 mediante el siguiente tag &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; descrito en &lt;a href="http://alistapart.com/articles/beyonddoctype"&gt;A List Apart&lt;/a&gt;:&lt;pre&gt;&amp;lt;meta equiv="X-UA-Compatible" content="IE=7"/&amp;gt;&lt;/pre&gt;Les recuerdo que aún falta mucho tiempo para que IE8 sea liberado por lo que aún pueden aparecer muevas noticias al respecto.&lt;br /&gt;&lt;br /&gt;Como artículo relacionado les recomiendo un excelente artículo de Joel on Software (algo que no es novedad en él) que describe de manera magistral el problema de la compatibilidad en la web: &lt;a href="http://www.joelonsoftware.com/items/2008/03/17.html"&gt;Martian Headsets&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-6993349876717278133?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/6993349876717278133/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=6993349876717278133" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/6993349876717278133?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/6993349876717278133?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/el-dilema-de-intenet-explorer-8.html" title="El dilema de Internet Explorer 8" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_6K2axIH8bMw/R-UcwO41HgI/AAAAAAAAAEM/XXt4Dy2lW0E/s72-c/acid2.jpg" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;A08CQX86fyp7ImA9WxZVEUQ.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-4067838587261230574</id><published>2008-03-17T21:21:00.006-03:00</published><updated>2008-03-22T12:31:00.117-03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-22T12:31:00.117-03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="zend" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>Zend Framework 1.5 ya está aquí</title><content type="html">&lt;a href="http://zend.com/"&gt;ZEND&lt;/a&gt; acaba de liberar la versión 1.5 de &lt;a href="http://framework.zend.com/"&gt;Zend Framework&lt;/a&gt;, el cual es uno de los mejores frameworks disponibles para PHP.&lt;br /&gt;&lt;br /&gt;Esta noticia se suma a la de hace algunas semanas publicada por &lt;a href="http://andigutmans.blogspot.com/2008/02/zend-framework-to-be-part-of-ubuntu.html"&gt;Andi Gutmans&lt;/a&gt; que confirma que Zend Framework formará parte de Ubuntu 8.04 vía el repositorio Ubuntu Universe.&lt;br /&gt;&lt;br /&gt;Entre las ventajas de Zend Framework se cuenta con que es un código eficiente, implementa el modelo MVC y posee una gran cantidad de módulos extremadamente útiles.&lt;br /&gt;&lt;br /&gt;En mi caso particular no utilizo ningún framework ya que muchas veces agregan un overhead adicional a los scripts y además me obliga a adaptar mi diseño de la aplicación a la de esas herramientas. Por otro lado, este último punto es una de las ventajas de los frameworks, el hecho de obligar al usuario a ser ordenado permite desarrollar código que es más mantenible en el tiempo. Otra de las ventajas evidentes de un framework es que evita tener que reinventar la rueda creando rutinas básicas como por ejemplo control de sesión y manejo de templates lo cual nos permite construir aplicaciones web contando con una base preexistente.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-4067838587261230574?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/4067838587261230574/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=4067838587261230574" title="1 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/4067838587261230574?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/4067838587261230574?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/zend-framework-15-ya-est-aqu.html" title="Zend Framework 1.5 ya está aquí" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;Dk4DQ3k4fSp7ImA9WxZWFkQ.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-7798969475908555026</id><published>2008-03-15T15:29:00.014-03:00</published><updated>2008-03-16T16:16:12.735-03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-16T16:16:12.735-03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>Lo nuevo de PHP 5.3</title><content type="html">La versión actual de PHP es la 5.2.5 y la siguiente versión próxima a ser liberada es la 5.2.6 que principalmente incluye parches que resuelven bugs y problemas seguridad.&lt;br /&gt;&lt;br /&gt;Sin embargo, desde hace varios meses se está trabajando en la versión 5.3 que incorporará nuevas funcionalidades que desde hace algún tiempo estaban siendo pedidas por parte de la comunidad. Esta nueva versión debiera ser publicada a mediados de 2008.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Namespaces (¡por fin!)&lt;/span&gt;&lt;br /&gt;Sin duda el mayor cambio en esta nueva versión es el soporte a namespaces. Hasta ahora la técnica que se utilizaba para evitar la colisión de nombres era simplemente agregarle un prefijo al nombre de cada clase obteniendo nombres tan largos como: &lt;code&gt;MDB2_Driver_Datatype_Common&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Algunas de las características de la implementación de namespaces son las siguientes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Namespaces para clases, funciones y constantes.&lt;/li&gt;&lt;li&gt;Múltiples namespaces en un mismo archivo.&lt;/li&gt;&lt;li&gt;Se incorpora la constante &lt;code&gt;__NAMESPACE&lt;span style="font-family:Georgia,serif;"&gt;__&lt;/span&gt;&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;A continuación incluyo un ejemplo básico de cómo será el uso de namespaces en PHP 5.3:&lt;br /&gt;&lt;pre&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;namespace DB;&lt;br /&gt;class MyDate {}&lt;br /&gt;$clase1 = new MyDate();&lt;br /&gt;&lt;br /&gt;namespace TYPES::BASIC;&lt;br /&gt;class MyDate {}&lt;br /&gt;$clase2 = new MyDate();&lt;br /&gt;&lt;br /&gt;var_dump(&lt;br /&gt;get_class($clase1),&lt;br /&gt;get_class($clase2),&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;?&amp;gt;&lt;br /&gt;&lt;/pre&gt;La salida de este script es la siguiente:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;string(10) "DB::MyDate"&lt;br /&gt;string(20) "TYPES::BASIC::MyDate"&lt;br /&gt;&lt;/pre&gt;En el repositorio CVS de PHP podrán encontrar un &lt;a href="http://cvs.php.net/viewvc.cgi/php-src/README.namespaces?revision=1.7&amp;amp;view=markup"&gt;README con más detalles de la implementación de namespaces&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Operador "?:"&lt;br /&gt;&lt;/span&gt;Esta es otra funcionalidad que llevaba bastante tiempo siendo solicitada por algunos desarrolladores. Su utilidad es similar a la que presta el operador &lt;code&gt;||&lt;/code&gt; en javascript, tal como muestro en el siguiente ejemplo donde se le asigna un valor por defecto a las variables &lt;code&gt;width &lt;/code&gt;y &lt;code&gt;height&lt;/code&gt;:&lt;br /&gt;&lt;pre&gt;&amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;&lt;br /&gt;function foo(width, height) {&lt;br /&gt;width  = width  || 640;&lt;br /&gt;height = height || 480;&lt;br /&gt;// ...&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;/pre&gt;En PHP podríamos hacer las siguientes validaciones:&lt;br /&gt;&lt;pre&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;$a = true ?: false;               // true&lt;br /&gt;$a = false ?: true;               // true&lt;br /&gt;$a = "" ?: 1;                     // 1&lt;br /&gt;$a = 0 ?: 2;         // 2&lt;br /&gt;$a = array() ?: array(1);         // array(1)&lt;br /&gt;$a = strlen("") ?: strlen("a");   // 1&lt;br /&gt;&lt;br /&gt;?&amp;gt;&lt;/pre&gt;&lt;span style="font-size:78%;"&gt;&lt;a href="http://ilia.ws/"&gt;Este ejemplo fue obtenido de http://ilia.ws&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Como vemos no es una &lt;span style="font-style: italic;"&gt;super-hiper-killer-funcionalidad&lt;/span&gt;, pero para los usuarios habituados a javascript es un placer contar con ella.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Otras mejoras&lt;br /&gt;&lt;/span&gt;Otras mejoras -además de las habilituales correcciones de bugs, mejoras en seguridad y optimización de código- incluidas en PHP 5.3 son las siguientes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Mejora en performance de &lt;code&gt;md5()&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Optimización de manejo de excepciones.&lt;/li&gt;&lt;li&gt;Nueva constante &lt;code&gt;__DIR__&lt;/code&gt;. (esto nos evitará hacer &lt;code&gt;dirname(__FILE__)&lt;/code&gt;)&lt;/li&gt;&lt;li&gt;Nueva función mágina &lt;code&gt;_&lt;/code&gt;&lt;code&gt;_callStatic()&lt;/code&gt; que complementa a &lt;code&gt;_&lt;/code&gt;&lt;code&gt;_call()&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;El procesamiento de métodos estáticos se movió desde el tiempo de compilación a tiempo de ejecución (&lt;a href="http://blog.felho.hu/what-is-new-in-php-53-part-2-late-static-binding.html"&gt;Late Static Binding&lt;/a&gt;).&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Nuevo driver nativo de MySQL (&lt;a href="http://dev.mysql.com/downloads/connector/php-mysqlnd/"&gt;MySQLnd&lt;/a&gt;) diseñado especialmente para PHP.&lt;/li&gt;&lt;li&gt;Soporte para archivos .ini (al estilo de &lt;code&gt;.htaccess&lt;/code&gt;).&lt;/li&gt;&lt;li&gt;Nuevas funcionalidades de OpenSSL destinadas a simplificar las implementaciones de OpenID en PHP.&lt;/li&gt;&lt;li&gt;Profiling de XSLT. (&lt;code&gt;$xsltprocessor-&gt;setProfiling("/tmp/profile.txt")&lt;/code&gt;);&lt;/li&gt;&lt;li&gt;Nueva constante de error &lt;code&gt;E_DEPRECATED&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Garbage collector (&lt;code&gt;gc_enable()&lt;/code&gt;).&lt;/li&gt;&lt;li&gt;NOWDOC, similar a los &lt;a href="http://en.wikipedia.org/wiki/Here_document"&gt;HEREDOC&lt;/a&gt; pero que no interpreta las variables como &lt;code&gt;$myvar&lt;/code&gt;.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-7798969475908555026?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/7798969475908555026/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=7798969475908555026" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/7798969475908555026?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/7798969475908555026?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/lo-nuevo-de-php-53.html" title="Lo nuevo de PHP 5.3" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DkIER3c6fip7ImA9WxZWFU4.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-56869904576706395</id><published>2008-03-14T19:00:00.001-03:00</published><updated>2008-03-14T19:41:46.916-03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-14T19:41:46.916-03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rpm" /><category scheme="http://www.blogger.com/atom/ns#" term="linux" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>Manteniendo PHP al día</title><content type="html">Uno de los dolores de cabeza con que tuve que lidiar hasta hace algún tiempo fue que las diversas distribuciones de linux incluían versiones antiguas de PHP por lo que tenía que compilar el código fuente para tener versiones más recientes.&lt;br /&gt;&lt;br /&gt;Tengo dos motivos principales para querer instalar versiones de PHP actualizadas:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;En cada release se corrigen &lt;a href="http://www.php.net/ChangeLog-5.php"&gt;innumerables bugs&lt;/a&gt; detectados en las versiones anteriores.&lt;/li&gt;&lt;li&gt;Nuevas funcionalidades que se agregan a PHP, como por ejemplo en la versión 5.2 se incorporó soporte para canonicalización &lt;sup&gt;[&lt;a href="http://www.w3.org/TR/xml-c14n"&gt;W3C&lt;/a&gt;]&lt;/sup&gt; (&lt;a href="http://en.wikipedia.org/wiki/Canonicalization"&gt;C14N&lt;/a&gt;) las cuales son imprescindibles para la firma electrónica en XML &lt;sup&gt;[&lt;a href="http://www.w3.org/TR/xml-c14n"&gt;W3C&lt;/a&gt;]&lt;/sup&gt; (&lt;a href="http://en.wikipedia.org/wiki/XML_Signature"&gt;&lt;i&gt;XMLDsig&lt;/i&gt;&lt;/a&gt;).&lt;/li&gt;&lt;/ol&gt;Comúnmente para instalar una versión reciente de PHP compilaba el código fuente, lo cual toma bastante tiempo y puede ser bastante engorroso en caso de que el sistema operativo no tenga todas las dependencias para compilar, la desinstalación de código compilado siempre ha sido difícil y para qué hablar del proceso de actualización de versión en ambientes productivos, siempre era una incógnita cuánto iba a demorar la actualización (y mientras el sistema está detenido sin dar servicio a los clientes). Siempre he preferido administrar los paquetes mediante RPM ya que facilita mucho la instalación y desinstalación de software, la actualización de versión es bastante rápida, por lo general uno no se topa con sorpresas durante la instalación y el procedimiento de vuelta atrás es bastante sencillo ya que consiste sólo en volver a instalar los RPM de la versión previa.&lt;br /&gt;&lt;br /&gt;Un día, todas estas preocupaciones quedaron en el olvido gracias a Remi Collet quien matiene un &lt;a href="http://blog.famillecollet.com/"&gt;repositorio&lt;/a&gt; de paquetes RedHat que incluye las versiones más recientes de PHP. Usualmente el repositorio se actualiza en no más de una semana después de que PHP libera una actualización. Además, incluye módulos PEAR y PECL para PHP y actualizaciones para MySQL, Firefox y Thundebird.&lt;br /&gt;&lt;br /&gt;Una vez configurado correctamente el repositorio, basta con ejecutar el siguiente comando para actualizar PHP a la versión más reciente:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;yum update php&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Al ser orientado a RedHat, el repositorio cuenta con releases para las versiones más comunes de Fedora Core, CentOS y RedHat. Además publica los SRPM&lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;, es decir, los RPM que contienen los códigos fuentes lo cual permite que uno recompile y genere sus propios RPM. Provee de &lt;a href="http://blog.famillecollet.com/post/2005/10/02/8-telechargement-installation-et-yum"&gt;instrucciones de instalación&lt;/a&gt; para &lt;code&gt;yum&lt;/code&gt;, &lt;code&gt;apt&lt;/code&gt; y &lt;code&gt;smart&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Como nota final, tengo que aclarar que no es recomendable estar actualizando el software con demasiada frecuencia ya que cada proceso de actualización requiere de pruebas exhaustivas y eventualmente podría comenzar a fallar código que antes funcionaba correctamente.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-56869904576706395?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/56869904576706395/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=56869904576706395" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/56869904576706395?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/56869904576706395?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/manteniendo-php-al-dia.html" title="Manteniendo PHP al día" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DkABSX47fyp7ImA9WxZVFks.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-4276406290413131452</id><published>2008-03-13T22:47:00.008-03:00</published><updated>2008-03-27T21:39:18.007-03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-27T21:39:18.007-03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="xml" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>Motivación</title><content type="html">Cada vez que participo en un proyecto de software aprendo una enormidad de nuevos temas que de alguna manera las quiero ir dejando registradas. Usualmente son temas que han requerido de bastante tiempo de investigación o son producto de la experiencia.&lt;br /&gt;&lt;br /&gt;Hace más de un año que tomé la decisión  de crear un blog para ir registrando información útil e interesante relacionada con mi trabajo en desarrollo web, sin embargo había pospuesto su inicio debido a que no había decidido la orientación que le iba a dar.&lt;br /&gt;&lt;br /&gt;Finalmente tomé la decisión de que si no comenzaba a publicar ahora mismo, no lo iba a hacer nunca por lo que aproveché la contingencia del retraso del cambio de hora en Chile para bautizar mi blog y comenzar a publicar algunos artículos.&lt;br /&gt;&lt;br /&gt;Existen cientos de sitios que hablan sobre PHP, Apache, javascript, XML, firma electrónica y otros temas. Sin embargo, casi todos hablan de lo mismo y son pocos los que profundizan en temas específicos. En base a esto, la orientación de este blog no será hablar de temas básicos -y ser uno más de esos cientos de sitios- sino que trataré temas más específicos pero que han sido extremadamente útiles en mi trabajo.&lt;br /&gt;&lt;br /&gt;Hay varios temas que me interesan y no tengo claro por cuál voy a comenzar, algunos de los artículos que estoy preparando son:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://gmt-4.blogspot.com/2008/03/usando-el-lenguaje-html-de-manera.html"&gt;Usando HTML y XHTML correctamente&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Firma electrónica usando OpenSSL y PHP&lt;/li&gt;&lt;li&gt;Encriptación en PHP&lt;/li&gt;&lt;li&gt;Recomendaciones para almacenamiento de password&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Trabajar con XML en PHP&lt;/li&gt;&lt;li&gt;Buenas prácticas en PHP&lt;/li&gt;&lt;li&gt;Compilación y generación de RPMs de PHP&lt;/li&gt;&lt;li&gt;Compilación de módulos PECL para PHP&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Cración de módulos PECL para PHP (programar en lenguaje C)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Memcache&lt;/li&gt;&lt;li&gt;Optimización de apache: Configuración de cache&lt;/li&gt;&lt;li&gt;Optimización de apache: Compresión de datos&lt;/li&gt;&lt;li&gt;Redescubriendo javascript&lt;/li&gt;&lt;li&gt;Biblioteca YUI para javascript&lt;/li&gt;&lt;li&gt;Manipulación de DOM mediante javascript&lt;/li&gt;&lt;li&gt;Convivieno con UTF-8&lt;/li&gt;&lt;li&gt;Desarrollando con Firebug&lt;/li&gt;&lt;li&gt;OpenID&lt;/li&gt;&lt;li&gt;WebServices y SOAP&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-4276406290413131452?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/4276406290413131452/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=4276406290413131452" title="1 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/4276406290413131452?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/4276406290413131452?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/motivacin.html" title="Motivación" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;A0EDSHk9fSp7ImA9WxZWFko.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-5812990645648052629</id><published>2008-03-08T16:50:00.013-03:00</published><updated>2008-03-16T12:01:19.765-03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-16T12:01:19.765-03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ntp" /><category scheme="http://www.blogger.com/atom/ns#" term="linux" /><category scheme="http://www.blogger.com/atom/ns#" term="pecl" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>PHP y las zonas horarias</title><content type="html">&lt;span style="font-weight: bold;"&gt;Abstract&lt;/span&gt;&lt;br /&gt;Debido a la modificación del cambio de horario en Chile para este año 2008 que traslada el término de horario de verano desde la noche del 8 de marzo hacia la noche del 29 de marzo, nos hemos topado con la necesidad de corregir nuestros sistemas para que se adapten a este cambio.&lt;br /&gt;&lt;br /&gt;En LINUX, la solución más apropiada es la actualización manual de la definición de la zona horaria &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt; o (idealmente) la actualización del paquete &lt;span style="font-family:courier new;"&gt;tzdata&lt;/span&gt; (una vez que hayan publicado la actualización correspondiente).&lt;br /&gt;&lt;br /&gt;Sin embargo, algunos software como por ejemplo PHP y JAVA mantienen su propia información de zona horaria (zoneinfo) por lo que ingnorarán las reglas que tenga definido el sistema operativo. Esto significa que -aunque yamos parchado nuestro sistema operativo- mantendrán el problema del cambio de hora.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Solución para PHP&lt;/span&gt;&lt;br /&gt;PHP desde la versión 5.1 mantiene su propia base de datos de zona horaria por lo que ignorará las reglas de zona horaria definidas en el Sistema Operativo. La solución a este problema es la actualización del módulo &lt;a href="http://pecl.php.net/package/timezonedb"&gt;timezonedb&lt;/a&gt; incluido con PHP. &lt;del&gt;Lamentablemente al 8 de marzo de 2008 la versión más reciente de &lt;span style="font-family:courier new;"&gt;timezonedb &lt;/span&gt;no incluye la actualización a la zona horaria &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt; por lo tanto tendremos que parchar el módulo antes de instalarlo&lt;/del&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Gestión de módulos en PHP mediante PECL&lt;/span&gt;&lt;br /&gt;PHP está compuesto por un core llamado Zend Engine y un subconjunto de módulos que le agregan funcionalidades como manejo de strings, manejo de fechas, soporte a sesiones, etc. El conjunto de módulos adicionales de PHP se llama &lt;a href="http://pecl.php.net/"&gt;PECL&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Es posible agregar módulos PECL adicionales que no vienen incluidos en la distribución original mediante el comando pecl el cual viene en el paquete &lt;span style="font-family:courier new;"&gt;php-devel&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;En las distribuciones RedHat y sus derivados (Fedora Core, CentOS, etc.) la instalación de paquetes se puede realizar con el comando yum, el cual utilizaré como ejemplo en este artículo. En otras distribuciones existen otros gestores de paquetes y los nombres de los paquetes podrían variar.&lt;br /&gt;&lt;br /&gt;En caso que no tengamos el paquete &lt;span style="font-family:courier new;"&gt;php-devel&lt;/span&gt;, lo instalamos con el comando:&lt;br /&gt;&lt;pre&gt;yum install php-devel&lt;br /&gt;&lt;/pre&gt;Ahora que contamos con el comando &lt;span style="font-family:courier new;"&gt;phpize&lt;/span&gt;, el procedimiento para instalar cualquier módulo PECL es el siguiente:&lt;br /&gt;&lt;pre&gt;pecl instal nombre_paquete&lt;br /&gt;&lt;/pre&gt;Por lo tanto, para instalar el módulo &lt;span style="font-family:courier new;"&gt;timezonedb &lt;/span&gt;se ejecutaría el siguiente comando&lt;br /&gt;&lt;pre&gt;pecl install timezonedb&lt;br /&gt;&lt;/pre&gt;&lt;del&gt;Sin embargo, tal como indiqué al principio, esto no resolverá el problema porque en este momento (8 de marzo de 2008), este módulo aún no incorpora la actualización para &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt;&lt;/del&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Actualizado:&lt;/span&gt; &lt;a href="http://pecl.php.net/bugs/bug.php?id=13369"&gt;Hace algunos días publiqué un bug en el sitio de PHP&lt;/a&gt; y se acaba de liberar la versión de timezonedb que actualiza la zona horaria para &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt;, por lo que desde ahora ya no es necesario realizar los pasos que indico en "Instalar módulo timezonedb parchado" sino que simplemente se debe ejecutar el comando indicado a continuación y luego seguir con los pasos indicados más abajo en "Habilitando extensión":&lt;br /&gt;&lt;pre&gt;pecl install timezonedb&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Instalar módulo timezonedb parchado&lt;br /&gt;&lt;/span&gt;El procedimiento a seguir es descargar los fuentes de timezonedb (que están en lenguaje C), modificar la definición de la zona America/Santiago y luego agregar el módulo a PHP. Este procedimiento requiere de conocimientos básicos del lenguaje C.&lt;br /&gt;&lt;br /&gt;En primer lugar descargamos los fuentes mediante:&lt;br /&gt;&lt;pre&gt;pecl download timezonedb&lt;br /&gt;&lt;/pre&gt;Esto descargará un archivo con la versión más reciente del módulo que en mi caso fue &lt;span style="font-family:courier new;"&gt;timezonedb-2007.11.tgz&lt;/span&gt;. Lo descomprimimos y entramos al directorio recien creado:&lt;br /&gt;&lt;pre&gt;tar xfz timezonedb-2007.11.tgz&lt;br /&gt;cd timezonedb-2007.11&lt;br /&gt;&lt;/pre&gt;El archivo &lt;span style="font-family:courier new;"&gt;timezonedb.h&lt;/span&gt; es el que contiene la información de las zonas horarias. He publicado una versión parchada que contiene la información actualizada para &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt; en base a la versión &lt;span style="font-family:courier new;"&gt;timezonedb-2007.11&lt;/span&gt;. A contnuación descargamos y descompriminos el archivo.&lt;br /&gt;&lt;pre&gt;wget http://www.joserodriguez.cl/blog/2008/03/timezonedb.h.gz&lt;br /&gt;gunzip timezonedb.h.gz&lt;br /&gt;&lt;/pre&gt;Luego, estamos en condiciones de compilar el módulo ejecutando los siguientes comandos:&lt;br /&gt;&lt;pre&gt;phpize&lt;br /&gt;./configure&lt;br /&gt;make&lt;br /&gt;&lt;/pre&gt;Luego, como usuario &lt;span style="font-family:courier new;"&gt;root&lt;/span&gt;, instalamos el módulo.&lt;br /&gt;&lt;pre&gt;make install&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;br /&gt;Habilitando extensión&lt;/span&gt;&lt;br /&gt;Habilitamos la extensión en el archivo &lt;span style="font-family:courier new;"&gt;php.ini&lt;/span&gt; el cual usualmente se encuentra en &lt;span style="font-family:courier new;"&gt;/etc/php.ini&lt;/span&gt;. &lt;em&gt;En algunas distribuciones tales como UBUNTU existen dos archivos &lt;span style="font-family:courier new;"&gt;php.ini&lt;/span&gt;, ambos deben ser actualizados.&lt;/em&gt;&lt;br /&gt;&lt;pre&gt;echo "extension=timezonedb.so" &gt;&gt; /etc/php.ini&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Validando instalación&lt;/span&gt;&lt;br /&gt;Para verificar que la actualización funcione correctamente podemos ejecutar el siguiente script:&lt;br /&gt;&lt;pre&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;f(1204988400);&lt;br /&gt;f(1205074800);&lt;br /&gt;f(1206802800);&lt;br /&gt;f(1206889200);&lt;br /&gt;&lt;br /&gt;function f($time) {&lt;br /&gt;echo date("Y-m-d G:i:s O e I\n", $time);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;?&amp;gt;&lt;br /&gt;&lt;/pre&gt;Cuya salida debería ser la siguiente:&lt;br /&gt;&lt;pre&gt;2008-03-08 12:00:00 -0300 America/Santiago 1&lt;br /&gt;2008-03-09 12:00:00 -0300 America/Santiago 1&lt;br /&gt;2008-03-29 12:00:00 -0300 America/Santiago 1&lt;br /&gt;2008-03-30 11:00:00 -0400 America/Santiago 0&lt;/pre&gt;En caso que ocurra algún error fatal al ejecutar PHP, como por ejemplo &lt;span style="font-style: italic;"&gt;segmentation fault&lt;/span&gt;, se puede deshabilitar la extensión eliminando la línea recien agregada a &lt;span style="font-family:courier new;"&gt;php.ini&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-weight: bold;"&gt;Actualizado el 16 de marzo de 2008.&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-5812990645648052629?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/5812990645648052629/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=5812990645648052629" title="3 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/5812990645648052629?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/5812990645648052629?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/php-y-las-zonas-horarias.html" title="PHP y las zonas horarias" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>3</thr:total></entry><entry gd:etag="W/&quot;CkUGRXo-fSp7ImA9WxZWEkk.&quot;"><id>tag:blogger.com,1999:blog-6180140326159714233.post-7427175971138064532</id><published>2008-03-07T16:16:00.010-03:00</published><updated>2008-03-11T09:57:04.455-03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-11T09:57:04.455-03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ntp" /><category scheme="http://www.blogger.com/atom/ns#" term="linux" /><title>¿Cómo corrijo la zona horaria?</title><content type="html">En vista del retraso de este año en el cambio de la zona horaria para Chile intentaré describir el manejo de la hora en los sistemas computacionales y dar algunas luces de cómo es el procedimiento adecuado para actualizar el cambio de hora. He omitido algunos conceptos y generalizado en algunos aspectos con el fin de no hacer tan compleja la explicación.&lt;br /&gt;&lt;br /&gt;Si no han vivido en una caverna durante toda su vida, debieran saber que el planeta tierra se divide en 24 husos horarios donde cada uno posee una diferencia de una hora con respecto al contiguo. Por efectos prácticos cada país hace coincidir el huso horario con sus limites geográficos y utiliza el término de zona horaria.&lt;br /&gt;&lt;br /&gt;Todos los husos horarios o zonas horarias se definen en función del &lt;a href="http://es.wikipedia.org/wiki/Tiempo_Universal_Coordinado"&gt;Tiempo universal coordinado (UTC)&lt;/a&gt; el cual está fijado en el meridiano de Greenwich (&lt;span style="font-family:courier new;"&gt;GMT-0&lt;/span&gt;), de esta manera nuestro país normalmente está en &lt;span style="font-family:courier new;"&gt;GMT-4&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Ahora bien, cada país realiza algunos ajustes en su horario, por ejemplo Chile en la época estival utiliza el horario de verano el cual nos mueve desde &lt;span style="font-family:courier new;"&gt;GMT-4&lt;/span&gt; a &lt;span style="font-family:courier new;"&gt;GMT-3&lt;/span&gt;. Debido a que es complejo saber todas las reglas de cada país, se definen nuevas zonas horarias que para el caso de Chile son &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt; en el territorio continental y &lt;span style="font-family:courier new;"&gt;Pacific/Easter&lt;/span&gt; para la isla de Pascua.&lt;br /&gt;&lt;br /&gt;Como vivimos en un mundo globalizado, podríamos asumir que la "hora oficial del mundo" es la correspondiente a &lt;span style="font-family:courier new;"&gt;GMT-0&lt;/span&gt; (simplemente &lt;span style="font-family:courier new;"&gt;GMT &lt;/span&gt;o &lt;span style="font-family:courier new;"&gt;UTC&lt;/span&gt;), de hecho nuestros computadores almacenan todas las fecha en GMT.&lt;br /&gt;&lt;br /&gt;Por ejemplo, (aquí en Chile: &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt;) si tengo que agendar una reunión el 10 de enero (que corresponde al horario de verano), al registrar la fecha "10 de enero de 2008 15:00:00" mi software de agenda -antes de guardarla en su base de datos- la convertirá a hora "universal" (o &lt;span style="font-family:courier new;"&gt;GMT&lt;/span&gt;) por lo que le sumará 3 horas quedando guardada como "10 de enero de 2008 18:00:00". Luego, cada vez que mi agenda me muestre la fecha, leerá la fecha en GMT, la modificará según mi zona horaria y luego de eso me la mostrará.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Compliquemos las cosas...&lt;/span&gt;&lt;br /&gt;Lo estándar es que el término del horario de verano (regresar de &lt;span style="font-family:courier new;"&gt;GMT-4&lt;/span&gt; a &lt;span style="font-family:courier new;"&gt;GMT-3&lt;/span&gt;) fuese el segundo sábado de marzo (al final del día), pero para este año 2008 el cambio se realizará el sábado 29 de marzo.&lt;br /&gt;&lt;br /&gt;Para los usuarios domésticos, tener algunas fechas equivocadas puede ser aceptable, pero en sistemas corporativos eso puede ser muy peligroso.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Alternativa N°0: Sincronizar con un servidor ntp (servidor de hora)&lt;/span&gt;&lt;br /&gt;Un servidor de hora permite que nuestro equipo tenga la hora actualizada, pero estos servicios conversan en UTC y luego el equipo local ajusta la hora local en base a la zona horaria, por lo tanto no sirve para el problema del cambio de hora. De todos modos es una buena práctica tener &lt;a href="http://www.horaoficial.cl/sincron.htm"&gt;nuestro equipo sincronizado con ntp.shoa.cl&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Alternativa N°1: Adelantar la hora&lt;/span&gt;&lt;br /&gt;La solución más sencilla que a uno se le ocurre es que -luego de que suceda el cambio de hora- adelantemos el reloj del sistema y luego el 29 de marzo volver a retrasarlo. Este cambio haría que todas las fechas almacenadas entre el 9 y 29 de marzo queden guardadas erróneamente ya que en vez de considerarlas como &lt;span style="font-family:courier new;"&gt;GMT-4&lt;/span&gt; (horario de verano) las tomará como &lt;span style="font-family:courier new;"&gt;GMT-3&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Repitamos el ejemplo inicial: Si agendo una reunión para el día 12 de marzo a las 10:00 (hora local &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt;), el sistema -al convertirla a &lt;span style="font-family:courier new;"&gt;UTC&lt;/span&gt;- le aplicará la corrección &lt;span style="font-family:courier new;"&gt;GMT-3&lt;/span&gt; (le sumará 3 horas) convirtiéndola erróneamente en "12 de marzo de 2008 13:00:00" cuando lo correcto es sumarle 4 horas y almacenarla como "12 de marzo de 2008 14:00:00".&lt;br /&gt;&lt;br /&gt;Si envío la cita a los demás asistentes, ellos podrían recibir la cita con una hora de anticipación pudiendo pasar más de un mal rato. Mientras nuestro software (o sistema operativo) no actualice las reglas de la zona horaria de &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt; este error pasará desapercibido en mi computador porque tanto al guardar la fecha como al leerla utilizará la misma zona horaria &lt;span style="font-family:courier new;"&gt;GMT-3&lt;/span&gt;. Cuando a futuro haga una actualización de mi software (o sistema operativo) es posible que se haya incluido la actualización de la zona horaria y ahí quedaría en evidencia el error haciendo que todas las fechas ingresadas durante esas 3 semanas estén adelantadas en una hora.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Alternativa &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;N°2: Cambiar la zona horaria a GMT-4&lt;/span&gt;&lt;br /&gt;Otra alternativa es cambiar la zona horaria manualmente a &lt;span style="font-family:courier new;"&gt;GMT-4&lt;/span&gt; (cuya zona horaria es &lt;span style="font-family:courier new;"&gt;ETC/GMT+4&lt;/span&gt;, el signo "+" es correcto) para forzar el uso del horario de verano y luego el 29 de marzo volver a &lt;span style="font-family:courier new;"&gt;America/Santiago.&lt;/span&gt; Esto hará que las fechas se almacenen correctamente ya que no habremos cambiado la hora de nuestro computador, pero tiene el inconveniente que asumirá que todas las fechas están en &lt;span style="font-family:courier new;"&gt;GMT-4&lt;/span&gt; por lo que al consultar una fecha del horario de invierno (por ejemplo "18 de septiembre de 2007 10:00:00"), en vez de restarle 3 horas a la fecha GMT que está almacenada en el sistema, le restará 4.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Alternativa &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;N°3: Actualizar la "definición" de la zona horaria&lt;/span&gt;&lt;br /&gt;La solución correcta es actualizar la definición de la zona horaria, es decir, que el sistema sepa que en 2008 el término del horario de verano para &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt; es el 29 de marzo y no el 9 de marzo.&lt;br /&gt;&lt;br /&gt;El problema de esta solución es que no es fácil realizar el cambio, ya que usualmente consiste en realizar cambios a archivos del sistema operativo o al registro de Windows.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Solución para Windows&lt;/span&gt;&lt;br /&gt;Para servidores Windows, Microsoft &lt;a href="http://www.microsoft.com/chile/cambiodehora/"&gt;publicó un comunicado&lt;/a&gt; donde recomienda la &lt;a href="http://www.microsoft.com/chile/cambiodehora/recomendaciones.aspx"&gt;cambiar la zona horaria&lt;/a&gt; (nuestra alternativa N°2) para usuarios domésticos y la &lt;a href="http://www.microsoft.com/chile/cambiodehora/actualizacion.aspx"&gt;actualizar la definición de la zona horaria America/Santiago&lt;/a&gt; (nuestra alternativa N°3) para usuarios corporativos.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Solución para LINUX&lt;/span&gt;&lt;br /&gt;En servidores LINUX (y en general *NIX) la información de las zonas horarias es mantenida por el paquete tzdata, sin embargo a la fecha de escritura de este artículo, dicho paquete aún no contiene la actualización a nuestra zona &lt;span style="font-family:courier new;"&gt;America/Santiago&lt;/span&gt;, por lo tanto la solución es regenerar la definición del archivo de zona. A continuación describo el procedimiento para distribuciones RedHat, Fedora y CentOS. En otras distribuciones es posible que los comandos y ubicación de los archivos sea distinta.&lt;br /&gt;&lt;br /&gt;Al momento de escribir este artículo UBUNTU ya cuenta con una actualización del paquete &lt;span style="font-family:courier new;"&gt;tzdata&lt;/span&gt;: &lt;a href="https://bugs.launchpad.net/ubuntu/+source/tzdata/+bug/198129"&gt;https://bugs.launchpad.net/ubuntu/+source/tzdata/+bug/198129&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Si bien hay muchos servicios que utilizan el sistema operativo para el manejo de la zona horaria, hay software que mantiene su propio módulo de zonas horarias por lo que ignorará estos cambios, por ejemplo (algunas versiones de): PHP 5.1+, ORACLE y JAVA.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Actualizado:&lt;/span&gt; Ya se publicó la &lt;a href="ftp://elsie.nci.nih.gov/pub/"&gt;actulización oficial de tzdata 2008a&lt;/a&gt; por lo que ahora -si lo prefieres- en vez de crear el archivo &lt;span style="font-family:courier new;"&gt;tzdata.txt&lt;/span&gt; que indico más abajo, puedes descargar el archivo oficial y luego importar el archivo &lt;span style="font-family:courier new;"&gt;southamerica&lt;/span&gt; como indico a continuación:&lt;br /&gt;&lt;pre&gt;cd /tmp&lt;br /&gt;wget ftp://elsie.nci.nih.gov/pub/tzdata2008a.tar.gz&lt;br /&gt;tar xfz tzdata2008a.tar.gz&lt;br /&gt;zic southamerica&lt;br /&gt;&lt;/pre&gt;En caso que prefieras no descargar el archivo anterior, se debe crear el archivo &lt;span style="font-family:courier new;"&gt;newzone.txt&lt;/span&gt; con el siguiente contenido:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Rule Chile  1927 1932 -  Sep   1    0:00   1:00 S&lt;br /&gt;Rule Chile  1928 1932 -  Apr   1    0:00   0  -&lt;br /&gt;Rule Chile  1942 only -  Jun   1    4:00u  0  -&lt;br /&gt;Rule Chile  1942 only -  Aug   1    5:00u  1:00 S&lt;br /&gt;Rule Chile  1946 only -  Jul  15    4:00u  1:00 S&lt;br /&gt;Rule Chile  1946 only -  Sep   1    3:00u  0:00 -&lt;br /&gt;Rule Chile  1947 only -  Apr   1    4:00u  0  -&lt;br /&gt;Rule Chile  1968 only -  Nov   3    4:00u  1:00 S&lt;br /&gt;Rule Chile  1969 only -  Mar  30    3:00u  0  -&lt;br /&gt;Rule Chile  1969 only -  Nov  23    4:00u  1:00 S&lt;br /&gt;Rule Chile  1970 only -  Mar  29    3:00u  0  -&lt;br /&gt;Rule Chile  1971 only -  Mar  14    3:00u  0  -&lt;br /&gt;Rule Chile  1970 1972 -  Oct  Sun&gt;=9  4:00u  1:00 S&lt;br /&gt;Rule Chile  1972 1986 -  Mar  Sun&gt;=9  3:00u  0  -&lt;br /&gt;Rule Chile  1973 only -  Sep  30    4:00u  1:00 S&lt;br /&gt;Rule Chile  1974 1987 -  Oct  Sun&gt;=9  4:00u  1:00 S&lt;br /&gt;Rule Chile  1987 only -  Apr  12    3:00u  0  -&lt;br /&gt;Rule Chile  1988 1989 -  Mar  Sun&gt;=9  3:00u  0  -&lt;br /&gt;Rule Chile  1988 only -  Oct  Sun&gt;=1  4:00u  1:00 S&lt;br /&gt;Rule Chile  1989 only -  Oct  Sun&gt;=9  4:00u  1:00 S&lt;br /&gt;Rule Chile  1990 only -  Mar  18    3:00u  0  -&lt;br /&gt;Rule Chile  1990 only -  Sep  16    4:00u  1:00 S&lt;br /&gt;Rule Chile  1991 1996 -  Mar  Sun&gt;=9  3:00u  0  -&lt;br /&gt;Rule Chile  1991 1997 -  Oct  Sun&gt;=9  4:00u  1:00 S&lt;br /&gt;Rule Chile  1997 only -  Mar  30    3:00u  0  -&lt;br /&gt;Rule Chile  1998 only -  Mar  Sun&gt;=9  3:00u  0  -&lt;br /&gt;Rule Chile  1998 only -  Sep  27    4:00u  1:00 S&lt;br /&gt;Rule Chile  1999 only -  Apr   4    3:00u  0  -&lt;br /&gt;Rule Chile  2000 2007 -  Mar  Sun&gt;=9  3:00u  0  -&lt;br /&gt;Rule Chile  2008 only -  Mar  30    3:00u  0  -&lt;br /&gt;Rule Chile  1999 max  -  Oct  Sun&gt;=9  4:00u  1:00 S&lt;br /&gt;Rule Chile  2009 max  -  Mar  Sun&gt;=9  3:00u  0  -&lt;br /&gt;Zone America/Santiago  -4:42:46 -    LMT    1890&lt;br /&gt;-4:42:46 -  SMT  1910&lt;br /&gt;-5:00  -    CLT    1916 Jul  1&lt;br /&gt;-4:42:46 -    SMT    1918 Sep  1&lt;br /&gt;-4:00  -    CLT    1919 Jul  1&lt;br /&gt;-4:42:46 -    SMT    1927 Sep  1&lt;br /&gt;-5:00  Chile  CL%sT  1947 May 22&lt;br /&gt;-4:00 Chile CL%sT&lt;br /&gt;Zone Pacific/Easter    -7:17:44 -    LMT    1890&lt;br /&gt;-7:17:28 -  EMT  1932 Sep&lt;br /&gt;-7:00 Chile   EAS%sT  1982 Mar 13 21:00&lt;br /&gt;-6:00 Chile   EAS%sT&lt;br /&gt;Link America/Santiago        Chile/Continental&lt;br /&gt;Link Pacific/Easter          Chile/EasterIsland&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;se debe ejecutar como usuario "root" el comando:&lt;br /&gt;&lt;pre&gt;zic  newzone.txt&lt;br /&gt;&lt;/pre&gt;luego, para verificar que haya quedado bien configurado, se debe ejecutar:&lt;br /&gt;&lt;pre&gt;zdump -v America/Santiago | grep 2008&lt;br /&gt;&lt;/pre&gt;y verificar que  en dos de las reglas de cambio de horario indique 30 de marzo (&lt;span style="font-style: italic;"&gt;Sun Mar 30&lt;/span&gt;). En mi LINUX la salida de este comando es:&lt;br /&gt;&lt;pre&gt;America/Santiago  Sun Mar 30 02:59:59 2008 UTC = Sat Mar 29 23:59:59 2008 CLST isdst=1 gmtoff=-10800&lt;br /&gt;America/Santiago  Sun Mar 30 03:00:00 2008 UTC = Sat Mar 29 23:00:00 2008 CLT isdst=0 gmtoff=-14400&lt;br /&gt;America/Santiago  Sun Oct 12 03:59:59 2008 UTC = Sat Oct 11 23:59:59 2008 CLT isdst=0 gmtoff=-14400&lt;br /&gt;America/Santiago  Sun Oct 12 04:00:00 2008 UTC = Sun Oct 12 01:00:00 2008 CLST isdst=1 gmtoff=-10800&lt;br /&gt;&lt;/pre&gt;Luego hay que ejecutar la herramienta para configurar la zona horaria &lt;span style="font-family:courier new;"&gt;system-config-date&lt;/span&gt; (puede ser por linea de comandos o en el entorno gráfico), escoger &lt;span style="font-family:courier new;"&gt;America/Santiago &lt;/span&gt;y luego "Aceptar".&lt;br /&gt;&lt;br /&gt;Algunos servicios requieren ser reiniciados para reconocer los cambios, en caso de ser posible es mejor reiniciar el equipo.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;PHP&lt;/span&gt;&lt;br /&gt;Tal como comentaba más arriba, PHP desde la versión 5.1 incluye su propio módulo de zona horaria por lo que ignorará las reglas de la zona horaria del sistema operativo. En este caso se puede realizar otro procedimiento que &lt;a href="http://gmt-4.blogspot.com/2008/03/php-y-las-zonas-horarias.html"&gt;comento aquí&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-weight: bold;"&gt;(Actualizado el domingo 9 de marzo a las 11:30)&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6180140326159714233-7427175971138064532?l=gmt-4.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://gmt-4.blogspot.com/feeds/7427175971138064532/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6180140326159714233&amp;postID=7427175971138064532" title="2 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/7427175971138064532?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6180140326159714233/posts/default/7427175971138064532?v=2" /><link rel="alternate" type="text/html" href="http://gmt-4.blogspot.com/2008/03/qu-es-la-zona-horaria.html" title="¿Cómo corrijo la zona horaria?" /><author><name>José Rodríguez</name><uri>http://www.blogger.com/profile/11042521695514251708</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://bp3.blogger.com/_6K2axIH8bMw/R9SNofX8LkI/AAAAAAAAADE/jq2cqFvi1Oc/S220/jos3.jpg" /></author><thr:total>2</thr:total></entry></feed>

