Lubuntu 12.04 (faster than a bullet)

Faster than a bullet… así empieza el Painkiller de los Judas, y así se puede definir la versión de Ubuntu con escritorio LXDE. ¿Tienes un equipo viejo por casa que no es capaz de tirar con Windows Vista, 7 o con un Ubuntu normal? Dejarle un XP desactualizado, con todos sus agujerazos de seguridad por falta de actualizaciones, puede no ser una buena opción si pretendes navegar por internet con él. Cualquier experto en seguridad te lo dirá, el SO siempre actualizado a la última. ¿Quiere eso decir que hay que dejar el equipo morirse? No, Lubuntu te ofrece una alternativa ligera.

El sistema requiere como mínimo un Pentium II con 128 megas de RAM para arrancar. Con esta configuración debería ir un pelín justo, pero para lo básico ya furrula. Una curiosidad es que no incluye Firefox como navegador predeterminado, sino Chromium, por cuestiones de rendimiento (Firefox es más pesado). De hecho tampoco incluye Libre Office ni Open Office, sino una pequeña serie de programas ofimáticos más limitados pero con menor consumo. En todo caso, basta un apt-get install para hacerse con todo lo que queramos o, siendo más comodones, desde el gestor de aplicaciones.

Testeado sobre un máquina virtual de 1.5 GB de RAM primero, con instalación básica, y luego sobre una máquina física (1GB de RAM DDR2, Intel Centrino T2500 2GHZ) separando en 5 particiones (SWAP, /boot, /, /tmp y /home) el resultado es soprendentemente rápido.

A nivel de software dispondrás de todo el habido y por haber para Ubuntu, si bien ten en cuenta que si estás usando este sistema para tirar de máquina con pocos recursos, es posible que para cierto software andes justo en extremo (modificar una foto con GIMP 2.8 con 128 megas de RAM no se si es imposible pero harto complejo, seguro). En todo caso, si tu equipo tiene los mínimos para mover Ubuntu (que iría pelín lento y pesado en él), con Lubuntu lograrás que vaya como un tiro. Desde su gestor de aplicaciones, además, podrás instalar cómodamente todo el software libre que quieras. Y sobre seguridad, no te comes mucho la cabeza, todas las derivaciones de Ubuntu 12.04 son LTS (es decir, 5 años de actualizaciones de seguridad, tienes SO hasta abril de 2017 actualizado).

 

Lubuntu es una forma de revivir un viejo ordenador teniéndolo totalmente actualizado, o de mejorar tu rendimiento en un equipo que vaya justito. Realmente lo único es que tiene un entorno más feo que un Ubuntu normal, pero a nivel funcional igual hasta un usuario que venga de Windows se adapta mejor a LXDE que a Unity. En este vídeo puedes ver lo bien que furrula, y en este enlace descargarlo.

¿Qué tamaño doy a la partición boot?

Si ayer os hablaba de qué tamaño dar a la partición SWAP durante la instalación de Linux, hoy vamos con otra partición: La boot. En el caso de /boot no es obligatorio definirla, pero si la ubicas en una partición primaria al principio (después de la partición SWAP, que se recomienda que sea la primera) y le pones como sistema de archivos ext2 (más ligero que ext3 o ext4, que son los que habitualmente utilizaremos para el resto) lograrás una mayor velocidad de arranque. Que es posible que con ordenador muy potente no notes ninguna diferencia. En /boot se almacena el núcleo del sistema operativo y el sistema de arranque, así que no requiere mucho espacio.

Esto en caso de sistemas domésticos, porque cuando se trata de servidores con múltiples discos intercambiables o sistemas raid sí que puede volverse totalmente obligatorio tener el boot separado.

En todo caso ¿Qué tamaño le damos a /boot? Pues va dependiendo de la distribución de Linux y de si tenemos pensado tener varios kernels (ya sabéis que hay gente con el vicio de recompilarlo), pero en principio vamos a suponer un uso normal de usuario doméstico.

Todo esto lo comento basándome en lo leído en documentación y foros varios, y en alguna prueba doméstica con máquinas virtuales: para Ubuntu desde 10.10 hasta 11.10 dicen que 150 megas van sobrados, pero que para 12.04 mejor 250 . Para Debian 6 leo que se recomiendan 200. Para Fedora dicen que hasta  350, para ArchLinux leo que entre 100 y 150… Si tenéis un disco duro amplio lo mejor es no racanear y con 350 te aseguras no tener ningún problema de falta de espacio. La clave, como comentaba antes, está más en el sistema de archivos (ext2) y en ubicarla justo tras la SWAP.

¿Qué tamaño le asigno a la partición SWAP en Linux?

Si decides realizar una instalación manual en Linux ya sabes que necesitas al menos dos particiones: una para / y otra para el espacio de intercambio (SWAP). Aunque yo suelo hacer 4 particiones (/, swap, /boot y /home) para máquinas virtuales, y hasta 5 si el equipo va a usarse para editar vídeo, audio o trabajar con bases de datos muy grandes (ahí meto además separada /tmp); y tengo colegas que rara vez bajan de 7… (yo eso ya diría que es vicio). En todo caso suele surgir una duda ¿Qué espacio le asigno a SWAP durante la instalación?

Tradicionalmenet se decía «el doble de SWAP que lo que tengas de RAM«, pero es una teoría de los primeros tiempos de Linux, cuando los equipos andaban faltos de memoria. A día de hoy, tener 12 gigas de SWAP es una verdadera animalada, además de un desperdicio de recursos. Lo que sí se recomienda en varias páginas es que SWAP sea la primera partición del disco, por motivos de rendimiento.

Por lo que recomienda el señor Russell Coker la regla a seguir es la siguiente:

  • Si tienes hasta 1GB de RAM: El clásico «doble de SWAP que de RAM«
  • Si tienes entre 2GB y 4GB de RAM: Nueva teoría «La mitad de SWAP que de RAM«
  • Si tienes más de 4GB: Con 2 GB de SWAP vas que chutas.

Es más, si te fijas, todas las cuentas dan un máximo de 2GB de RAM. En fin, espero que esta minientrada te sea útil.

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.

Miniordenadores ¿qué opciones hay?

Todavía no he probado ninguno de estos miniordenadores, pero tras el susto que me llevé con el portátil hace unos días he estado pensando en hacerme con uno. A fin de cuentas, para lo más básico (consultar el correo electrónico, picar código) permiten ir tirando.

De momento los procesadores ARM se están llevando la palma en estos dispositivos, generalmente con SO Android o Linux. Aunque Intel ha anunciado la idea de sacar un modelo con arquitectura x86 bajo el nombre de NUC, pero en principio parece que este diseño iba destinado sólo a sistemas embebidos.

El proyecto inglés Raspberry Pi ha recibido muchos aplausos en los últimos timpos. Se trata de un proyecto orientado al campo educativo, pretendiendo ofrecer un sistema funcional a muy bajo coste orientado a la enseñanza de la programación a jóvenes estudiantes. Con 256 MB compartidos entre la RAM y la GPU, un CPU ARM mononúcleo a 700 MHz, salida vídeo HDMI y RCA y almacenamiento en tarjeta SD, se comercializan dos versiones: una de 25$ con un solo puerto USB y sin conexión a la red, y otro a 35$ con conexión 10/100 Ethernet RJ-45 y dos puertos USB (2.0). Aunque la idea de la fundación es que funcione con RISC-OS, existe soporte de Arch Linux, Debian o Fedora.

Como no, cuando se trata de producto barato los chinos tienen que sacar la cabeza. Los Allwiner 10 MK802, con aspecto de un usb grande (en esto ganan al Raspberry Pi por goleada), son equipos pensados originalmente para operar con Android (soportan la versión 4.0) aunque no deberían dar problemas con las versiones de Linux para procesadores ARM. Con un precio alrededor de los 74$, son bastante más potentes que el Pi: procesador mononúcleo de 1.5 GHz, 512 megas de RAM, GPU Mali 400, un puerto USB 2.0, un mini USB y una ranura para MicroSD. La salida de vídeo es HDMI e incluye receptor wifi interno.

La taiwanesa VIA Technologies también ha sacado una placa estilo Raspberry Pi por 49$. Montra un procesador ARM de 800 MHz, 512 megas de RAM DDR3, 2 gigas de memoria flash interna ampliable mediante tarjetas micro SD, salida de video VGA y HDMI, entrada y salida de audio por jack de 3.5», cuatro USB 2.0, Ethernet LAN RJ-45… En principio viene con Android 2.3 y lo que no está claro es cómo será de abierto el sistema, si permitirá actualizar el Android libremente o habrá que esperar a que lo haga el fabricante, o si se podrán instalar versiones de Linux para ARM.

Finalmente la TXI Cotton Candy presenta un precio más caro (alrededor de los 199$) acompañado de un diseño atractivo, similar a un pen USB de 8 centímetros, con un conector USB macho que permite conectarlo a televisores, monitores o equipos que tengan puerto USB sin necesidad de cable HDMI. En todo casi también incorpora salida de vídeo HDMI para dispositivos sin USB, puerto micro USB, posiblidad de ampliar la memoria interna con tarjeta MicroSD hasta 64 gigas, 1 giga de RAM, GPU Mali-400 y procesador ARM de 1,2 GHz. Además permite conectividad wifi y bluetooth. Trabaja con SO Android y Ubuntu, e incluye un cliente de vitualización para Windows, MacOS y Linux.

¿Utilidad? Son equipos baratos y muy portátiles. Para mucha gente que sólo necesita el ordenador para surfear por la web pueden ser sobradamente potentes, y para otra pueden funcionar como un equipo de repuesto.

Instalar Ubuntu 12.04 Precise Pangolin en Virtual Box

Si eres usuario de Virtual Box y has intentado instalar la versión i386 de Ubuntu 12.04 (o en mi caso Kubuntu) en una máquina virtual te habrás encontrado con que «El kernel necesita algunas características que no están presentes en el CPU: pae…» ¿cómo solucionar esto? (tranquilo no va a ser una entrada larga):

En la versión de 32 bits de Ubuntu 12.04 se usa por defecto el Kernel PAE, por eso te está puteando la máquina vritual, porque en Virtual Box viene deshabilitado en principio. Ya puedes respirar tranquilo, se soluciona, y sin ningún problema: Te vas a la configuración de la máquina virtual, al apartado Sistema. Allí seleccionas la pestaña Procesador y en ella buscas el checkbox llamado Características Extendidas y marcas Habilitar PAE/NX.

Sólo con esto ya debería funcionar, así que ahora disfruta de tus pruebas con Linux, trasteando con máquinas virtuales.