Usando SHA1 y RSA con java.security.Signature vs. MessageDigest and Cipher

Estoy tratando de entender lo que hace la clase Java java.security.Signature . Si calculo un resumen de mensaje SHA1 y luego encripto ese resumen usando RSA, obtengo un resultado diferente a pedirle a la clase Signature que firme lo mismo:

// Generate new key KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); String plaintext = "This is the message being signed"; // Compute signature Signature instance = Signature.getInstance("SHA1withRSA"); instance.initSign(privateKey); instance.update((plaintext).getBytes()); byte[] signature = instance.sign(); // Compute digest MessageDigest sha1 = MessageDigest.getInstance("SHA1"); byte[] digest = sha1.digest((plaintext).getBytes()); // Encrypt digest Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] cipherText = cipher.doFinal(digest); // Display results System.out.println("Input data: " + plaintext); System.out.println("Digest: " + bytes2String(digest)); System.out.println("Cipher text: " + bytes2String(cipherText)); System.out.println("Signature: " + bytes2String(signature)); 

Resultados en (por ejemplo):

Datos de entrada: este es el mensaje que se está firmando
Resumen: 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
Texto de cifrado: 057dc0d2f7f54acc95d3cf5cba9f944619394711003bdd12 …
Firma: 7177c74bbbb871cc0af92e30d2808ebae146f25d3fd8ba1622 …

Debo tener un malentendido fundamental de lo que Signature está haciendo: lo he rastreado, y parece estar llamando a update en un objeto MessageDigest , con el algoritmo establecido en SHA1 como era de esperar, luego obtengo el compendio, luego hago el cifrado. ¿Qué hace que los resultados difieran?

EDITAR:

Leonidas me hizo ver si se supone que el esquema de la firma hace lo que creo que hace. Hay dos tipos de firma definidos en el RFC :

  • RSASSA-PKCS1-v1_5
  • RSASSA-PSS

El primero de estos (PKCS1) es el que describo arriba. Utiliza una función hash para crear un resumen, y luego cifra el resultado con una clave privada.

El segundo algoritmo usa un valor de sal aleatorio, y es más seguro pero no determinista. La firma producida a partir del código anterior no cambia si la misma clave se usa repetidamente, por lo que no creo que pueda ser PSS.

EDITAR:

Aquí está el método bytes2string que estaba usando:

 private static String bytes2String(byte[] bytes) { StringBuilder string = new StringBuilder(); for (byte b : bytes) { String hexString = Integer.toHexString(0x00FF & b); string.append(hexString.length() == 1 ? "0" + hexString : hexString); } return string.toString(); } 

De acuerdo, he averiguado qué está pasando. Estaba siendo estúpido. Leonidas tiene razón, no es solo el hash el que se cifra, es la ID del algoritmo hash concatenado con el resumen:

  DigestInfo ::= SEQUENCE { digestAlgorithm AlgorithmIdentifier, digest OCTET STRING } 

Por eso son diferentes.

Para producir los mismos resultados:

 MessageDigest sha1 = MessageDigest.getInstance("SHA1", BOUNCY_CASTLE_PROVIDER); byte[] digest = sha1.digest(content); DERObjectIdentifier sha1oid_ = new DERObjectIdentifier("1.3.14.3.2.26"); AlgorithmIdentifier sha1aid_ = new AlgorithmIdentifier(sha1oid_, null); DigestInfo di = new DigestInfo(sha1aid_, digest); byte[] plainSig = di.getDEREncoded(); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", BOUNCY_CASTLE_PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] signature = cipher.doFinal(plainSig); 

Una versión un poco más eficiente del método bytes2String es

 private static final char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static String byteArray2Hex(byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.length * 2); for (final byte b : bytes) { sb.append(hex[(b & 0xF0) >> 4]); sb.append(hex[b & 0x0F]); } return sb.toString(); } 

Erm, después de entender su pregunta: ¿está seguro de que el método de firma solo crea un SHA1 y lo encripta? GPG et al ofrecen comprimir / borrar el signo de los datos. Tal vez este java-signature-alg también crea una firma desmontable / conectable.

Tomando la respuesta de @Mike Houston como puntero, aquí hay un código de muestra completo que hace Signature y Hash y encriptación.

 /** * @param args */ public static void main(String[] args) { try { boolean useBouncyCastleProvider = false; Provider provider = null; if (useBouncyCastleProvider) { provider = new BouncyCastleProvider(); Security.addProvider(provider); } String plainText = "This is a plain text!!"; // KeyPair KeyPairGenerator keyPairGenerator = null; if (null != provider) keyPairGenerator = KeyPairGenerator.getInstance("RSA", provider); else keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // Signature Signature signatureProvider = null; if (null != provider) signatureProvider = Signature.getInstance("SHA256WithRSA", provider); else signatureProvider = Signature.getInstance("SHA256WithRSA"); signatureProvider.initSign(keyPair.getPrivate()); signatureProvider.update(plainText.getBytes()); byte[] signature = signatureProvider.sign(); System.out.println("Signature Output : "); System.out.println("\t" + new String(Base64.encode(signature))); // Message Digest String hashingAlgorithm = "SHA-256"; MessageDigest messageDigestProvider = null; if (null != provider) messageDigestProvider = MessageDigest.getInstance(hashingAlgorithm, provider); else messageDigestProvider = MessageDigest.getInstance(hashingAlgorithm); messageDigestProvider.update(plainText.getBytes()); byte[] hash = messageDigestProvider.digest(); DigestAlgorithmIdentifierFinder hashAlgorithmFinder = new DefaultDigestAlgorithmIdentifierFinder(); AlgorithmIdentifier hashingAlgorithmIdentifier = hashAlgorithmFinder.find(hashingAlgorithm); DigestInfo digestInfo = new DigestInfo(hashingAlgorithmIdentifier, hash); byte[] hashToEncrypt = digestInfo.getEncoded(); // Crypto // You could also use "RSA/ECB/PKCS1Padding" for both the BC and SUN Providers. Cipher encCipher = null; if (null != provider) encCipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", provider); else encCipher = Cipher.getInstance("RSA"); encCipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); byte[] encrypted = encCipher.doFinal(hashToEncrypt); System.out.println("Hash and Encryption Output : "); System.out.println("\t" + new String(Base64.encode(encrypted))); } catch (Throwable e) { e.printStackTrace(); } } 

Puede usar el proveedor BouncyCastle o el proveedor Sun predeterminado.

Tengo un problema similar, probé la adición de código y encontré algunos resultados interesantes. Con este código que agrego, puedo deducir que dependiendo del “proveedor” que se use, la empresa puede ser diferente. (porque los datos incluidos en el cifrado no siempre son iguales en todos los proveedores).

Resultados de mi prueba.

Conclusión.- Descifrado de firma = ??? (papelera) + DigestInfo (si conocemos el valor de “papelera”, las firmas digitales serán iguales)

IDE Eclipse OUTPUT …

Datos de entrada: este es el mensaje que se está firmando

Resumen: 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067

DigestInfo: 3021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067

Descifrar la firma: 1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067

CÓDIGO

 import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import org.bouncycastle.asn1.x509.DigestInfo; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; public class prueba { /** * @param args * @throws NoSuchProviderException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws SignatureException * @throws NoSuchPaddingException * @throws BadPaddingException * @throws IllegalBlockSizeException */// public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { // TODO Auto-generated method stub KeyPair keyPair = KeyPairGenerator.getInstance("RSA","BC").generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); PublicKey puKey = keyPair.getPublic(); String plaintext = "This is the message being signed"; // Hacer la firma Signature instance = Signature.getInstance("SHA1withRSA","BC"); instance.initSign(privateKey); instance.update((plaintext).getBytes()); byte[] signature = instance.sign(); // En dos partes primero hago un Hash MessageDigest digest = MessageDigest.getInstance("SHA1", "BC"); byte[] hash = digest.digest((plaintext).getBytes()); // El digest es identico a openssl dgst -sha1 texto.txt //MessageDigest sha1 = MessageDigest.getInstance("SHA1","BC"); //byte[] digest = sha1.digest((plaintext).getBytes()); AlgorithmIdentifier digestAlgorithm = new AlgorithmIdentifier(new DERObjectIdentifier("1.3.14.3.2.26"), null); // create the digest info DigestInfo di = new DigestInfo(digestAlgorithm, hash); byte[] digestInfo = di.getDEREncoded(); //Luego cifro el hash Cipher cipher = Cipher.getInstance("RSA","BC"); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] cipherText = cipher.doFinal(digestInfo); //byte[] cipherText = cipher.doFinal(digest2); Cipher cipher2 = Cipher.getInstance("RSA","BC"); cipher2.init(Cipher.DECRYPT_MODE, puKey); byte[] cipherText2 = cipher2.doFinal(signature); System.out.println("Input data: " + plaintext); System.out.println("Digest: " + bytes2String(hash)); System.out.println("Signature: " + bytes2String(signature)); System.out.println("Signature2: " + bytes2String(cipherText)); System.out.println("DigestInfo: " + bytes2String(digestInfo)); System.out.println("Signature Decipher: " + bytes2String(cipherText2)); }