Función palindrome? en Ruby

Otro de los ejercicios del Curso SAAS de Berkeley era hacer un método que comprobara un palíndromo (ej 1) e incluirlo dentro de la clase String (en el ej… 5 o el 6, no me acuerdo). En todo caso, puede parecer algo extremadamente simple, pero tiene un mínimo de chicha.

Digo que tiene más chicha porque más allá de hacer un simple reverse, que nos valdría para una palabra sola, la idea es hacerlo ignorando espacios y números, para poder aplicarlo a una frase completa, y que además no sea case sensitive (vamos, que no distinga mayúsculas de minúsculas). Tampoco tiene mucha complicación, es todo cuestioń de una expresión regular:

def palindrome?(string)
    letters = string.downcase.scan(/\w/)
    letters == letters.reverse
  end

Cuatro líneas… bueno, realmente dos porque las otras dos definen el método. ¿Y meterlo dentro de String? Dos líneas más, una modificación y vía. Es la potencia de Ruby, que puedes abrir cualquier clase cuando lo necesites:

class String
  def palindrome?
    letters = self.downcase.scan(/\w/)
    letters == letters.reverse
  end
end

El método devolverá true en caso de éxito y nulo en caso de que no haya coincidencia. Sencillo y liquidado.

Producto cartesiano de dos arrays en Ruby

Bueno, sigo con el curso de SAAS de Berkeley, y me ha dado por poner otro ejercicio (bueno, realmente es que llevo días sin escribir, estoy sin ideas y como acabo de hacerlo…). En este caso el último del «homework 1», que ha sido todo un alivio porque me esperaba algo jodidérrimo pero… no, para nada, es una chorrada tan gorda como hacer una clase con un método each que muestre el producto cartesiano de dos arrays:

class CartesianProduct
  include Enumerable
  def initialize(col1, col2)
    @col1 = col1
    @col2 = col2
  end

  def each
    return to_enum unless block_given?
    @col1.each do |x| 
      @col2.each { |y| yield [x, y] }
    end
  end
end

Como ya ves el constructor de la clase recibe dos arrays (col1 y col2) para inicializar el objeto, y por otra parte el método each recorre el primero cruzando cada elemento con todos los elementos del segundo (de ahí que haya un each dentro de otro each).

Fácil, rápido y barato… por decir algo.

Piedra-papel-tijera en Ruby

Un pequeño ejercicio de Ruby que he tenido que hacer en el curso de SaaS de Berkeley (en EdX) es un método que «juegue» al tres en raya. Recibiendo un array bidimimensional (la variable game en el ejemplo) debe devolver el resultado de qué jugador sería el ganador. Si hay más de dos jugadores devuelve una excepción, y si alguno mete una letra que no sea R (rock), P (paper), S (scissors) lanza otra excepción (ambas vienen ya dadas en el código de ejemplo). Finalmente, si ambos sacan lo mismo da como ganador al jugador 1 (¿por qué? pues porque es lo que pide el enunciado a reclamarle al maese armero)

En fin, el código sería tal que así:

class WrongNumberOfPlayersError < StandardError ; end
class NoSuchStrategyError < StandardError ; end

def rps_game_winner(game)
  raise WrongNumberOfPlayersError unless game.length == 2
  game[0][1] = game[0][1].downcase
  game[1][1] = game[1][1].downcase
  raise NoSuchStrategyError if game[0][1] != 'r' and game[0][1] != 'p' and game[0][1] != 's'
  raise NoSuchStrategyError if game[1][1] != 'r' and game[1][1] != 'p' and game[1][1] != 's'
  ganador = 0
  if game[0][1] == 'r'
   if game[1][1] == 'r'
   elsif game [1][1] == 'p'
   ganador = 1
   else
   end
  elsif game[0][1] == 'p'
   if game[1][1] == 'r'
   elsif game [1][1] == 'p'
   else
   ganador = 1
   end
  else
   if game[1][1] == 'r'
   ganador = 1
   elsif game [1][1] == 'p'
   else
   end
  end
  return game[ganador]
end

La chicha viene con la segunda parte del ejercicio, el modo tournament. Este recibe un array de arrays bidimensionales con todos los enfrentamientos (hay que suponer que están bien formados y bien anidados, y debe permitir cualquier cantidad de jugadores, siempre que crezcan en progresión de potencias de 2: 4,8,16,32… piensa el cualquier Play off de cualquier deporte) y los recorre como una eliminatoria. Ponen como ejemplo que reciba algo tal que así:

[
    [
        [ ["Armando", "P"], ["Dave", "S"] ],
        [ ["Richard", "R"],  ["Michael", "S"] ],
    ],
    [
        [ ["Allen", "S"], ["Omer", "P"] ],
        [ ["David E.", "R"], ["Richard X.", "P"] ]
    ]
]

Ok, ¿qué hacer frente al anidamiento? Pues recursividad, tema que ya explicamos. En este caso, el método comprobará que cada array a su vez contenga un array y si es así vuelve a llamarse a si mismo hasta que llegue a un enfretamiento, donde entonces llamará al método de la primera parte para devolver al ganador.

def rps_tournament_winner(tournament)
  if tournament[0][0].kind_of?(Array)
       rps_game_winner([rps_tournament_winner(tournament[0]), rps_tournament_winner(tournament[1])])
  else
   rps_game_winner(tournament)
  end
end

Y con esto y un bizcocho, ejercicio realizado. Lo he enviado y ya está corregido, y me ha dado el 100%, así que espero que os aclare las dudas. (Como dice un colega mío «no me copiar, me cago en Satán»… en fin, o sí, que yo tampoco soy ni profesor ni vuestro padre). En todo caso veis que con la recursividad un simple if/else resuelve toda la carga de trabajo en cinco líneas (bueno, realmente dos)

Parsear un XML en Ruby

Ando mirando cosillas de Ruby ya que mañana empiezo un curso de SAAS y entre otras cosas he encontrado cómo parsear un xml tirando de Ruby. En Yahoo respuestas me he encontrado dos opciones: Usar REXML o XML-Simple.

En el caso del primero comentan que es lo más típico, ya que REXML es parte de la librería estandard de Ruby. Y como ejemplo de su uso explican cómo utilizarlo para parsear los datos devueltos por el API de Yahoo Web Search.:

require 'net/http'
require 'rexml/document'

# Búsqueda web de la palabra "madonna"
url = 'http://api.search.yahoo.com/WebSearchService/V1/webSearch?appid=YahooDemo&query=madonna&results=2'

# obtener los datos xml como una cadena
xml_data = Net::HTTP.get_response(URI.parse(url)).body

# extraer la información del evento
doc = REXML::Document.new(xml_data)
titles = []
links = []
doc.elements.each('ResultSet/Result/Title') do |ele|
   titles << ele.text
end
doc.elements.each('ResultSet/Result/Url') do |ele|
   links << ele.text
end

# imprimir todos los eventos
titles.each_with_index do |title, idx|
   print "#{title} => #{links[idx]}\n"
end

En fin, la cosa no parece muy compleja. Para XMLSimple tampoco hay mucha complicación. Se trata de un port de la librería de Perl XMLSimple que os podéis descargar en este enlace y que para muchos es más intuitivo que REXML. En el ejemplo se haría la misma operación que en el anterior, para que compruebes las diferencias en la sintaxis:

require 'net/http'
require 'rubygems'
require 'xmlsimple'

#buscamos "madonna" y lo cogemos como XML
url = 'http://api.search.yahoo.com/WebSearchService/V1/webSearch?appid=YahooDemo&query=madonna&results=2'
xml_data = Net::HTTP.get_response(URI.parse(url)).body

data = XmlSimple.xml_in(xml_data)

#Pateamos los datos y los imprimimos.
data['Result'].each do |item|
   item.sort.each do |k, v|
      if ["Title", "Url"].include? k
         print "#{v[0]}" if k=="Title"
         print " => #{v[0]}\n" if k=="Url"
      end
   end
end

XMLSimple lo que hace es transformar los datos XML a una estructura de datos nativa de Ruby.

En fin, son dos ejemplillos simplones de cómo funcionan ambas librerías para parsear XML. En este enlace tenéis más info sobre REXML y en este sobre XMLSimple para profundizar.

Herramientas para revisar la calidad del código Java

Creo que hace un tiempo mi colega El señor Mozano me comentó hace tiempo que hablara de herramientas para el control de calidad del código. Lo dejé colgado, pero he decidido finalmente documentarme, experimentar y lanzar una entrada sobre el tema.

Creo que lo primero es hablar de PDM, una herramienta que controla que se hayan utilizado buenas prácticas de programación: que no haya código duplicado por copypaste, que los algoritmos no se hayan liado y vuelto complejos sin necesidad, que todos los try tengan además del catch su finally… en fin, se trata de una gran herramienta que se integra a la perfección en Eclipse o NetBeans.

Creo que hace tiempo se usaba mucho Checkstyle, pero a mi al menos en las últimas versiones de Eclipse me pareció bastante inútil, porque muchas de sus funcionalidades ya las abarca de por si el IDE. En todo caso, siempre puedes probar, es un revisor de convenciones como la indentación, las convenciones en el nombramiento de variables… etc.

Por su parte Cobertura identifica el porcentaje de código que ha sido accedido en los tests, detectando código inválido o que no ha sido testeado, generando luego un informe en XML.

Me habían recomendado una herramienta llamada FindBugs he de decir que no he trabajado mucho con él en solitario. Busca fallos dentro del código Java haciendo un análisis estático. Es una herramienta desarrollada en Java que requiere de JDK 1.5 como mínimo, aunque es capaz de revisar hasta código en Java 1.0, es libre (licencia LGPL) y fue desarrollado por la universidad de Maryland

Pero todas estas herramientas pueden resumirse en una, la más utilizada: SONAR. Realmente es una recopilación que utiliza algunos de los programas antes citados (al menos seguro FindBugs, Checkstyle y PDM, y algunos más de los que no he hablado) por lo que se trata de una herramienta muy completa. Podéis instalarlo solo o integrado en un Tomcat con MySQL (y puede que haya más opciones, pero me remito a lo aprendido en este tutorial) y existen plugins para otros muchos lenguajes (PHP, Cobol, C, C#, PL/SQL…), aunque nativamente trabaja con Java. Podéis informaros sobre él en la web del proyecto, desde el que lo podéis descargar.

Desde luego que Sonar os ahorrará romperos mucho la cabeza al integrar herramientas ya para todo: buscar duplicados, revisar la arquitectura, el sangrando, la complejidad, los posibles bugs, los comentarios, las convenciones… Pero si queréis probar por separado todas las antes citadas se integran perfectamente en Eclipse.

Programando un activity de Android para que se abra a una hora definida

Ya sea para una aplicación de despertador, para programar una actualización diaria, para hacer una app de notas de aviso… cuando desarrollamos una app para android es muy útil (y a veces indispensable) poder aprovechar para programar que una aplicación se lance a una determinada hora.

Suponiendo que ya sabes sobre desarrollo Android, la idea es simple: creas una activity que, al recibir un evento de calendario (con la hora concreta) lanza la otra activity en cuestión. Por ejemplo, «Quiero que todos los días a las 12:01 la aplicación se conecte a la base de datos y actualice la información».

El primer paso, lo dicho, es crear una clase que herede de BroadcastReceiver, sobreescribiendo su método onReceive:

public class Temporizador extends BroadcastReceiver{
@Override 
	public void onReceive(Context context, Intent intent)  { 
            Intent actividadALanzar = new Intent(context, actividadALanzar.class);
            context.startActivity(actividadALanzar);
	}
}

Debemos declarar esta clase en el Android Manifest, dentro de la etiqueta

<receiver  android:process=":remote" android:name="Temporizador"></receiver>

Lo siguientes es crear la pantalla principal y programar el evento, usando una clase PendingIntent:

Intent intentoLanzar = new Intent(getBaseContext(), Temporizador.class);
PendingIntent pIntent=PendingIntent.getBroadcast(this, 0, intentoLanzar, PendingIntent.FLAG_UPDATE_CURRENT);

Finalmente basta con crear un objeto calendario, con las horas definidas, y utilizar el AlarmManager para lanzar el intento pendiente a la hora prefijada (en este caso, estará pillando la hora actual):

Calendar cal = Calendar.getInstance(); 
cal.setTimeInMillis(System.currentTimeMillis());
cal.set (Calendar.HOUR_OF_DAY, timePicker.getCurrentHour()); 
cal.set (Calendar.MINUTE, timePicker.getCurrentMinute()); 
cal.set (Calendar.SECOND, 0);

AlarmManager aMan = (AlarmManager)getSystemService(ALARM_SERVICE); 
aMan.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pIntent); 

Aunque este método os permite hacer llamadas periódicas a la aplicación, esto no es recomendable por el excesivo consumo de energía. Los servicios se crearon para hacer eso de forma eficiente.

Usando SHA-1 en C y C++

Estaba trasteando en C estos días como ejercicio y se me ha ocurrido utilizar una función de resumen (hash) SHA-1. Como el tema es profundamente complejo, lo que he hecho ha sido buscar una ya creada, dado que al tratarde de un algoritmo creado en 1995 existen múltiples. Voy a compartir el que he encontrado por ahí que funciona bien:

/* sha1.c : Implementation of the Secure Hash Algorithm */

/* SHA: NIST's Secure Hash Algorithm */

/*	This version written November 2000 by David Ireland of 
	DI Management Services Pty Limited <code@di-mgt.com.au>

	Adapted from code in the Python Cryptography Toolkit, 
	version 1.0.0 by A.M. Kuchling 1995.
*/

/* AM Kuchling's posting:- 
   Based on SHA code originally posted to sci.crypt by Peter Gutmann
   in message <30ajo5$oe8@ccu2.auckland.ac.nz>.
   Modified to test for endianness on creation of SHA objects by AMK.
   Also, the original specification of SHA was found to have a weakness
   by NSA/NIST.  This code implements the fixed version of SHA.
*/

/* Here's the first paragraph of Peter Gutmann's posting:
   
The following is my SHA (FIPS 180) code updated to allow use of the "fixed"
SHA, thanks to Jim Gillogly and an anonymous contributor for the information on
what's changed in the new version.  The fix is a simple change which involves
adding a single rotate in the initial expansion function.  It is unknown
whether this is an optimal solution to the problem which was discovered in the
SHA or whether it's simply a bandaid which fixes the problem with a minimum of
effort (for example the reengineering of a great many Capstone chips).
*/

/* h files included here to make this just one file ... */

/* global.h */

#ifndef _GLOBAL_H_
#define _GLOBAL_H_ 1

/* POINTER defines a generic pointer type */
typedef unsigned char *POINTER;

/* UINT4 defines a four byte word */
typedef unsigned long int UINT4;

/* BYTE defines a unsigned character */
typedef unsigned char BYTE;

#ifndef TRUE
  #define FALSE	0
  #define TRUE	( !FALSE )
#endif /* TRUE */

#endif /* end _GLOBAL_H_ */

/* sha.h */

#ifndef _SHA_H_
#define _SHA_H_ 1

/* #include "global.h" */

/* The structure for storing SHS info */

typedef struct 
{
	UINT4 digest[ 5 ];            /* Message digest */
	UINT4 countLo, countHi;       /* 64-bit bit count */
	UINT4 data[ 16 ];             /* SHS data buffer */
	int Endianness;
} SHA_CTX;

/* Message digest functions */

void SHAInit(SHA_CTX *);
void SHAUpdate(SHA_CTX *, BYTE *buffer, int count);
void SHAFinal(BYTE *output, SHA_CTX *);

#endif /* end _SHA_H_ */

/* endian.h */

#ifndef _ENDIAN_H_
#define _ENDIAN_H_ 1

void endianTest(int *endianness);

#endif /* end _ENDIAN_H_ */

/* sha.c */

#include <stdio.h>
#include <string.h>

static void SHAtoByte(BYTE *output, UINT4 *input, unsigned int len);

/* The SHS block size and message digest sizes, in bytes */

#define SHS_DATASIZE    64
#define SHS_DIGESTSIZE  20


/* The SHS f()-functions.  The f1 and f3 functions can be optimized to
   save one boolean operation each - thanks to Rich Schroeppel,
   rcs@cs.arizona.edu for discovering this */

/*#define f1(x,y,z) ( ( x & y ) | ( ~x & z ) )          // Rounds  0-19 */
#define f1(x,y,z)   ( z ^ ( x & ( y ^ z ) ) )           /* Rounds  0-19 */
#define f2(x,y,z)   ( x ^ y ^ z )                       /* Rounds 20-39 */
/*#define f3(x,y,z) ( ( x & y ) | ( x & z ) | ( y & z ) )   // Rounds 40-59 */
#define f3(x,y,z)   ( ( x & y ) | ( z & ( x | y ) ) )   /* Rounds 40-59 */
#define f4(x,y,z)   ( x ^ y ^ z )                       /* Rounds 60-79 */

/* The SHS Mysterious Constants */

#define K1  0x5A827999L                                 /* Rounds  0-19 */
#define K2  0x6ED9EBA1L                                 /* Rounds 20-39 */
#define K3  0x8F1BBCDCL                                 /* Rounds 40-59 */
#define K4  0xCA62C1D6L                                 /* Rounds 60-79 */

/* SHS initial values */

#define h0init  0x67452301L
#define h1init  0xEFCDAB89L
#define h2init  0x98BADCFEL
#define h3init  0x10325476L
#define h4init  0xC3D2E1F0L

/* Note that it may be necessary to add parentheses to these macros if they
   are to be called with expressions as arguments */
/* 32-bit rotate left - kludged with shifts */

#define ROTL(n,X)  ( ( ( X ) << n ) | ( ( X ) >> ( 32 - n ) ) )

/* The initial expanding function.  The hash function is defined over an
   80-UINT2 expanded input array W, where the first 16 are copies of the input
   data, and the remaining 64 are defined by

        W[ i ] = W[ i - 16 ] ^ W[ i - 14 ] ^ W[ i - 8 ] ^ W[ i - 3 ]

   This implementation generates these values on the fly in a circular
   buffer - thanks to Colin Plumb, colin@nyx10.cs.du.edu for this
   optimization.

   The updated SHS changes the expanding function by adding a rotate of 1
   bit.  Thanks to Jim Gillogly, jim@rand.org, and an anonymous contributor
   for this information */

#define expand(W,i) ( W[ i & 15 ] = ROTL( 1, ( W[ i & 15 ] ^ W[ (i - 14) & 15 ] ^ \
                                                 W[ (i - 8) & 15 ] ^ W[ (i - 3) & 15 ] ) ) )


/* The prototype SHS sub-round.  The fundamental sub-round is:

        a' = e + ROTL( 5, a ) + f( b, c, d ) + k + data;
        b' = a;
        c' = ROTL( 30, b );
        d' = c;
        e' = d;

   but this is implemented by unrolling the loop 5 times and renaming the
   variables ( e, a, b, c, d ) = ( a', b', c', d', e' ) each iteration.
   This code is then replicated 20 times for each of the 4 functions, using
   the next 20 values from the W[] array each time */

#define subRound(a, b, c, d, e, f, k, data) \
    ( e += ROTL( 5, a ) + f( b, c, d ) + k + data, b = ROTL( 30, b ) )

/* Initialize the SHS values */

void SHAInit(SHA_CTX *shsInfo)
{
    endianTest(&shsInfo->Endianness);
    /* Set the h-vars to their initial values */
    shsInfo->digest[ 0 ] = h0init;
    shsInfo->digest[ 1 ] = h1init;
    shsInfo->digest[ 2 ] = h2init;
    shsInfo->digest[ 3 ] = h3init;
    shsInfo->digest[ 4 ] = h4init;

    /* Initialise bit count */
    shsInfo->countLo = shsInfo->countHi = 0;
}


/* Perform the SHS transformation.  Note that this code, like MD5, seems to
   break some optimizing compilers due to the complexity of the expressions
   and the size of the basic block.  It may be necessary to split it into
   sections, e.g. based on the four subrounds

   Note that this corrupts the shsInfo->data area */

static void SHSTransform( digest, data )
     UINT4 *digest, *data ;
    {
    UINT4 A, B, C, D, E;     /* Local vars */
    UINT4 eData[ 16 ];       /* Expanded data */

    /* Set up first buffer and local data buffer */
    A = digest[ 0 ];
    B = digest[ 1 ];
    C = digest[ 2 ];
    D = digest[ 3 ];
    E = digest[ 4 ];
    memcpy( (POINTER)eData, (POINTER)data, SHS_DATASIZE );

    /* Heavy mangling, in 4 sub-rounds of 20 interations each. */
    subRound( A, B, C, D, E, f1, K1, eData[  0 ] );
    subRound( E, A, B, C, D, f1, K1, eData[  1 ] );
    subRound( D, E, A, B, C, f1, K1, eData[  2 ] );
    subRound( C, D, E, A, B, f1, K1, eData[  3 ] );
    subRound( B, C, D, E, A, f1, K1, eData[  4 ] );
    subRound( A, B, C, D, E, f1, K1, eData[  5 ] );
    subRound( E, A, B, C, D, f1, K1, eData[  6 ] );
    subRound( D, E, A, B, C, f1, K1, eData[  7 ] );
    subRound( C, D, E, A, B, f1, K1, eData[  8 ] );
    subRound( B, C, D, E, A, f1, K1, eData[  9 ] );
    subRound( A, B, C, D, E, f1, K1, eData[ 10 ] );
    subRound( E, A, B, C, D, f1, K1, eData[ 11 ] );
    subRound( D, E, A, B, C, f1, K1, eData[ 12 ] );
    subRound( C, D, E, A, B, f1, K1, eData[ 13 ] );
    subRound( B, C, D, E, A, f1, K1, eData[ 14 ] );
    subRound( A, B, C, D, E, f1, K1, eData[ 15 ] );
    subRound( E, A, B, C, D, f1, K1, expand( eData, 16 ) );
    subRound( D, E, A, B, C, f1, K1, expand( eData, 17 ) );
    subRound( C, D, E, A, B, f1, K1, expand( eData, 18 ) );
    subRound( B, C, D, E, A, f1, K1, expand( eData, 19 ) );

    subRound( A, B, C, D, E, f2, K2, expand( eData, 20 ) );
    subRound( E, A, B, C, D, f2, K2, expand( eData, 21 ) );
    subRound( D, E, A, B, C, f2, K2, expand( eData, 22 ) );
    subRound( C, D, E, A, B, f2, K2, expand( eData, 23 ) );
    subRound( B, C, D, E, A, f2, K2, expand( eData, 24 ) );
    subRound( A, B, C, D, E, f2, K2, expand( eData, 25 ) );
    subRound( E, A, B, C, D, f2, K2, expand( eData, 26 ) );
    subRound( D, E, A, B, C, f2, K2, expand( eData, 27 ) );
    subRound( C, D, E, A, B, f2, K2, expand( eData, 28 ) );
    subRound( B, C, D, E, A, f2, K2, expand( eData, 29 ) );
    subRound( A, B, C, D, E, f2, K2, expand( eData, 30 ) );
    subRound( E, A, B, C, D, f2, K2, expand( eData, 31 ) );
    subRound( D, E, A, B, C, f2, K2, expand( eData, 32 ) );
    subRound( C, D, E, A, B, f2, K2, expand( eData, 33 ) );
    subRound( B, C, D, E, A, f2, K2, expand( eData, 34 ) );
    subRound( A, B, C, D, E, f2, K2, expand( eData, 35 ) );
    subRound( E, A, B, C, D, f2, K2, expand( eData, 36 ) );
    subRound( D, E, A, B, C, f2, K2, expand( eData, 37 ) );
    subRound( C, D, E, A, B, f2, K2, expand( eData, 38 ) );
    subRound( B, C, D, E, A, f2, K2, expand( eData, 39 ) );

    subRound( A, B, C, D, E, f3, K3, expand( eData, 40 ) );
    subRound( E, A, B, C, D, f3, K3, expand( eData, 41 ) );
    subRound( D, E, A, B, C, f3, K3, expand( eData, 42 ) );
    subRound( C, D, E, A, B, f3, K3, expand( eData, 43 ) );
    subRound( B, C, D, E, A, f3, K3, expand( eData, 44 ) );
    subRound( A, B, C, D, E, f3, K3, expand( eData, 45 ) );
    subRound( E, A, B, C, D, f3, K3, expand( eData, 46 ) );
    subRound( D, E, A, B, C, f3, K3, expand( eData, 47 ) );
    subRound( C, D, E, A, B, f3, K3, expand( eData, 48 ) );
    subRound( B, C, D, E, A, f3, K3, expand( eData, 49 ) );
    subRound( A, B, C, D, E, f3, K3, expand( eData, 50 ) );
    subRound( E, A, B, C, D, f3, K3, expand( eData, 51 ) );
    subRound( D, E, A, B, C, f3, K3, expand( eData, 52 ) );
    subRound( C, D, E, A, B, f3, K3, expand( eData, 53 ) );
    subRound( B, C, D, E, A, f3, K3, expand( eData, 54 ) );
    subRound( A, B, C, D, E, f3, K3, expand( eData, 55 ) );
    subRound( E, A, B, C, D, f3, K3, expand( eData, 56 ) );
    subRound( D, E, A, B, C, f3, K3, expand( eData, 57 ) );
    subRound( C, D, E, A, B, f3, K3, expand( eData, 58 ) );
    subRound( B, C, D, E, A, f3, K3, expand( eData, 59 ) );

    subRound( A, B, C, D, E, f4, K4, expand( eData, 60 ) );
    subRound( E, A, B, C, D, f4, K4, expand( eData, 61 ) );
    subRound( D, E, A, B, C, f4, K4, expand( eData, 62 ) );
    subRound( C, D, E, A, B, f4, K4, expand( eData, 63 ) );
    subRound( B, C, D, E, A, f4, K4, expand( eData, 64 ) );
    subRound( A, B, C, D, E, f4, K4, expand( eData, 65 ) );
    subRound( E, A, B, C, D, f4, K4, expand( eData, 66 ) );
    subRound( D, E, A, B, C, f4, K4, expand( eData, 67 ) );
    subRound( C, D, E, A, B, f4, K4, expand( eData, 68 ) );
    subRound( B, C, D, E, A, f4, K4, expand( eData, 69 ) );
    subRound( A, B, C, D, E, f4, K4, expand( eData, 70 ) );
    subRound( E, A, B, C, D, f4, K4, expand( eData, 71 ) );
    subRound( D, E, A, B, C, f4, K4, expand( eData, 72 ) );
    subRound( C, D, E, A, B, f4, K4, expand( eData, 73 ) );
    subRound( B, C, D, E, A, f4, K4, expand( eData, 74 ) );
    subRound( A, B, C, D, E, f4, K4, expand( eData, 75 ) );
    subRound( E, A, B, C, D, f4, K4, expand( eData, 76 ) );
    subRound( D, E, A, B, C, f4, K4, expand( eData, 77 ) );
    subRound( C, D, E, A, B, f4, K4, expand( eData, 78 ) );
    subRound( B, C, D, E, A, f4, K4, expand( eData, 79 ) );

    /* Build message digest */
    digest[ 0 ] += A;
    digest[ 1 ] += B;
    digest[ 2 ] += C;
    digest[ 3 ] += D;
    digest[ 4 ] += E;
    }

/* When run on a little-endian CPU we need to perform byte reversal on an
   array of long words. */

static void longReverse(UINT4 *buffer, int byteCount, int Endianness )
{
    UINT4 value;

    if (Endianness==TRUE) return;
    byteCount /= sizeof( UINT4 );
    while( byteCount-- )
        {
        value = *buffer;
        value = ( ( value & 0xFF00FF00L ) >> 8  ) | \
                ( ( value & 0x00FF00FFL ) << 8 );
        *buffer++ = ( value << 16 ) | ( value >> 16 );
        }
}

/* Update SHS for a block of data */

void SHAUpdate(SHA_CTX *shsInfo, BYTE *buffer, int count)
{
    UINT4 tmp;
    int dataCount;

    /* Update bitcount */
    tmp = shsInfo->countLo;
    if ( ( shsInfo->countLo = tmp + ( ( UINT4 ) count << 3 ) ) < tmp )
        shsInfo->countHi++;             /* Carry from low to high */
    shsInfo->countHi += count >> 29;

    /* Get count of bytes already in data */
    dataCount = ( int ) ( tmp >> 3 ) & 0x3F;

    /* Handle any leading odd-sized chunks */
    if( dataCount )
        {
        BYTE *p = ( BYTE * ) shsInfo->data + dataCount;

        dataCount = SHS_DATASIZE - dataCount;
        if( count < dataCount )
            {
            memcpy( p, buffer, count );
            return;
            }
        memcpy( p, buffer, dataCount );
        longReverse( shsInfo->data, SHS_DATASIZE, shsInfo->Endianness);
        SHSTransform( shsInfo->digest, shsInfo->data );
        buffer += dataCount;
        count -= dataCount;
        }

    /* Process data in SHS_DATASIZE chunks */
    while( count >= SHS_DATASIZE )
        {
        memcpy( (POINTER)shsInfo->data, (POINTER)buffer, SHS_DATASIZE );
        longReverse( shsInfo->data, SHS_DATASIZE, shsInfo->Endianness );
        SHSTransform( shsInfo->digest, shsInfo->data );
        buffer += SHS_DATASIZE;
        count -= SHS_DATASIZE;
        }

    /* Handle any remaining bytes of data. */
    memcpy( (POINTER)shsInfo->data, (POINTER)buffer, count );
    }

/* Final wrapup - pad to SHS_DATASIZE-byte boundary with the bit pattern
   1 0* (64-bit count of bits processed, MSB-first) */

void SHAFinal(BYTE *output, SHA_CTX *shsInfo)
{
    int count;
    BYTE *dataPtr;

    /* Compute number of bytes mod 64 */
    count = ( int ) shsInfo->countLo;
    count = ( count >> 3 ) & 0x3F;

    /* Set the first char of padding to 0x80.  This is safe since there is
       always at least one byte free */
    dataPtr = ( BYTE * ) shsInfo->data + count;
    *dataPtr++ = 0x80;

    /* Bytes of padding needed to make 64 bytes */
    count = SHS_DATASIZE - 1 - count;

    /* Pad out to 56 mod 64 */
    if( count < 8 )
        {
        /* Two lots of padding:  Pad the first block to 64 bytes */
        memset( dataPtr, 0, count );
        longReverse( shsInfo->data, SHS_DATASIZE, shsInfo->Endianness );
        SHSTransform( shsInfo->digest, shsInfo->data );

        /* Now fill the next block with 56 bytes */
        memset( (POINTER)shsInfo->data, 0, SHS_DATASIZE - 8 );
        }
    else
        /* Pad block to 56 bytes */
        memset( dataPtr, 0, count - 8 );

    /* Append length in bits and transform */
    shsInfo->data[ 14 ] = shsInfo->countHi;
    shsInfo->data[ 15 ] = shsInfo->countLo;

    longReverse( shsInfo->data, SHS_DATASIZE - 8, shsInfo->Endianness );
    SHSTransform( shsInfo->digest, shsInfo->data );

	/* Output to an array of bytes */
	SHAtoByte(output, shsInfo->digest, SHS_DIGESTSIZE);

	/* Zeroise sensitive stuff */
	memset((POINTER)shsInfo, 0, sizeof(shsInfo));
}

static void SHAtoByte(BYTE *output, UINT4 *input, unsigned int len)
{	/* Output SHA digest in byte array */
	unsigned int i, j;

	for(i = 0, j = 0; j < len; i++, j += 4) 
	{
        output[j+3] = (BYTE)( input[i]        & 0xff);
        output[j+2] = (BYTE)((input[i] >> 8 ) & 0xff);
        output[j+1] = (BYTE)((input[i] >> 16) & 0xff);
        output[j  ] = (BYTE)((input[i] >> 24) & 0xff);
	}
}


unsigned char digest[20];
unsigned char message[3] = {'a', 'b', 'c' };
unsigned char *mess56 = 
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";

/* Correct solutions from FIPS PUB 180-1 */
char *dig1 = "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D";
char *dig2 = "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1";
char *dig3 = "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F";

/* Output should look like:-
 a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d
 A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D <= correct
 84983e44 1c3bd26e baae4aa1 f95129e5 e54670f1
 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 <= correct
 34aa973c d4c4daa4 f61eeb2b dbad2731 6534016f
 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F <= correct
*/

main()
{
	SHA_CTX sha;
	int i;
	BYTE big[1000];

	SHAInit(&sha);
	SHAUpdate(&sha, message, 3);
	SHAFinal(digest, &sha);

	for (i = 0; i < 20; i++)
	{
		if ((i % 4) == 0) printf(" ");
		printf("%02x", digest[i]);
	}
	printf("\n");
	printf(" %s <= correct\n", dig1);

	SHAInit(&sha);
	SHAUpdate(&sha, mess56, 56);
	SHAFinal(digest, &sha);

	for (i = 0; i < 20; i++)
	{
		if ((i % 4) == 0) printf(" ");
		printf("%02x", digest[i]);
	}
	printf("\n");
	printf(" %s <= correct\n", dig2);

	/* Fill up big array */
	for (i = 0; i < 1000; i++)
		big[i] = 'a';

	SHAInit(&sha);
	/* Digest 1 million x 'a' */
	for (i = 0; i < 1000; i++)
		SHAUpdate(&sha, big, 1000);
	SHAFinal(digest, &sha);

	for (i = 0; i < 20; i++)
	{
		if ((i % 4) == 0) printf(" ");
		printf("%02x", digest[i]);
	}
	printf("\n");
	printf(" %s <= correct\n", dig3);

	return 0;
}

/* endian.c */

void endianTest(int *endian_ness)
{
	if((*(unsigned short *) ("#S") >> 8) == '#')
	{
		/* printf("Big endian = no change\n"); */
		*endian_ness = !(0);
	}
	else
	{
		/* printf("Little endian = swap\n"); */
		*endian_ness = 0;
	}
}

He dejado todos los comentarios, aunque se os pueda hacer coñazo, por respeto a los creadores originales. La función original la podéis consultar aquí.

Para C++ por otra parte, podéis disfrutar de la librería libre Crypto++ con múltiples algoritmos, que podéis encontrar en este enlace.

Instalación de OpenERP en Ubunto o Debian

OpenERP es una de las soluciones empresariales más populares dentro del software libre. Con una enorme escalabilidad, nos permite modificarlo para crear módulos específicos para las necesidades de una empresa determinada. Utiliza PostgreSQL como SGBD y está programado en Python, por lo que el conocimiento de este lenguaje será clave para la realización de módulos, aunque si ya has trabajado con C, Java o C# la sorpresa será mínima, incluso para programadores PHP (programadores, no diseñadores gráficos que hacen webs a base de Dreamweaver y módulos prefabricados). Pero el desarrollo de módulos puede que lo toquemos más adelante, hoy vamos con la instalación del servidor web de OpenERP 6.0 sobre Ubuntu o sobre Debian. Mayormente es una traducción de este artículo en inglés… bueno, una adaptación explicada con mis palabras.

Vamos al principio, first step, preparar el equipo para toda la instalación: instalar un servidor, crear el usuario, instalar Postgres… Ya sabes, por partes te lo pongo en el siguiente ejemplo:

#Preparar el servidor
apt-get install openssh-server denyhosts
apt-get update
apt-get dist-upgrade

#Crear el usuario
adduser –system –home=/opt/openerp –group openerp passwd openerp

#Instalamos PostgreSQL, creamos la base de datos y el usuario
apt-get install postgresql
su – postgres
createuser –createdb –username postgres –no-createrole –pwprompt openerp
exit

#Instalamos los paquetes y librerías de Python, que falta nos van a hacer (tochazo del 15)

apt-get install python python-psycopg2 python-reportlab python-egenix-mxdatetime python-tz python-pychart python-mako python-pydot python-lxml python-vobject python-yaml python-dateutil python-pychart python-pydot python-webdav

apt-get install python-cherrypy3 python-formencode python-pybabel python-simplejson python-pyparsing

Ahí estamos, todo el equipo listo para instalar OpenERP. La cuestión es descargar el server y el cliente web, descomprimir todo, dar permisos… En el ejemplo lo tenéis todo:


#Descargamos ambos paquetes
wget http://www.openerp.com/download/stable/source/openerp-server-6.0.3.tar.gz
wget http://www.openerp.com/download/stable/source/openerp-web-6.0.3.tar.gz

#Descomprimir, cambiar permisos... etc:
cd /opt/openerp
tar xvf ~/openerp-server-6.0.3.tar.gz
tar xvf ~/openerp-web-6.0.3.tar.gz
chown -R openerp: *
cp -a openerp-server-6.0.3 server
cp -a openerp-web-6.0.3 web

Ahora toca tocar el archivo de configuración del servidor, cambiando el password de la base de datos. En la carpeta /etc/ le metéis mano a openerp-server.conf con el editor Nano. Busca la línea db_password y allí añade la contraseña buena.

Tras esto, toca cambiar al propietario:

sudo chown openerp:root /etc/openerp-server.conf
sudo chmod 640 /etc/openerp-server.conf

Ahora toca crear, o modificar, el script para que arranque con el sistema, modificando /etc/initd.d/openerp-server, tal que así:

#!/bin/sh

### BEGIN INIT INFO
# Provides:             openerp-server
# Required-Start:       $remote_fs $syslog
# Required-Stop:        $remote_fs $syslog
# Should-Start:         $network
# Should-Stop:          $network
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Enterprise Resource Management software
# Description:          Open ERP is a complete ERP and CRM software.
### END INIT INFO

PATH=/bin:/sbin:/usr/bin
DAEMON=/opt/openerp/server/bin/openerp-server.py
NAME=openerp-server
DESC=openerp-server

# Specify the user name (Default: openerp).
USER=openerp

# Specify an alternate config file (Default: /etc/openerp-server.conf).
CONFIGFILE="/etc/openerp-server.conf"

# pidfile
PIDFILE=/var/run/$NAME.pid

# Additional options that are passed to the Daemon.
DAEMON_OPTS="-c $CONFIGFILE"

[ -x $DAEMON ] || exit 0
[ -f $CONFIGFILE ] || exit 0

checkpid() {
    [ -f $PIDFILE ] || return 1
    pid=`cat $PIDFILE`
    [ -d /proc/$pid ] && return 0
    return 1
}

case "${1}" in
        start)
                echo -n "Starting ${DESC}: "

                start-stop-daemon --start --quiet --pidfile ${PIDFILE} \
                        --chuid ${USER} --background --make-pidfile \
                        --exec ${DAEMON} -- ${DAEMON_OPTS}

                echo "${NAME}."
                ;;

        stop)
                echo -n "Stopping ${DESC}: "

                start-stop-daemon --stop --quiet --pidfile ${PIDFILE} \
                        --oknodo

                echo "${NAME}."
                ;;

        restart|force-reload)
                echo -n "Restarting ${DESC}: "

                start-stop-daemon --stop --quiet --pidfile ${PIDFILE} \
                        --oknodo

                sleep 1

                start-stop-daemon --start --quiet --pidfile ${PIDFILE} \
                        --chuid ${USER} --background --make-pidfile \
                        --exec ${DAEMON} -- ${DAEMON_OPTS}

                echo "${NAME}."
                ;;

        *)
                N=/etc/init.d/${NAME}
                echo "Usage: ${NAME} {start|stop|restart|force-reload}" >&2
                exit 1
                ;;
esac

exit 0

Tras eso cambiamos los permisos a root:

chmod 755 /etc/init.d/openerp-server
chown root: /etc/init.d/openerp-server

#Con el directorio de logs hay que hacer lo mismo
mkdir /var/log/openerp
chown openerp:root /var/log/openerp

#Y finalmente añadimos el servicio para que se inicie con el sistema:
update-rc.d openerp-server defaults

Ahora podéis probar a arrancar y ver si todo furrula: sudo /etc/init.d/openerp-server start y si la cosa va bien, en el puerto 8080 debería haber una página de prueba.

Pero claro, ahora toca el cliente web. El proceso es similar, tenéis que meterle mano a cd /etc/openerp-web.conf.

server.environment = "development"

# Some server parameters that you may want to tweak
server.socket_host = "0.0.0.0"
server.socket_port = 8080

# Sets the number of threads the server uses
server.thread_pool = 10

tools.sessions.on = True
tools.sessions.persistent = False

# Simple code profiling
server.profile_on = False
server.profile_dir = "profile"

# if this is part of a larger site, you can set the path
# to the TurboGears instance here
#server.webpath = ""

# Set to True if you are deploying your App behind a proxy
# e.g. Apache using mod_proxy
#tools.proxy.on = True

# If your proxy does not add the X-Forwarded-Host header, set
# the following to the *public* host url.
#tools.proxy.base = 'http://mydomain.com'

# logging
#log.access_file = "/var/log/openerp-web/access.log"
log.error_file = "/var/log/openerp/openerp-web-error.log"
log.access_level = "INFO"
log.error_level = "INFO"

# Set to false to disable CSRF checks
tools.csrf.on = True

# replace builtin traceback tools by cgitb
tools.log_tracebacks.on: False
tools.cgitb.on: True
# a default install can probably avoid logging those via cgitb as they're
# available in the server log
tools.cgitb.ignore=(
    openobject.errors.Concurrency,
    openobject.errors.TinyException)

# OpenERP Server
openerp.server.host = 'localhost'
openerp.server.port = '8070'
openerp.server.protocol = 'socket'
openerp.server.timeout = 450

# Web client settings
[openerp-web]
# filter dblists based on url pattern?
# NONE: No Filter
# EXACT: Exact Hostname
# UNDERSCORE: Hostname_
# BOTH: Exact Hostname or Hostname_

dblist.filter = 'NONE'

# whether to show Databases button on Login screen or not
dbbutton.visible = True

# will be applied on company logo
company.url = ''

Tras esto, de nuevo toca cambiar propietarios:

chown openerp:root /etc/openerp-web.conf
chmod 640 /etc/openerp-web.conf

Tras esto tocará automatizar el cliente web también, tocando /etc/init.d/nano openerp-web y dejando la cosa tal como en el ejemplo:

#!/bin/sh

### BEGIN INIT INFO
# Provides:             openerp-web
# Required-Start:       $remote_fs $syslog
# Required-Stop:        $remote_fs $syslog
# Should-Start:         $network
# Should-Stop:          $network
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    OpenERP Web - the Web Client of the OpenERP
# Description:          Open ERP is a complete ERP and CRM software.
### END INIT INFO

PATH="/sbin:/bin:/usr/sbin:/usr/bin"
DAEMON="/opt/openerp/web/openerp-web.py"
NAME="openerp-web"
DESC="openerp-web"

# Specify the user name (Default: openerp).
USER=openerp

# Specify an alternate config file (Default: /etc/openerp-web.conf).
CONFIGFILE="/etc/openerp-web.conf"

# pidfile
PIDFILE=/var/run/$NAME.pid

# Additional options that are passed to the Daemon.
DAEMON_OPTS="-c $CONFIGFILE"

[ -x $DAEMON ] || exit 0
[ -f $CONFIGFILE ] || exit 0

checkpid() {
    [ -f $PIDFILE ] || return 1
    pid=`cat $PIDFILE`
    [ -d /proc/$pid ] && return 0
    return 1
}

case "${1}" in
        start)
                echo -n "Starting ${DESC}: "

                start-stop-daemon --start --quiet --pidfile ${PIDFILE} \
                        --chuid ${USER} --background --make-pidfile \
                        --exec ${DAEMON} -- ${DAEMON_OPTS}

                echo "${NAME}."
                ;;

        stop)
                echo -n "Stopping ${DESC}: "

                start-stop-daemon --stop --quiet --pidfile ${PIDFILE} \
                        --oknodo

                echo "${NAME}."
                ;;

        restart|force-reload)
                echo -n "Restarting ${DESC}: "

                start-stop-daemon --stop --quiet --pidfile ${PIDFILE} \
                        --oknodo

                sleep 1

                start-stop-daemon --start --quiet --pidfile ${PIDFILE} \
                        --chuid ${USER} --background --make-pidfile \
                        --exec ${DAEMON} -- ${DAEMON_OPTS}

                echo "${NAME}."
                ;;

        *)
                N=/etc/init.d/${NAME}
                echo "Usage: ${NAME} {start|stop|restart|force-reload}" >&2
                exit 1
                ;;
esac

exit 0

En fin, de nuevo sólo nos queda cambiar los permisos de propietario e incluir el script en el inicio, para que todo vaya como la seda:

chmod 755 /etc/init.d/openerp-web
chown root: /etc/init.d/openerp-web

update-rc.d openerp-web defaults

Y con todo esto, el servidor y el cliente web ya están listos para trastear con OpenERP en vuestro equipo.

Funciones de resumen (MD5, SHA-1)

Aunque ya hablé de MD5 en este blog (con una entrada que hasta está referenciada en la wikipedia), no expliqué de todo el funcionamiento y la utilidad de las funciones de resumen, o funciones hash, y también conocidas como huellas de mensaje o huellas digitales. Se utilizan para crear una cadena de longitud fija o resumen de mensaje a partir de una cadena de entrada de longitud variable.

Este tipo de funciones han de cumplir una serie de condiciones para ser consideradas seguras:

  • No debe ser posible averiguar el mensaje de entrada basándose sólo en su resumen. Ha de ser una función irreversible de una sola dirección.
  • Dado un resumen debe ser imposible encontrar un mensaje que lo genere.
  • Debe ser computacionalmente imposible encontrar dos mensajes que generen el mismo resumen.

Es por esto por lo que a día de hoy se está dudando del futuro de MD5, dado que existen posibilidades de colisión (incumple el punto tres) que le harían vulnerable a un ataque de tipo cumpleaños.

SHA-1, al tener una longitud de clave mayor (160 bits frente a los 128 de MD5), es más robusto y resistente a ataques, si bien también se ha planteado que en un futuro próximo puede tornarse inseguro a ataques de fuerza bruta.

Las utilidades de estas funciones son múltiples y varias, entre otras almacenar contraseñas de forma segura (almacenando el hash en lugar del texto plano), la autenticación de entidades en un control de acceso, la identificación y comparación rápida de contenidos (por ejemplo en redes P2P),  o para proteger la integridad de los datos ya sea como firma digital o generando un checksum.

Como ejemplo, si probamos a encriptar la cadena pepito con MD5 y SHA-1 obtendremos estos resultados:

  • MD5: 32164702f8ffd2b418d780ff02371e4c
  • SHA-1: e04820372e7f2ebb2d76987433579219b11c2ba5

Ya véis que la segunda cadena/resumen es sensiblemente más larga.

En fin, las funciones hash son un elemento clave en la seguridad informática, siendo muy utilizadas tanto en UNIX como en Internet.

Algoritmos criptográficos más frecuentes

Un algoritmo criptográfico no es más (bueno, lo digo como si esto fuera poco… xD) que una función matemática usada en los procesos de encriptación y desencriptación. El algoritmo se sirve de una clave para encriptar y desencriptar datos.

Tal vez lo principal sea empezar por la diferencia entre sistemas de clave simétrica y asimétrica.  En el caso de la criptografía simétrica la explicación es simple: se usa la misma clave para encriptar y desencriptar, es decir, tanto el emisor como el receptor han de conocer la misma clave. La cuestión de la criptografía asimétrica es más compleja, utilizando dos claves: pública y privada, la privada sólo la tiene el receptor, y la pública todos los emisores. El emisor encripta el mensaje usando la clave pública, pero este sólo puede ser desencriptado con la clave privada (que sólo tiene el receptor y que nadie más ha de saber para que la seguridad sea eficiente).

Ahora, simplemente, incluyo una lista con los más utilizados.

  • DES:  Consistente en una serie de permutaciones y sustituciones, utiliza una clave simétrica de 64 bits, de los cuales 56 se usan para la encriptación y 8 para la paridad. Fue diseñado por IBM y declarado standar en 1977 por la NBS, y en esos tiempos el algoritmo estuvo rodeado de gran polémica: se consideraba que la longitud de clave era insuficiente y existían fuertes rumores de que la NSA había impuesto que este tuviera una puerta trasera. En 1992 se publicó el primer ataque teórico para romperlo, en 1998 una máquina diseñada específicamente para romperlo lo logró en 56 horas, y en 1999 un ordenador normal fue capaz de romperlo en 22 horas. A partir de ese momento se considera DES como inseguro, pero una nueva especificación, TripleDES o 3DES, una evolución de DES con clave de 128 bits y triple cifrado, lo sustituye. Desde 2001 se adopta como estandar AES, mucho más robusto.
  • RC5: Es la evolución de RC4, que consistía en hacer un XOR al mensaje con un vector aleatorio que se desprende de la clave. RC5 en cambio usa otra operación, llamada dependencia de datos. Como peculiaridad tiene un tamaño variable de bloque (16,32, 64 0 128 bits), de clave (de 0 a 2024 bits) y de número de vueltas (de 0 a 255)
  • IDEA: Trabaja con bloques de texto de 64 bits, operando siempre con números de 16 bits y usando operaciones como XOR, y suma y multiplicación de enteros. El algoritmo de desencriptación es muy similar al de encriptación, lo que lo hace muy rápido y fácil de programar. Actualmente se considera que es invulnerable a ataques por fuerza bruta, e incluso frente al criptoanálisis diferencial resiste muy bien, teniendo sólo vulnerabilidades las claves más cortas, pero con una clave fuerte es muy resistente. Además, se trata de un algoritmo de libre difusión, lo que permite que cualquiera pueda utilizarlo.
  • Diffie-Hellman: El protocolo criptográfico de Diffie-Hellman, llamado así por sus creadores, fue el punto de partida de los sistemas asimétricos, basados en clave pública y clave privada. Su validez para el intercambio de claves asimétricas hace que esté implementado en los diferentes sistemas seguros más habituales en Internet, como SSL o VPN. El algoritmo se basa en potencias de números y la función mod (módulo discreto), haciendo uso de las potencias discretas, fáciles de calcular pero siendo muy difícil obtener la función inversa. Este protocolo sí es sensible a ataques man in the middle, en los que alguien pueda interceptar la comunicación.
  • RSA: Aunque fue creado en 1977, RSA sigue siendo el sistema de clave pública más conocido y usado. Se trata de un sistema muy rápido y que se utiliza mucho en los sistemas mixtos (lo explicaré más abajo). Su seguridad radica en la dificultad de factorizar números enteros grandes. Los mensajes enviados se representan mediante números, y el funcionamiento se basa en el producto conocido de dos números primos grandes elegidos al azar. El cálculo de estas claves se realiza en secreto en la máquina en la que se va a guardar la clave privada.

Bueno, como ya comenté arriba, existen sistemas mixtos. Los sistemas de clave asimétrica requieren más coste de proceso que los de clave simétrica, este hecho ha llevado a buscar una solución híbrida que combine la seguridad de la transmisión de claves asimétricas con la mayor ligereza en el procesamiento de los sistemas simétricos. El concepto es sencillo: el mensaje se encripta utilizando una clave simétrica, la clave simétrica a su vez se encripta utilizando una clave pública y se envía todo en el mismo paquete. De esta forma sólo se requiere usar la clave privada para desencriptar la clave simétrica, y una vez se obtiene esta basta con utilizarla para descifrar el resto del mensaje.