使用SHA1和RSA与java.security.Signature与MessageDigest和Cipher

我想了解什么是Java java.security.Signature类。 如果我计算SHA1消息摘要,然后使用RSA对摘要进行encryption,则会得到不同的结果来要求Signature类签署相同的事情:

// 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)); 

结果(例如):

input数据:这是正在签名的消息
摘要:62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
密码文本:057dc0d2f7f54acc95d3cf5cba9f944619394711003bdd12 …
签名:7177c74bbbb871cc0af92e30d2808ebae146f25d3fd8ba1622 …

我必须对Signature正在做什么有一个基本的误解 – 我已经完成了它,并且似乎是在MessageDigest对象上调用update,并将algorithm设置为SHA1,然后获取摘要,然后执行encryption。 什么使得结果有所不同?

编辑:

列奥尼达斯让我检查签名scheme是否应该做我认为的做法。 在RFC中定义了两种types的签名:

  • RSASSA-PKCS1-v1_5中
  • RSASSA-PSS

其中第一个 (PKCS1)就是我上面描述的那个。 它使用散列函数创build一个摘要,然后用私钥encryption结果。

第二种algorithm使用随机盐值,并且更安全但不确定。 如果重复使用相同的密钥,从上面的代码产生的签名不会改变,所以我不认为它可以是PSS。

编辑:

这里是我使用的bytes2string方法:

 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(); } 

好的,我已经弄清楚发生了什么。 我当时很蠢 Leonidas是对的,它不仅仅是被encryption的散列,它是与摘要连接的散列algorithm的ID:

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

这就是为什么他们不同。

为了产生相同的结果:

 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); 

bytes2String方法的稍微更有效的版本是

 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(); } 

嗯,理解你的问题后:你确定签名方法只创build一个SHA1并encryption它? GPG等提供压缩/清除数据签名。 也许这个java签名alg也创build一个可分离/可附签名。

以@Mike Houston的答案作为指针,这里是一个完整的示例代码,签名和散列和encryption。

 /** * @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(); } } 

您可以使用BouncyCastle提供程序或默认的Sun提供程序。

我有一个类似的问题,我testing了添加代码,发现一些有趣的结果。 通过我添加的这段代码,我可以推断出,根据所使用的“供应商”,公司可以有所不同? (因为包含在encryption中的数据在所有提供者中并不总是相等的)。

我的testing结果。

结论 – 签名解密= ???(垃圾)+摘要信息(如果我们知道“垃圾”的值,数字签名将是相等的)

IDE Eclipse输出…

input数据:这是正在签名的消息

摘要:62b0a9ef15461c82766fb5bdaae9edbe4ac2e067

DigestInfo:3021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067

签名破译:1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067

 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)); }