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

在java.security.Signature与MessageDigest和Cipher中使用SHA1和RSA,java,encryption,cryptography,rsa,digital-signature,Java,Encryption,Cryptography,Rsa,Digital Signature,我试图理解Java.security.Signature类的功能。如果我计算一个SHA1消息摘要,然后使用RSA加密该摘要,我会得到一个与要求签名类对同一事物签名不同的结果: // Generate new key KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); String plaintext = "Thi

我试图理解Java.security.Signature类的功能。如果我计算一个SHA1消息摘要,然后使用RSA加密该摘要,我会得到一个与要求签名类对同一事物签名不同的结果:

// 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));
结果(例如):

输入数据:这是正在签名的消息
摘要:62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
密文:057dc0d2f7f54acc95d3cf5cba9f944619394711003bdd12…
签名:7177C74BB871CC0AF92E30D2808EBAE146F25D3FD8BA1622

我一定对签名的作用有一个根本性的误解——我一直在跟踪它,它似乎在调用MessageDigest对象上的更新,正如我所期望的那样,算法设置为SHA1,然后获取摘要,然后进行加密。是什么使结果不同

编辑:

Leonidas让我检查签名方案是否应该像我认为的那样。中定义了两种类型的签名:

(PKCS1)就是我上面描述的那个。它使用哈希函数创建摘要,然后用私钥加密结果

使用随机盐值,更安全但不确定。如果重复使用同一个密钥,则上述代码生成的签名不会改变,因此我认为它不可能是PSS

编辑:

下面是我使用的
bytesString
方法:

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

呃,在理解您的问题之后:您确定签名方法只创建SHA1并对其进行加密吗?GPG等人提出压缩/清除数据签名。也许这个java签名alg还创建了一个可分离/可附加的签名。

好的,我已经解决了问题。Leonidas是对的,加密的不仅仅是散列(在Cipher类方法的情况下),而是与摘要连接的散列算法的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();
}

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

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

IDEEclipse输出。。。 输入数据:这是正在签名的消息

摘要:62b0a9ef15461c82766fb5bdaae9edbe4ac2e067

摘要信息:302130090652B0E03021A0500041462B0A9EF15461C82766FB5BDAAE9EDBE4AC2E067

签名解密:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

代码
以@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();
    }
}
您可以使用BouncyCastle提供程序或默认Sun提供程序。

下面的代码(摘自我的博客文章-)有望有助于理解带有RSA签名的标准SHA中的内容。这应该在标准的OracleJDK中工作,并且不需要BouncyCastle库。它使用sun.security类来处理解密的签名内容——您也可以轻松地手动解析

在下面的示例中,消息摘要算法是SHA-512,它生成64字节(512位)校验和

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;

import java.util.Arrays;

import javax.crypto.Cipher;

import sun.security.util.DerInputStream;
import sun.security.util.DerValue;

public class RSASignatureVerification
{
    public static void main(String[] args) throws Exception
    {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(2048);

        KeyPair keyPair = generator.generateKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();

        String data = "hello oracle";
        byte[] dataBytes = data.getBytes("UTF8");

        Signature signer = Signature.getInstance("SHA512withRSA");
        signer.initSign(privateKey);

        signer.update(dataBytes);

        byte[] signature = signer.sign(); // signature bytes of the signing operation's result.

        Signature verifier = Signature.getInstance("SHA512withRSA");
        verifier.initVerify(publicKey);
        verifier.update(dataBytes);

        boolean verified = verifier.verify(signature);
        if (verified)
        {
            System.out.println("Signature verified!");
        }

/*
    The statement that describes signing to be equivalent to RSA encrypting the
    hash of the message using the private key is a greatly simplified view
    The decrypted signatures bytes likely convey a structure (ASN.1) encoded
    using DER with the hash just one component of the structure.
*/

        // lets try decrypt signature and see what is in it ...
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, publicKey);

        byte[] decryptedSignatureBytes = cipher.doFinal(signature);

/*
    sample value of decrypted signature which was 83 bytes long

    30 51 30 0D 06 09 60 86 48 01 65 03 04 02 03 05
    00 04 40 51 00 41 75 CA 3B 2B 6B C0 0A 3F 99 E3
    6B 7A 01 DC F2 9B 36 E6 0D D4 31 89 53 A3 D9 80
    6D AE DD 45 7E 55 45 01 FC C8 73 D2 DD 8D E5 B9
    E0 71 57 13 41 D0 CD FF CA 58 01 03 A3 DD 95 A1
    C1 EE C8

    Taking above sample bytes ...
    0x30 means A SEQUENCE - which contains an ordered field of one or more types.
    It is encoded into a TLV triplet that begins with a Tag byte of 0x30.
    DER uses T,L,V (tag bytes, length bytes, value bytes) format

    0x51 is the length = 81 decimal (13 bytes)

    the 0x30 (48 decimal) that follows begins a second sequence

    https://tools.ietf.org/html/rfc3447#page-43
    the DER encoding T of the DigestInfo value is equal to the following for SHA-512
    0D 06 09 60 86 48 01 65 03 04 02 03 05 00 04 40 || H
    where || is concatenation and H is the hash value.

    0x0D is the length = 13 decimal (13 bytes)

    0x06 means an OBJECT_ID tag
    0x09 means the object id is 9 bytes ...

    https://docs.microsoft.com/en-au/windows/win32/seccertenroll/about-object-identifier?redirectedfrom=MSDN

    taking 2.16.840.1.101.3.4.2.3 (object id for SHA512 Hash Algorithm)

    The first two nodes of the OID are encoded onto a single byte.
    The first node is multiplied by the decimal 40 and the result is added to the value of the second node
    2 * 40 + 16 = 96 decimal = 60 hex
    Node values less than or equal to 127 are encoded on one byte.
    1 101 3 4 2 3 corresponds to in hex 01 65 03 04 02 03
    Node values greater than or equal to 128 are encoded on multiple bytes.
    Bit 7 of the leftmost byte is set to one. Bits 0 through 6 of each byte contains the encoded value.
    840 decimal = 348 hex
    -> 0000 0011 0100 1000
    set bit 7 of the left most byte to 1, ignore bit 7 of the right most byte,
    shifting right nibble of leftmost byte to the left by 1 bit
    -> 1000 0110 X100 1000 in hex 86 48

    05 00          ; NULL (0 Bytes)

    04 40          ; OCTET STRING (0x40 Bytes = 64 bytes
    SHA512 produces a 512-bit (64-byte) hash value

    51 00 41 ... C1 EE C8 is the 64 byte hash value
*/

        // parse DER encoded data
        DerInputStream derReader = new DerInputStream(decryptedSignatureBytes);

        byte[] hashValueFromSignature = null;

        // obtain sequence of entities
        DerValue[] seq = derReader.getSequence(0);
        for (DerValue v : seq)
        {
            if (v.getTag() == 4)
            {
                hashValueFromSignature = v.getOctetString(); // SHA-512 checksum extracted from decrypted signature bytes
            }
        }

        MessageDigest md = MessageDigest.getInstance("SHA-512");
        md.update(dataBytes);

        byte[] hashValueCalculated = md.digest();

        boolean manuallyVerified = Arrays.equals(hashValueFromSignature, hashValueCalculated);
        if (manuallyVerified)
        {
            System.out.println("Signature manually verified!");
        }
        else
        {
            System.out.println("Signature could NOT be manually verified!");
        }
    }
}
SHA-1将非常类似,但会产生一个20字节(160位)的校验和

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;

import java.util.Arrays;

import javax.crypto.Cipher;

import sun.security.util.DerInputStream;
import sun.security.util.DerValue;

public class RSASignatureVerification
{
    public static void main(String[] args) throws Exception
    {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(2048);

        KeyPair keyPair = generator.generateKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();

        String data = "hello oracle";
        byte[] dataBytes = data.getBytes("UTF8");

        Signature signer = Signature.getInstance("SHA512withRSA");
        signer.initSign(privateKey);

        signer.update(dataBytes);

        byte[] signature = signer.sign(); // signature bytes of the signing operation's result.

        Signature verifier = Signature.getInstance("SHA512withRSA");
        verifier.initVerify(publicKey);
        verifier.update(dataBytes);

        boolean verified = verifier.verify(signature);
        if (verified)
        {
            System.out.println("Signature verified!");
        }

/*
    The statement that describes signing to be equivalent to RSA encrypting the
    hash of the message using the private key is a greatly simplified view
    The decrypted signatures bytes likely convey a structure (ASN.1) encoded
    using DER with the hash just one component of the structure.
*/

        // lets try decrypt signature and see what is in it ...
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, publicKey);

        byte[] decryptedSignatureBytes = cipher.doFinal(signature);

/*
    sample value of decrypted signature which was 83 bytes long

    30 51 30 0D 06 09 60 86 48 01 65 03 04 02 03 05
    00 04 40 51 00 41 75 CA 3B 2B 6B C0 0A 3F 99 E3
    6B 7A 01 DC F2 9B 36 E6 0D D4 31 89 53 A3 D9 80
    6D AE DD 45 7E 55 45 01 FC C8 73 D2 DD 8D E5 B9
    E0 71 57 13 41 D0 CD FF CA 58 01 03 A3 DD 95 A1
    C1 EE C8

    Taking above sample bytes ...
    0x30 means A SEQUENCE - which contains an ordered field of one or more types.
    It is encoded into a TLV triplet that begins with a Tag byte of 0x30.
    DER uses T,L,V (tag bytes, length bytes, value bytes) format

    0x51 is the length = 81 decimal (13 bytes)

    the 0x30 (48 decimal) that follows begins a second sequence

    https://tools.ietf.org/html/rfc3447#page-43
    the DER encoding T of the DigestInfo value is equal to the following for SHA-512
    0D 06 09 60 86 48 01 65 03 04 02 03 05 00 04 40 || H
    where || is concatenation and H is the hash value.

    0x0D is the length = 13 decimal (13 bytes)

    0x06 means an OBJECT_ID tag
    0x09 means the object id is 9 bytes ...

    https://docs.microsoft.com/en-au/windows/win32/seccertenroll/about-object-identifier?redirectedfrom=MSDN

    taking 2.16.840.1.101.3.4.2.3 (object id for SHA512 Hash Algorithm)

    The first two nodes of the OID are encoded onto a single byte.
    The first node is multiplied by the decimal 40 and the result is added to the value of the second node
    2 * 40 + 16 = 96 decimal = 60 hex
    Node values less than or equal to 127 are encoded on one byte.
    1 101 3 4 2 3 corresponds to in hex 01 65 03 04 02 03
    Node values greater than or equal to 128 are encoded on multiple bytes.
    Bit 7 of the leftmost byte is set to one. Bits 0 through 6 of each byte contains the encoded value.
    840 decimal = 348 hex
    -> 0000 0011 0100 1000
    set bit 7 of the left most byte to 1, ignore bit 7 of the right most byte,
    shifting right nibble of leftmost byte to the left by 1 bit
    -> 1000 0110 X100 1000 in hex 86 48

    05 00          ; NULL (0 Bytes)

    04 40          ; OCTET STRING (0x40 Bytes = 64 bytes
    SHA512 produces a 512-bit (64-byte) hash value

    51 00 41 ... C1 EE C8 is the 64 byte hash value
*/

        // parse DER encoded data
        DerInputStream derReader = new DerInputStream(decryptedSignatureBytes);

        byte[] hashValueFromSignature = null;

        // obtain sequence of entities
        DerValue[] seq = derReader.getSequence(0);
        for (DerValue v : seq)
        {
            if (v.getTag() == 4)
            {
                hashValueFromSignature = v.getOctetString(); // SHA-512 checksum extracted from decrypted signature bytes
            }
        }

        MessageDigest md = MessageDigest.getInstance("SHA-512");
        md.update(dataBytes);

        byte[] hashValueCalculated = md.digest();

        boolean manuallyVerified = Arrays.equals(hashValueFromSignature, hashValueCalculated);
        if (manuallyVerified)
        {
            System.out.println("Signature manually verified!");
        }
        else
        {
            System.out.println("Signature could NOT be manually verified!");
        }
    }
}

我不确定,不,但我希望算法能够表明它是否会做的不仅仅是这两个操作。我一直在读RFC:,据我所知,它只是散列,然后加密散列。GPG对加密信息进行压缩吗?为自己找到它万岁:)好的,迈克。那么如何使它们产生相同的结果呢?请注意,如果在此之后结果仍然不正确,那么可能是底层加密库对任何加密操作使用了随机填充。这似乎是一个常见的错误。如果您将ASN1/DER检查器用作ASN,您可以检测到这类问题。1我们对此投了反对票,因为它没有提供完整的答案,并且忘记了解释PKCS#1兼容的填充,这也是算法工作所必需的。PKCS#1在大多数情况下都是Java的默认值,但是如果提供程序选择不同的默认值,您的代码仍然可能失败。谢谢!事实上,查找表是一个很好的解决方案+1 ;)@新手它是SHA-1的对象标识符(OID)。这是简写符号,但在PKCS#1中,它被指定为
id-sha1对象标识符::={iso(1)已识别组织(3)oiw(14)secsig(3)算法(2)26}
我输入了正确的算法字符串,这样您就不必猜测默认设置。请注意,当您像这样使用
密码时,Oracle的提供者和Bouncy Castle正确地使用PKCS#1 v1.5填充生成签名。其他提供程序可能没有这么灵活,而是使用PKCS#1填充进行加密。一般来说,常用哈希的OID在PKCS1的第11.2.3节或附录B.1中复制,其预处理的DER编码在PKCS1的第9.2.1节中,即rfc 2437 3447和8017中复制。这几乎就像他们想象有人可能在实现之前阅读了规范一样