Firma electrónica reconocida

Firma electrónica reconocida

Definición

En el artículo Sistemas actuales de autenticación y firma se describen los tipos de firma electrónica existentes. El tipo de firma electrónica que se puede implementar utilizando el DNIe es la firma electrónica reconocida. Es el tipo de firma que permite identificar al firmante y detectar cualquier cambio posterior en los datos firmados. Está vinculada al firmante de manera única y a los datos a que se refiere.

Debe haber sido creada a través de medios que el firmante puede mantener bajo su exclusivo control, estar basada en un certificado reconocido y generada mediante un dispositivo seguro de creación de firma, lo que le atribuye el mismo valor legal que la firma manuscrita.

Una firma electrónica realizada con el certificado correspondiente del DNIe cumple todas las características descritas de la firma reconocida.

Desarrollo basado en la firma electrónica

Se puede realizar una firma electrónica reconocida con el DNI electrónico haciendo uso de las diferentes librerías desarrolladas para los distintos sistemas operativos que existen. Estas son la CryptoAPI y la librería PKCS#11.

CryptoAPI con C++

Una de las librerías que se puede utilizar en el sistema operativo Microsoft Windows es la CryptoAPI. Esta librería proporciona todas las funciones necesarias para interactuar con el DNI electrónico y realizar una firma. Estos son los pasos a seguir:

  • CryptAcquireContext

    Esta función devuelve el manipulador al contenedor de la tarjeta.

    /* Declaración del manejador al CSP*/
    HCRYPTPROV hCryptProv = NULL;

    /* Llamada de la función CryptAcquireContext, a la cual se le pasa por referencia el manejador anteriormente declarado,
    el nombre del contenedor, en este caso será el definido por defecto que se indica mediante el valor NULL,
    el nombre del proveedor, en este caso el DNIe,
    el tipo de proveedor,
    el valor del flag.
    */
    CryptAcquireContext(&hCryptProv, NULL, L"DNIeCSP", PROV_RSA_FULL, 0);

  • CryptCreateHash

    El siguiente paso es crear un objeto de resumen.

    /* Declaración del objeto resumen*/
    HCRYPTHASH hHash;

    /* La llamada a la función CryptCreateHash con el segundo parámetro igualado a CALG_SHA1 nos retornará
    un objeto resumen preparado para aplicar la función SHA.
    */
    CryptCreateHash(hCryptProv, CALG_SHA1, 0, 0, &hHash);

  • CryptHashData

    Con la función CryptHashData, se añaden los datos a resumir en el objeto de resumen.

    /* Datos y longitud de los datos a resumir y firmar */
    BYTE *pbBuffer = (BYTE *)"Los datos que tienen que ser resumidos y firmados.";
    DWORD dwBufferLen = strlen((char *)pbBuffer)+1;

    /*Llamada a la función CryptHashData para resumir los datos con la función resumen indicada en el paso anterior. En este ejemplo, la función SHA */
    CryptHashData(hHash, pbBuffer, dwBufferLen, 0);

  • CryptGetHashParam

    Con una llamada a esta función se obtiene el tamaño del valor del resumen de los datos obtenidos en el paso anterior.

    DWORD pdwDataLen = 0;

    /* La primera llamada a esta función, con el parámetro dwParam con el valor HP_HASHVAL, retorna el tamaño que ocupa el resumen de los datos */
    CryptGetHashParam(hHash, HP_HASHVAL, NULL, &pdwDataLen, 0);

  • CryptGetHashParam

    Con una segunda llamada a esta función, se obtiene el valor del resumen de los datos

    BYTE *pbData = (BYTE *)malloc(pdwDataLen * sizeof(BYTE *));

    /* La sengunda llamada a esta función, con el parámetro dwParam con el valor HP_HASHVAL, retorna el valor del resumen de los datos */
    CryptGetHashParam(hHash, HP_HASHVAL, pbData, pdwDataLen, 0);

  • CryptSignHash

    Con una llamada a esta función se obtiene el tamaño del valor de la firma a calcular.

    DWORD dwSigLen = 0;

    /* La primera llamada a esta función, con el parámetro dwKeySpec con el valor AT_SIGNATURE, retorna el tamaño que ocupa la firma de los datos resumidos */
    CryptSignHash(hHash, AT_SIGNATURE, NULL, NULL, NULL, &dwSigLen);

  • CryptSignHash

    Con una segunda llamada se obtiene la firma calculada.

    BYTE *pbSignature = (BYTE *)malloc(dwSigLen * sizeof(BYTE *));

    /* La segunda llamada a esta función, con el parámetro dwKeySpec con el valor AT_SIGNATURE, retorna el valor de la firma calculada */
    CryptSignHash(hHash, AT_SIGNATURE, NULL, NULL, pbSignature, &dwSigLen);

  • CryptDestroyHash

    Se debe liberar el espacio de memoria relativo al objeto de resumen.

    CryptDestroyHash(hHash);

  • CryptReleaseContext

    El espacio de memoria ocupado por el manipulador del contenedor, también debe ser liberado.

    CryptReleaseContext(hCryptProv, 0);

CSP y PKCS#11 con Java

Otra opción para realizar una firma electrónica con las claves asociadas al certificado de firma del DNIe, es el uso de Java y las diferentes clases que implementa para ello. Se puede utilizar tanto el CSP como el PKCS#11 del DNIe. El código generado con las librerías del CSP sólo se podrá ejecutar en entornos Microsoft Windows. Si se utiliza la librería PKCS#11, el código generado podrá ejecutarse cualquier plataforma (Microsoft Windows, GNU/Linux o Mac OS X).

A continuación, se muestra un ejemplo de cómo cargar el contenedor de claves en Java y con la librería CSP.

/* Instancia de la clase KeyStore con el contenedor Personal de Internet Explorer con el fin de listar los certificados */
KeyStore keyStore = KeyStore.getInstance("Windows-MY");
keyStore.load(null,null);

/* Listado de los alias de los certificados del contenedor de Internet Explorer */
Enumeration elist = keyStore.aliases();

/* Se distingue el certificado de firma del DNIe gracias a la cadena de caracteres FIRMA
que contiene */
StringBuffer sb = new StringBuffer("FIRMA");

/* Búsqueda del certificado de firma del DNIe */
while (elist.hasMoreElements()){
String certAlias = elist.nextElement();

if(certAlias.contains(sb)) {
/* Código de firma */ }
}

El siguiente ejemplo explica cómo cargar el contenedor de claves en Java y con la librería PKCS#11 para todas las plataformas posibles.

/* Se obtiene el nombre del sistema operativo del usuario para la carga del módulo PKCS#11. */
String osName = System.getProperty("os.name");

String pkcs11config = "";

if(osName.contains(new StringBuffer("Linux")))
pkcs11config = "name = DNIe\nlibrary = /usr/lib/opensc-pkcs11.so\n";
else if(osName.contains(new StringBuffer("Mac")))
pkcs11config = "name = DNIe\nlibrary = /Library/OpenSC/lib/opensc-pkcs11.so\n";
else if(osName.contains(new StringBuffer("Windows")))
pkcs11config = "name = DNIe\nlibrary = c:/WINDOWS/system32/UsrPkcs11.dll\n";

/* Se obtiene el proveedor del contenedor de claves */
sun.security.pkcs11.SunPKCS11 sunpkcs11 = new sun.security.pkcs11.SunPKCS11(new ByteArrayInputStream(pkcs11config.getBytes()));
Security.addProvider(sunpkcs11);

/* Instancia de la clase KeyStore cargando el módulo PKCS#11 para cargar exclusivamente los certificados y sus claves que hay en el DNIe */
KeyStore keyStore = KeyStore.getInstance("PKCS11", sunpkcs11);
keyStore.load(null, password);

/* Listado de los alias de los certificados del contenedor de Internet Explorer */
Enumeration elist = keyStore.aliases();

/* Se distingue el certificado de firma del DNIe gracias a la cadena de carácteres FIRMA
que contiene */
StringBuffer sb = new StringBuffer("FIRMA");

/* Búsqueda del certificado de firma del DNIe */
while (elist.hasMoreElements()){
String certAlias = elist.nextElement();

if(certAlias.contains(sb)) {
/* Código de firma */ }
}

La parte encargada de realizar el resumen de los datos y firmarlos, es igual para las dos implementaciones explicadas:

/* Se obtiene la clave privada necesaria para la firma de los datos */
PrivateKey privKey = (PrivateKey) keyStore.getKey(certAlias, password);

/* Se calcula el resumen de los datos a firmar */
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(data);
byte[] hash = md.digest();

/* Se carga el proveedor del contenedor de claves */
Provider p = keyStore.getProvider();

/* Se instancia el objeto de firma y se realiza la firma */
java.security.Signature jsig = java.security.Signature.getInstance("SHA1withRSA", p);
jsig.initSign(privKey);
jsig.update(hash);

/* Firma de los datos */
byte[] sigval = jsig.sign();