Implementacion De Firmas Digitales en Java

WARNING
Jim Manico recently brought to my attention this wiki page, the same who asked me to check exactly. In doing so, I noticed several errors and areas of weakness. Lately, I have the intention to revisit this page (hopefully with the help of one of the original owners), but I do not have time to do a full review at this time. Therefore, I will summarize the problems observed on this page and leaving you the decision to use or not, with these warnings.


 * 1) First, this page does not describe "digital signatures". Rather, it describes a concept known as "digital envelopes", which is a scheme used with objects such as S / MIME. Digital signatures not only encrypt the actual message text, only encrypts the hash of the message text.
 * 2) UTF-8 should be used to convert between Java byte strings and arrays to ensure adequate portability across different operating systems.
 * 3) The certificate chain must always be validated. In this example, the certificate is self-signed, so this is not relevant and will not be true in the normal case. Furthermore, it should be noted that the self-signed, but acceptable for demonstration purposes certificates is considered a dubious practice for production, as it opens the door to spoofing attacks.
 * 4) NIST now recommends using key size 2048 bits for RSA or DSA keys.
 * 5) There is a consistent use of weak algorithms. Here are some suggested replacements:
 * 6) Use "RSA/ECB/OAEPWithSHA1AndMGF1Padding" instead of "RSA/ECB/PKCS1Padding".
 * 7) Use SHA1 (or better SHA256, SHA1 but at least) instead of MD5 for message digest.
 * 8) Use "SHA1withRSA" for the signature algorithm instead of "MD5withRSA".
 * 9) When creating a symmetric encryption system to encrypt the message text, use "AES / CBC / PKCS5Padding" and choose an IV random for each text message instead of simply using "AES", ending with "AES / ECB / PKCS5Padding ". ECB mode is extremely weak for a regular plain text. (OK for random bit encryption, however, is well used with RSA.) However, the use CBC and PKCS5Padding could make it vulnerable to "Padding Oracle" attacks, so caution is advised. You can use Encryptor 2.0 ESAPI 's to avoid it. (Note also, this part is the concept of "on digital". If this page was really limited by "digital signatures" would not apply because it would be irrelevant.)

I hope to get soonest, before cleaning this wiki page. Meanwhile, email me your questions.

-Kevin Wall

Visión de conjunto
Este artículo presenta un breve resumen de los conceptos involucrados con firmas digitales y proporciona ejemplos de código para la aplicación de firmas digitales en Java utilizando la Arquitectura de Java Cryptography.

¿Qué es una firma digital?
Una firma digital es un concepto que ayuda a obtener el no repudio de origen (es decir, la Integridad del Origen) de datos. Al firmar digitalmente el documento, la persona que firma, asegura que él es el autor del documento o el mensaje firmado.

Necesidad de Firma Digital
Durante la "E" revolución, fue una necesidad para la autenticación de críticas transacciones sobre todo en el mundo financiero. Si Alice se comprometió a transferir $ x para Bob, entonces tenía que haber una manera para que Bob se asegure de que:


 * Fue Alice quien realizó la transacción y no otra persona suplantando Alice (Autenticación).
 * El monto acordado por Alice es $x (Integridad).
 * Alicia no pudo discutir su declaración de transacción $x a Bob (No repudio de origen).

Estas preocupaciones fueron tratadas con una solución conocida como firmas digitales. Más información de fondo sobre las firmas digitales se puede encontrar en Wikipedia article

Firmas digitales en Java utilizando JCA
La Arquitectura de Java Cryptography es un marco para el acceso y el desarrollo de la funcionalidad criptográfica para la plataforma Java. Un proveedor de JCA implementa las funcionalidades criptográficas como firmas digitales y compendios de mensajes. El proveedor predeterminado JCA en JDK 1.4.2 es SUN.

Consideraciones de seguridad al implementar la firma digital
Dos consideraciones principales de seguridad que se deben tener en cuenta al aplicar firmas digitales son:


 * 1) Firma el mensaje y, a continuación cifrar el mensaje firmado.
 * 2) Firma el hash del mensaje en lugar del mensaje completo.

Consideraciones sobre el rendimiento al implementar la firma digital
Dado que los algoritmos de cifrado asimétricos, como RSA, DSA son computacionalmente más lento que los algoritmos de cifrado simétricos como AES, es una buena práctica, cifrar el mensaje real que se transmite utilizando un algoritmo de clave simétrica y luego cifrar la clave que se utiliza en el algoritmo de clave simétrica utilizando un algoritmo de clave asimétrica. Por ejemplo: si se quiere transmitir el mensaje "Hola Mundo de Firmas Digitales", entonces primero se cifra este mensaje con una clave simétrica, por ejemplo una clave AES de 128 bits como x7oFaHSPnWxEMiZE/0qYrg y luego se cifra esta clave con un algoritmo de clave asimétrica como RSA.

Algoritmo para implementar la firma digital utilizando el algoritmo RSA
El proveedor de la implementación en Java RSA tiene una limitación en que el cifrado se puede realizar sólo en los datos de longitud <= 117 bytes. Si los datos son de longitud > 117 bytes, se lanzaría un IllegalBlockSizeException: Los datos no debe tener más de 117 bytes ahí la simetría tiene que ser cifrados y luego firmados.

El algoritmo RSA PKCS # 1 con relleno sólo puede cifrar los datos de tamaño k - 11 1, donde k cuya longitud es de un octeto del módulo RSA y 11 es la cantidad de bytes utilizados por el relleno PCKS # 1 v1.5. Por lo tanto, si usamos una clave RSA de tamaño de 1024 bits, podríamos cifrar sólo 128 - 11 => 117 bytes de datos. Hay dos opciones disponibles para cifrar los datos de tamaño de byte más grande.


 * 1) Podríamos usar una clave RSA de longitud> 1024. Por ejemplo, si usamos 2048 bits, entonces podríamos cifrar 256-11 => 245 bytes de datos. La desventaja de este enfoque es que no sería capaz de codificar los datos de tamaño> x bytes (en el ejemplo anterior x es 245).
 * 2) Analizar los datos de entrada en trozos de bytes de tamaño <117 y aplicar la encriptación en cada trozo. El código de ejemplo de este enfoque se puede encontrar aquí here.

Ambos enfoques podrían afectar al rendimiento ya que la clave RSA de mayor tamaño o un enfoque de "divide y vencerás" de bytes de entrada son computacionalmente costosa.

Algoritmo
Con las anteriores consideraciones, el algoritmo siguiente puede ser usado para la implementación de la criptografía de clave pública en Java.


 * 1) Cifrar el mensaje utilizando una clave simétrica
 * 2) Concatenar la clave simétrica + hash de clave simétrica + hash del mensaje.
 * 3) Cifrar la cadena concatenada con la clave pública de los receptores.
 * 4) Firmar los datos a transmitir (clave simétrica cifrada + Hash de la tecla + Hash del mensaje).
 * 5) Validar la firma.
 * 6) Descifrar el mensaje con la clave privada del receptor para obtener la clave simétrica.
 * 7) Validar la integridad de la clave utilizando el hash de la clave.
 * 8) Descifrar el mensaje real usando la clave simétrica que se ha descifrado, se analiza y se comprueba la integridad.
 * 9) Calcular MessageDigest de datos.
 * 10) Validar si la síntesis del mensaje del texto descifrado coincide con el resumen de mensaje del mensaje original.

Comandos para la generación de claves
prompt# keytool -genkey -alias testsender -keystore testkeystore.ks -keyalg RSA Enter keystore password: testpwd What is your first and last name?

[Unknown]: Alice Sender

¿Cuál es su nombre y apellido?

[Unknown]: IT

¿Cuál es el nombre de la unidad organizativa?

[Unknown]: ABC Inc

¿Cuál es el nombre de su organización?

[Unknown]: LA

¿Cuál es el nombre de su ciudad o localidad?

[Unknown]: CA

¿Cuál es el nombre de su estado o provincia?

[Unknown]: US

Es CN = Alice Sender, OU = IT, O = ABC Inc, L = LA, ST = CA, C = EE.UU. correcto?

[no]: y

Introduzca la contraseña clave para &lt;testsender&gt;

(RETURN if same as keystore password): send123

prompt # keytool -genkey -alias testrecv -keystore testkeystore.ks -keyalg RSA Introduzca la contraseña del almacén de claves: testpwd ¿Cuál es su nombre y apellido?

[Unknown]: Bob Receiver

¿Cuál es el nombre de la unidad organizativa?

[Unknown]: HR

¿Cuál es el nombre de su organización?

[Unknown]: ABC Inc

¿Cuál es el nombre de su ciudad o localidad?

[Unknown]: SFO

¿Cuál es el nombre de su estado o provincia?

[Unknown]: CA

¿Qué es el código de país de dos letras para esta unidad?

[Unknown]: US

Es CN = Bob receptor, OU = HR, O = ABC Inc, L = SFO, ST = CA, C = EE.UU. correcto?

[no]: y

Introduzca la contraseña clave para &lt;testrecv&gt;

(RETURN if same as keystore password): recv123

PublicKeyCryptography.java
package org.owasp.crypto;

import java.security.*; import java.security.cert.*; import javax.crypto.*; import sun.misc.BASE64Encoder; import sun.misc.BASE64Decoder;

/** * * @author Joe Prasanna Kumar * * 1. Cifrar los datos utilizando una clave simétrica * 2. Cifrar la clave simétrica con la clave pública Receptores * 3. Crear un resumen del mensaje de los datos a transmitir * 4. Firma el mensaje a transmitir * 5. Envíe los datos a través de un canal no seguro * 6. Validar la firma * 7. Descifrar el mensaje usando la llave privada Pets para obtener la clave simétrica * 8. Descifrar los datos usando la clave simétrica * 9. Calcule MessageDigest de datos + mensaje firmado * 10.Valide si la síntesis del mensaje del texto descifrado coincide con el resumen de mensaje del mensaje original * */

public class PublicKeyCryptography {

/**	 * @param args */	public static void main(String[] args) { SymmetricEncrypt encryptUtil = new SymmetricEncrypt; String strDataToEncrypt = "Hello World"; byte[] byteDataToTransmit = strDataToEncrypt.getBytes;

// Generación de un SecretKey para el cifrado simétrico SecretKey senderSecretKey = SymmetricEncrypt.getSecret; //1. Cifrar los datos utilizando una clave simétrica byte[] byteCipherText = encryptUtil.encryptData(byteDataToTransmit,senderSecretKey,"AES"); String strCipherText = new BASE64Encoder.encode(byteCipherText); //2. Cifrar la clave simétrica con la clave pública try{ // 2.1 Especifique el almacén de claves que se haya importado el certificado Receptores KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType); char [] password = "testpwd".toCharArray; java.io.FileInputStream fis = new java.io.FileInputStream("/home/Joebi/workspace/OWASP_Crypto/org/owasp/crypto/testkeystore.ks"); ks.load(fis, password); fis.close; // 2.2 Creación de un certificado X509 del receptor X509Certificate recvcert ; MessageDigest md = MessageDigest.getInstance("MD5"); recvcert = (X509Certificate)ks.getCertificate("testrecv"); // 2.3 Obtención de la llave pública de los certificados PublicKey pubKeyReceiver = recvcert.getPublicKey; // 2.4 Cifrado de la SecretKey con la clave pública Receptores byte[] byteEncryptWithPublicKey = encryptUtil.encryptData(senderSecretKey.getEncoded,pubKeyReceiver,"RSA/ECB/PKCS1Padding"); String strSenbyteEncryptWithPublicKey = new BASE64Encoder.encode(byteEncryptWithPublicKey); // 3. Crear un resumen del mensaje de los datos a transmitir md.update(byteDataToTransmit); byte byteMDofDataToTransmit[] = md.digest; String strMDofDataToTransmit = new String; for (int i = 0; i &lt; byteMDofDataToTransmit.length; i++){ strMDofDataToTransmit = strMDofDataToTransmit + Integer.toHexString((int)byteMDofDataToTransmit[i] &amp; 0xFF) ; }   // 3.1 Mensaje que se Firmado = clave secreta cifrada + MAC de los datos a transmitir String strMsgToSign = strSenbyteEncryptWithPublicKey + "|" + strMDofDataToTransmit; // 4. Firma el mensaje // 4.1 Obtener la clave privada del remitente desde el almacén de claves, proporcionando la contraseña establecida para la llave privada mientras se crea las llaves usados. char[] keypassword = "send123".toCharArray; Key myKey = ks.getKey("testsender", keypassword); PrivateKey myPrivateKey = (PrivateKey)myKey; // 4.2 Firmar el mensaje Signature mySign = Signature.getInstance("MD5withRSA"); mySign.initSign(myPrivateKey); mySign.update(strMsgToSign.getBytes); byte[] byteSignedData = mySign.sign; // 5. Los valores byteSignedData (la firma) y strMsgToSign (los datos que se firmó) se pueden enviar a través del receptor // 6. Validar la firma // 6.1 Extraer la clave pública de su certificado de remitentes X509Certificate sendercert ; sendercert = (X509Certificate)ks.getCertificate("testsender"); PublicKey pubKeySender = sendercert.getPublicKey; // 6.2 Verificar la Firma Signature myVerifySign = Signature.getInstance("MD5withRSA"); myVerifySign.initVerify(pubKeySender); myVerifySign.update(strMsgToSign.getBytes); boolean verifySign = myVerifySign.verify(byteSignedData); if (verifySign == false) {   	System.out.println(" Error in validating Signature "); }   else System.out.println(" Successfully validated Signature ");

// 7. Descifrar el mensaje usando la llave privada Pets para obtener la clave simétrica char[] recvpassword = "recv123".toCharArray; Key recvKey = ks.getKey("testrecv", recvpassword); PrivateKey recvPrivateKey = (PrivateKey)recvKey; // Analizar el MessageDigest y el valor cifrado String strRecvSignedData = new String (byteSignedData); String[] strRecvSignedDataArray = new String [10]; strRecvSignedDataArray = strMsgToSign.split("|"); int intindexofsep = strMsgToSign.indexOf("|"); String strEncryptWithPublicKey = strMsgToSign.substring(0,intindexofsep); String strHashOfData = strMsgToSign.substring(intindexofsep+1);

// Descifrado para obtener la clave simétrica byte[] bytestrEncryptWithPublicKey = new BASE64Decoder.decodeBuffer(strEncryptWithPublicKey); byte[] byteDecryptWithPrivateKey = encryptUtil.decryptData(byteEncryptWithPublicKey,recvPrivateKey,"RSA/ECB/PKCS1Padding"); // 8. Descifrar los datos usando la clave simétrica javax.crypto.spec.SecretKeySpec secretKeySpecDecrypted = new javax.crypto.spec.SecretKeySpec(byteDecryptWithPrivateKey,"AES"); byte[] byteDecryptText = encryptUtil.decryptData(byteCipherText,secretKeySpecDecrypted,"AES"); String strDecryptedText = new String(byteDecryptText); System.out.println(" Decrypted data is " +strDecryptedText); // 9. Calcule MessageDigest de datos + mensaje firmado MessageDigest recvmd = MessageDigest.getInstance("MD5"); recvmd.update(byteDecryptText); byte byteHashOfRecvSignedData[] = recvmd.digest;

String strHashOfRecvSignedData = new String; for (int i = 0; i &lt; byteHashOfRecvSignedData.length; i++){ strHashOfRecvSignedData = strHashOfRecvSignedData + Integer.toHexString((int)byteHashOfRecvSignedData[i] &amp; 0xFF) ; }	// 10. Validar si la síntesis del mensaje del texto coincide con el mensaje descifrado Digest of the Original Message

if (!strHashOfRecvSignedData.equals(strHashOfData)) {		System.out.println(" Message has been tampered "); }	}	catch(Exception exp) {		System.out.println(" Exception caught " + exp); exp.printStackTrace; }	}

}

SymmetricEncrypt.java
package org.owasp.crypto;

import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.Cipher; import java.security.Key;

import java.security.NoSuchAlgorithmException; import java.security.InvalidKeyException; import java.security.InvalidAlgorithmParameterException; import javax.crypto.NoSuchPaddingException; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException;

import sun.misc.BASE64Encoder;

/** * @author Joe Prasanna Kumar * Este programa ofrece las funcionalidades criptográficas siguientes * 1. Cifrado con AES * 2. El descifrado con AES * * Algoritmo de alto nivel: * 1. Generar una clave DES (especificar el tamaño de la clave durante esta fase) * 2. Cree el Cipher * 3. Para cifrar: Inicializar el cifrado para el cifrado * 4. Para descifrar: Inicializar el cifrado para descifrar * *  */

public class SymmetricEncrypt { String strDataToEncrypt = new String; String strCipherText = new String; String strDecryptedText = new String; static KeyGenerator keyGen; private static String strHexVal = "0123456789abcdef";

public static SecretKey getSecret{ /**		 * Paso 1. Generación de una clave AES mediante keygenerator * 		Inicializa el tamaño de clave de 128 * 		 */			try{ keyGen = KeyGenerator.getInstance("AES"); keyGen.init(128);

}			catch(Exception exp) {				System.out.println(" Exception inside constructor " +exp); }			SecretKey secretKey = keyGen.generateKey; return secretKey; }		/**		 * Paso 2. Crear un sistema de cifrado mediante la especificación de los siguientes parámetros * 			a. Nombre del algoritmo - aquí es AES */		public byte[] encryptData(byte[] byteDataToEncrypt, Key secretKey, String Algorithm) { byte[] byteCipherText = new byte[200]; try { Cipher aesCipher = Cipher.getInstance(Algorithm); /**		 * Paso 3. Inicialice el cifrado para el cifrado */			if(Algorithm.equals("AES")){ aesCipher.init(Cipher.ENCRYPT_MODE,secretKey,aesCipher.getParameters); }				else if(Algorithm.equals("RSA/ECB/PKCS1Padding")){ aesCipher.init(Cipher.ENCRYPT_MODE,secretKey); } 		/**		 * Paso 4. Cifrar los datos * 		1. Declarar / inicializar los datos. Aquí los datos son de tipo String * 		2. Convertir el texto de entrada a Bytes * 		3. Cifrado de los bytes, utilizando el método doFinal */		byteCipherText = aesCipher.doFinal(byteDataToEncrypt); strCipherText = new BASE64Encoder.encode(byteCipherText);

}			catch (NoSuchAlgorithmException noSuchAlgo) {				System.out.println(" No Such Algorithm exists " + noSuchAlgo); }				catch (NoSuchPaddingException noSuchPad) {					System.out.println(" No Such Padding exists " + noSuchPad); }					catch (InvalidKeyException invalidKey) {						System.out.println(" Invalid Key " + invalidKey); }					catch (BadPaddingException badPadding) {						System.out.println(" Bad Padding " + badPadding); }					catch (IllegalBlockSizeException illegalBlockSize) {						System.out.println(" Illegal Block Size " + illegalBlockSize); illegalBlockSize.printStackTrace; }					catch (Exception exp) {						exp.printStackTrace; }		return byteCipherText; }		/**		 * Paso 5. Descifrar los datos * 		1. Inicialice el cifrado para descifrar * 		2. Descifrar los bytes cifrados utilizando el método doFinal */		public byte[] decryptData(byte[] byteCipherText, Key secretKey, String Algorithm) { byte[] byteDecryptedText = new byte[200]; try{ Cipher aesCipher = Cipher.getInstance(Algorithm); if(Algorithm.equals("AES")){ aesCipher.init(Cipher.DECRYPT_MODE,secretKey,aesCipher.getParameters); }		else if(Algorithm.equals("RSA/ECB/PKCS1Padding")){ aesCipher.init(Cipher.DECRYPT_MODE,secretKey); } 		byteDecryptedText = aesCipher.doFinal(byteCipherText); strDecryptedText = new String(byteDecryptedText); }		catch (NoSuchAlgorithmException noSuchAlgo) {			System.out.println(" No Such Algorithm exists " + noSuchAlgo); }			catch (NoSuchPaddingException noSuchPad) {				System.out.println(" No Such Padding exists " + noSuchPad); }				catch (InvalidKeyException invalidKey) {					System.out.println(" Invalid Key " + invalidKey); invalidKey.printStackTrace; }				catch (BadPaddingException badPadding) {					System.out.println(" Bad Padding " + badPadding); badPadding.printStackTrace; }				catch (IllegalBlockSizeException illegalBlockSize) {					System.out.println(" Illegal Block Size " + illegalBlockSize); illegalBlockSize.printStackTrace; }				catch (InvalidAlgorithmParameterException invalidParam) {					System.out.println(" Invalid Parameter " + invalidParam); }		return byteDecryptedText; }		public static byte[] convertStringToByteArray(String strInput) { strInput = strInput.toLowerCase; byte[] byteConverted = new byte[(strInput.length + 1) / 2]; int j = 0; int interimVal; int nibble = -1;

for (int i = 0; i &lt; strInput.length; ++i) { interimVal = strHexVal.indexOf(strInput.charAt(i)); if (interimVal &gt;= 0) { if (nibble &lt; 0) { nibble = interimVal; } else { byteConverted[j++] = (byte) ((nibble &lt;&lt; 4) + interimVal); nibble = -1; }				}			}

if (nibble &gt;= 0) { byteConverted[j++] = (byte) (nibble &lt;&lt; 4); }

if (j &lt; byteConverted.length) { byte[] byteTemp = new byte[j]; System.arraycopy(byteConverted, 0, byteTemp, 0, j); byteConverted = byteTemp; }

return byteConverted; }		public static String convertByteArrayToString(byte[] block) { StringBuffer buf = new StringBuffer;

for (int i = 0; i &lt; block.length; ++i) { buf.append(strHexVal.charAt((block[i] &gt;&gt;&gt; 4) &amp; 0xf)); buf.append(strHexVal.charAt(block[i] &amp; 0xf)); }

return buf.toString; } }

Referencias

 * 1) Computer Security Arts and Science – Matt Bishop
 * 2) Core Security Patterns – Christopher Steele, Ray Lai and Ramesh Nagappan

Aplicaciones en Ambientes Libres

Tutor: Ing. Tito Armas

Traductoroas: Bravo Maria Jose y Portilla Maria Fernanda 2012