将 SHA1 和 RSA 与 java.security.Signature 与 MessageDigest 和 Cipher 一起使用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/521101/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using SHA1 and RSA with java.security.Signature vs. MessageDigest and Cipher
提问by Mike Houston
I'm trying to understand what the Java java.security.Signatureclass does. If I compute an SHA1 message digest, and then encrypt that digest using RSA, I get a different result to asking the Signatureclass to sign the same thing:
我试图了解 Java java.security.Signature类的作用。如果我计算一个 SHA1 消息摘要,然后使用 RSA 加密该摘要,我会得到与要求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));
Results in (for example):
结果(例如):
Input data: This is the message being signed
Digest: 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
Cipher text: 057dc0d2f7f54acc95d3cf5cba9f944619394711003bdd12...
Signature: 7177c74bbbb871cc0af92e30d2808ebae146f25d3fd8ba1622...
输入数据:这是所签署的消息
摘要:62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
密文:057dc0d2f7f54acc95d3cf5cba9f944619394711003bdd12 ...
签名:7177c74bbbb871cc0af92e30d2808ebae146f25d3fd8ba1622 ...
I must have a fundamental misunderstanding of what Signatureis doing - I've traced through it, and it appears to be calling update on a MessageDigestobject, with the algorithm set to SHA1 as I would expect, then getting the digest, then doing the encryption. What's making the results differ?
我一定对Signature正在做什么有一个根本的误解- 我已经通过它进行了跟踪,它似乎正在调用MessageDigest对象上的更新,算法设置为 SHA1,如我所料,然后获取摘要,然后执行加密。是什么导致结果不同?
EDIT:
编辑:
Leonidas made me check whether the signature scheme is supposed to do what I think it does. There are two types of signature defined in the RFC:
列奥尼达让我检查签名方案是否应该按照我的想法行事。RFC 中定义了两种类型的签名:
The first of these(PKCS1) is the one I describe above. It uses a hash function to create a digest, and then encrypts the result with a private key.
其中第一个(PKCS1)就是我上面描述的那个。它使用哈希函数创建摘要,然后使用私钥对结果进行加密。
The second algorithmuses a random salt value, and is more secure but non-deterministic. The signature produced from the code above does not change if the same key is used repeatedly, so I don't think it can be PSS.
所述第二算法使用随机盐值,并且是更安全的,但非确定性。如果重复使用相同的密钥,则由上述代码生成的签名不会改变,因此我认为它不能是PSS。
EDIT:
编辑:
Here's the bytes2string
method I was using:
这是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();
}
采纳答案by Mike Houston
OK, I've worked out what's going on. I was being stupid. Leonidas is right, it's not just the hash that gets encrypted, it's the ID of the hash algorithm concatenated with the digest:
好的,我已经弄清楚发生了什么。我是愚蠢的。Leonidas 是对的,它不仅仅是被加密的散列,它是与摘要连接的散列算法的 ID:
DigestInfo ::= SEQUENCE {
digestAlgorithm AlgorithmIdentifier,
digest OCTET STRING
}
Which is why they are different.
这就是它们不同的原因。
回答by Leonidas
Erm, after understanding your question: are you sure that the signature-method only creates a SHA1 and encrypts it? GPG et al offer to compress/clear sign the data. Maybe this java-signature-alg also creates a detachable/attachable signature.
嗯,在理解你的问题后:你确定签名方法只创建一个 SHA1 并加密它吗?GPG 等人提供压缩/清除数据签名。也许这个 java-signature-alg 也创建了一个可分离/可附加的签名。
回答by Romulo Pereira
To produce the same results:
要产生相同的结果:
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);
回答by magiconair
A slightly more efficient version of the bytes2String method is
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();
}
回答by Tany Villalba V.
I have a similar problem, I tested adding code and found some interesting results. With this code I add, I can deduce that depending on the "provider" to use, the firm can be different? (because the data included in the encryption is not always equal in all providers).
我有一个类似的问题,我测试了添加代码并发现了一些有趣的结果。使用我添加的这段代码,我可以推断出,根据要使用的“提供商”,公司可能会有所不同?(因为加密中包含的数据在所有提供程序中并不总是相同的)。
Results of my test.
我的测试结果。
Conclusion.- Signature Decipher= ???(trash) + DigestInfo (if we know the value of "trash", the digital signatures will be equal)
结论.- Signature Decipher= ???(trash) + DigestInfo(如果我们知道“trash”的值,则数字签名将相等)
IDE Eclipse OUTPUT...
IDE Eclipse 输出...
Input data: This is the message being signed
输入数据:这是被签名的消息
Digest: 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
摘要:62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
DigestInfo: 3021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
摘要信息:3021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
Signature Decipher: 1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
签名破译:1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
CODE
代码
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));
}
回答by always_a_rookie_to_learn
Taking @Mike Houston's answer as pointer, here is a complete sample code that does Signature and Hash and encryption.
以@Mike Houston 的回答为指针,这里有一个完整的示例代码,它执行签名和哈希以及加密。
/**
* @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();
}
}
You can use BouncyCastle Provider or default Sun Provider.
您可以使用 BouncyCastle Provider 或默认的 Sun Provider。