SHA256withRSA和SHA256之间的区别然后是RSA

时间:2015-10-23 15:13:00

标签: java rsa digital-signature bouncycastle

使用以下两种方法计算签名有什么区别?

  1. 使用Signature.getInstance("SHA256withRSA")
  2. 计算签名
  3. 使用MessageDigest.getInstance("SHA-256")计算SHA256并使用Signature.getInstance("RSA");计算摘要以获取签名?
  4. 如果它们不同,有没有办法修改方法2,以便两种方法都提供相同的输出?

    我尝试了以下代码:

    package mysha.mysha;
    import java.security.MessageDigest;
    import java.security.PrivateKey;
    import java.security.Security;
    import java.security.Signature;
    
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    
    public class MySHA256 {
    
        public static void main(String[] args) throws Exception {
            //compute SHA256 first
            Security.addProvider(new BouncyCastleProvider());
            String s = "1234";
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(s.getBytes());
            byte[] outputDigest = messageDigest.digest();       
            //sign SHA256 with RSA
            PrivateKey privateKey = Share.loadPk8("D:/key.pk8");
            Signature rsaSignature = Signature.getInstance("RSA");
            rsaSignature.initSign(privateKey);
            rsaSignature.update(outputDigest);
            byte[] signed = rsaSignature.sign();
            System.out.println(bytesToHex(signed));
    
    
            //compute SHA256withRSA as a single step
            Signature rsaSha256Signature = Signature.getInstance("SHA256withRSA");
            rsaSha256Signature.initSign(privateKey);
            rsaSha256Signature.update(s.getBytes());
            byte[] signed2 = rsaSha256Signature.sign();
            System.out.println(bytesToHex(signed2));
        }
    
        public static String bytesToHex(byte[] bytes) {
            final char[] hexArray = "0123456789ABCDEF".toCharArray();
            char[] hexChars = new char[bytes.length * 2];
            for ( int j = 0; j < bytes.length; j++ ) {
                int v = bytes[j] & 0xFF;
                hexChars[j * 2] = hexArray[v >>> 4];
                hexChars[j * 2 + 1] = hexArray[v & 0x0F];
            }
            return new String(hexChars);
        }
    
    }
    

    尽管如此,产出并不相同。

    以下是使用我的测试键输出的示例:

    方法1:61427B2A2CF1902A4B15F80156AEB09D8096BA1271F89F1919C78B18D0BABA08AA043A0037934B5AE3FC0EB7702898AC5AE96517AFD93433DF540353BCCE72A470CFA4B765D5835E7EA77743F3C4A0ABB11414B0141EF7ECCD2D5285A69728D0D0709C2537D6A772418A928B0E168F81C99B538FD25BDA7496AE8E185AC46F39

    方法2:BA9039B75CA8A40DC9A7AED51E174E2B3365B2D6A1CF94DF70A00D898074A51FDD9973672DDE95CBAC39EBE4F3BA529C538ED0FF9F0A3F9A8CE203F1DFFA907DC508643906AA86DA54DFF8A90B00F5F116D13A53731384C1C5C9C4E75A3E41DAF88F74D2F1BCCF818764A4AB144A081B641C1C488AC8B194EB14BC9D1928E4EA

    更新1:

    根据mkl的回答,我修改了我的代码,但仍然无法正确完成。我还想念一些东西吗?

    package mysha.mysha;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.security.MessageDigest;
    import java.security.PrivateKey;
    import java.security.Security;
    import java.security.Signature;
    
    import org.bouncycastle.asn1.DEROutputStream;
    import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
    import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
    import org.bouncycastle.asn1.x509.DigestInfo;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    
    public class MySHA256 { 
        public static void main(String[] args) throws Exception {
            //compute SHA256 first
            Security.addProvider(new BouncyCastleProvider());
            String s = "1234";
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(s.getBytes());
            byte[] outputDigest = messageDigest.digest();
    
            AlgorithmIdentifier sha256Aid = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, null);
            DigestInfo di = new DigestInfo(sha256Aid, outputDigest);
            //sign SHA256 with RSA
            PrivateKey privateKey = Share.loadPk8("D:/key.pk8");
            Signature rsaSignature = Signature.getInstance("RSA");
            rsaSignature.initSign(privateKey);
            rsaSignature.update(di.toASN1Primitive().getEncoded());
            byte[] signed = rsaSignature.sign();
            System.out.println("method 1: "+bytesToHex(signed));
    
    
            //compute SHA256withRSA as a single step
            Signature rsaSha256Signature = Signature.getInstance("SHA256withRSA");
            rsaSha256Signature.initSign(privateKey);
            rsaSha256Signature.update(s.getBytes());
            byte[] signed2 = rsaSha256Signature.sign();
            System.out.println("method 2: "+bytesToHex(signed2));
        }
        public static String bytesToHex(byte[] bytes) {
            final char[] hexArray = "0123456789ABCDEF".toCharArray();
            char[] hexChars = new char[bytes.length * 2];
            for ( int j = 0; j < bytes.length; j++ ) {
                int v = bytes[j] & 0xFF;
                hexChars[j * 2] = hexArray[v >>> 4];
                hexChars[j * 2 + 1] = hexArray[v & 0x0F];
            }
            return new String(hexChars);
        }
    }
    

    方法1: 675D868546777C5A9B5E74988E0CD41A46A929C1D0890B32B1FBE34F12D68F1FDB56E623294DB903F6AC60A2ADA61976B27C66056A16F5790A78168803AD2C685F9B4CF983C939305A9819CBA9D95441CD7214D40D06A98B4DDF9692A7D300DD51E808A6722A0D7C288DBD476DF4DEEBB3DAF41CFC0978F24424960F86F0284E

    方法2: BA9039B75CA8A40DC9A7AED51E174E2B3365B2D6A1CF94DF70A00D898074A51FDD9973672DDE95CBAC39EBE4F3BA529C538ED0FF9F0A3F9A8CE203F1DFFA907DC508643906AA86DA54DFF8A90B00F5F116D13A53731384C1C5C9C4E75A3E41DAF88F74D2F1BCCF818764A4AB144A081B641C1C488AC8B194EB14BC9D1928E4EA

2 个答案:

答案 0 :(得分:46)

差异

使用"SHA256withRSA"进行签名并计算SHA256哈希并使用"RSA"(= "NONEwithRSA")进行签名之间的区别最重要的是,在前一种情况下,计算出的SHA-256哈希值为首先封装在DigestInfo结构

DigestInfo ::= SEQUENCE {
    digestAlgorithm DigestAlgorithm,
    digest OCTET STRING
}
在填充然后加密之前

,而在后一种情况下,填充并加密裸露的SHA256哈希值。

  

如果它们不同,有没有办法修改方法2,以便两种方法都提供相同的输出?

首先,在使用DigestInfo签名之前,您必须将哈希值封装在"NONEwithRSA"结构中。

RFC 3447 Section 9.2通过在注释1中说明

来帮助解决这个问题
1. For the six hash functions mentioned in Appendix B.1, the DER
   encoding T of the DigestInfo value is equal to the following:
   ...
   SHA-256: (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00
                04 20 || H.

让它发挥作用

在回应上面的部分时,OP用更新的代码更新了他的问题。不幸的是,它对他来说还不起作用。因此,

OP的代码

我执行了OP的代码(SignInSteps.java)。由于他没有提供私钥,我使用了自己的测试密钥(demo-rsa2048.p12)。结果:

GreenhandOriginal:
1B9557B6A076226FA4C26A9370A0E9E91B627F14204D427B03294EC4BFC346FDEEFB3A483B1E5A0593F26E9DE87F9202E1064F4D75B24B8FA355B23A560AF263361BB94B2339C3A01952C447CAC862AA9DCAB64B09ABAA0AD50232CDB299D1E4B5F7138F448A87ED32BFF4B5B66F35FFA08F13FD98DFCEC7114710282E463245311DA7A56CBEA958D88137A8B507D8601464535978EFE36EE37EF721260DB7112484F244409F0BD64C823ACFB13D06ABA84A9A0C5AB207E19231D6A71CC80F07FDA2A9654F0F609C2C3396D6DFFBBB10EF4C3D4B5ADFC72EACC044E81F252B699F095CFEF8630B284B1F6BD7201367BD5FDF2BB4C20BD07B9CC20B214D86C729
4B9ECA6DD47C1B230D972E7DA026165F1CE743EC96825E4C13DFE2C6437FE673A13CA622047EE7D2F7C5280198D81550A1CBD17F8E8A3C4C2D53A746FA6464AA5194FC2782527B014F017008D89BB2C80B7FA367C74FE01369986B56BCE7DC573A11ED884511F0CB12160CA5E42D488451AA8961BF5A9F71E6A5E89F19BC8EFAC26DDE989A0369667EE74372F6E558887FE2561EA926B441AB8F0FD3DEDD608A671011313372084B059CAD7E4807AC852C0873C57F216349422771C089678BAC3021D054C4427EADE70219E251617B83E68640DD7D03C3F99E47F79EB71C124F59EDEA724496A4552F2E9E1F90DDE550745E85483D823F146982C6D2008FE9AA

GreenhandUpdated:
method 1: 4B9ECA6DD47C1B230D972E7DA026165F1CE743EC96825E4C13DFE2C6437FE673A13CA622047EE7D2F7C5280198D81550A1CBD17F8E8A3C4C2D53A746FA6464AA5194FC2782527B014F017008D89BB2C80B7FA367C74FE01369986B56BCE7DC573A11ED884511F0CB12160CA5E42D488451AA8961BF5A9F71E6A5E89F19BC8EFAC26DDE989A0369667EE74372F6E558887FE2561EA926B441AB8F0FD3DEDD608A671011313372084B059CAD7E4807AC852C0873C57F216349422771C089678BAC3021D054C4427EADE70219E251617B83E68640DD7D03C3F99E47F79EB71C124F59EDEA724496A4552F2E9E1F90DDE550745E85483D823F146982C6D2008FE9AA
method 2: 4B9ECA6DD47C1B230D972E7DA026165F1CE743EC96825E4C13DFE2C6437FE673A13CA622047EE7D2F7C5280198D81550A1CBD17F8E8A3C4C2D53A746FA6464AA5194FC2782527B014F017008D89BB2C80B7FA367C74FE01369986B56BCE7DC573A11ED884511F0CB12160CA5E42D488451AA8961BF5A9F71E6A5E89F19BC8EFAC26DDE989A0369667EE74372F6E558887FE2561EA926B441AB8F0FD3DEDD608A671011313372084B059CAD7E4807AC852C0873C57F216349422771C089678BAC3021D054C4427EADE70219E251617B83E68640DD7D03C3F99E47F79EB71C124F59EDEA724496A4552F2E9E1F90DDE550745E85483D823F146982C6D2008FE9AA

因此,与OP的观察结果相反,在更新代码的情况下,签名相同。

不假设复制和粘贴错误,可能还会涉及其他差异。

环境

我使用Java 8(1.8.0_20)进行了测试,添加了无限制的管辖区文件和BouncyCastle 1.52,1.49和1.46(由于BC API更改而对测试代码进行了少量修改)。

评论中提到的OP:

  

Java是JRE 8更新66. BouncyCastle是bcprov-jdk15on-153.jar。

因此我更新了Java,仍然没有区别。

然后我将BouncyCastle更新为1.53。事实上,结果突然有所不同:

GreenhandOriginal:
1B9557B6A076226FA4C26A9370A0E9E91B627F14204D427B03294EC4BFC346FDEEFB3A483B1E5A0593F26E9DE87F9202E1064F4D75B24B8FA355B23A560AF263361BB94B2339C3A01952C447CAC862AA9DCAB64B09ABAA0AD50232CDB299D1E4B5F7138F448A87ED32BFF4B5B66F35FFA08F13FD98DFCEC7114710282E463245311DA7A56CBEA958D88137A8B507D8601464535978EFE36EE37EF721260DB7112484F244409F0BD64C823ACFB13D06ABA84A9A0C5AB207E19231D6A71CC80F07FDA2A9654F0F609C2C3396D6DFFBBB10EF4C3D4B5ADFC72EACC044E81F252B699F095CFEF8630B284B1F6BD7201367BD5FDF2BB4C20BD07B9CC20B214D86C729
4B9ECA6DD47C1B230D972E7DA026165F1CE743EC96825E4C13DFE2C6437FE673A13CA622047EE7D2F7C5280198D81550A1CBD17F8E8A3C4C2D53A746FA6464AA5194FC2782527B014F017008D89BB2C80B7FA367C74FE01369986B56BCE7DC573A11ED884511F0CB12160CA5E42D488451AA8961BF5A9F71E6A5E89F19BC8EFAC26DDE989A0369667EE74372F6E558887FE2561EA926B441AB8F0FD3DEDD608A671011313372084B059CAD7E4807AC852C0873C57F216349422771C089678BAC3021D054C4427EADE70219E251617B83E68640DD7D03C3F99E47F79EB71C124F59EDEA724496A4552F2E9E1F90DDE550745E85483D823F146982C6D2008FE9AA

GreenhandUpdated:
method 1: 6BAAAC1060B6D0D56AD7D45A1BEECE82391088FF47A8D8179EFBBEB0925C4AC6C9DFC56F672E99F4A6E3C106A866B70513C25AE11B267286C584A136FBC20C4D1E7B10697352DF020BA5D67029A6EF890B2674F02C496CB1F1EBB0D4DBB580EB045DBB0FA0D7D73B418FF63F345658C6C73DA742FE260C9639C94967A928F74F61DACA03310B9986C32D83CAB8C7FC13E80612CCFC0B7E3E35BEA04EAEBDAA55FB8837B4661DC71499B4A0B1D36E1D23D9927CDB55C237D5AB2E5C088F29C6FAFAD9FE64DD4851CEC113560864E9923D485D0C6E092C8EBE82D29C312E5835B38EE9BD6B8B4BCC753EF4EE4D0977B2E781B391839E3EC31C36E5B1AA0CE90227
method 2: 4B9ECA6DD47C1B230D972E7DA026165F1CE743EC96825E4C13DFE2C6437FE673A13CA622047EE7D2F7C5280198D81550A1CBD17F8E8A3C4C2D53A746FA6464AA5194FC2782527B014F017008D89BB2C80B7FA367C74FE01369986B56BCE7DC573A11ED884511F0CB12160CA5E42D488451AA8961BF5A9F71E6A5E89F19BC8EFAC26DDE989A0369667EE74372F6E558887FE2561EA926B441AB8F0FD3DEDD608A671011313372084B059CAD7E4807AC852C0873C57F216349422771C089678BAC3021D054C4427EADE70219E251617B83E68640DD7D03C3F99E47F79EB71C124F59EDEA724496A4552F2E9E1F90DDE550745E85483D823F146982C6D2008FE9AA

有趣的是,只有更新代码中方法1的值不同。因此,在那种情况下,我查看了中间对象

[BC 1.52]
hash: 03AC674216F3E15C761EE1A5E255F067953623C8B388B4459E13F978D7C846F4
algo: 2.16.840.1.101.3.4.2.1
info: 3031300D06096086480165030402010500042003AC674216F3E15C761EE1A5E255F067953623C8B388B4459E13F978D7C846F4

[BC 1.53]
hash: 03AC674216F3E15C761EE1A5E255F067953623C8B388B4459E13F978D7C846F4
algo: 2.16.840.1.101.3.4.2.1
info: 302F300B0609608648016503040201042003AC674216F3E15C761EE1A5E255F067953623C8B388B4459E13F978D7C846F4

因此,BouncyCastle 1.53以不同方式编码DigestInfo对象! 1.52(及以下)中的编码是RFC 3447 Section 9.2预期的编码。

查看ASN.1转储,可以看到BC 1.52将AlgorithmIdentifier编码为

 2  13:   SEQUENCE {
   <06 09>
 4   9:     OBJECT IDENTIFIER sha-256 (2 16 840 1 101 3 4 2 1)
      :       (NIST Algorithm)
   <05 00>
15   0:     NULL
      :     }

而BC 1.53创建

 2  11:   SEQUENCE {
   <06 09>
 4   9:     OBJECT IDENTIFIER sha-256 (2 16 840 1 101 3 4 2 1)
      :       (NIST Algorithm)
      :     }

因此在1.53中,算法参数完全丢失。这表明改变了行

AlgorithmIdentifier sha256Aid = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, null);

AlgorithmIdentifier sha256Aid = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE);

突然它也适用于BouncyCastle 1.53,方法1和方法2的值重合! ;)

<强> TL; DR

在实例化null时,请勿使用AlgorithmIdentifier作为SHA-256参数,而是使用DERNull.INSTANCE

我是怎么做的......

在评论中,OP表示他想更多地了解

  
      
  1. 如何检查BouncyCastle的中间对象和
  2.   
  3. 如何生成ASN.1转储。
  4.   

因此...

...检查中间对象

非常简单。首先我分开了一行

rsaSignature.update(di.toASN1Primitive().getEncoded());

在更新后的代码中

byte[] encodedDigestInfo = di.toASN1Primitive().getEncoded();
rsaSignature.update(encodedDigestInfo);

然后添加控制台输出

System.out.println("    hash: " + bytesToHex(outputDigest));
System.out.println("    algo: " + sha256Aid.getAlgorithm());
System.out.println("    info: " + bytesToHex(encodedDigestInfo));

最后,我用不同的BouncyCastle版本执行了代码。

...生成ASN.1转储

有一个名为dumpasn1 by Peter Gutmann的着名实用程序,它已成为许多命令行和GUI工具的内核,用于创建和显示ASN.1转储。我目前碰巧使用GUIdumpASN-ng

在手头的情况下,我将byte[] encodedDigestInfo的内容保存到文件中(可以使用例如Files.write完成)并在GUIdumpASN-ng中打开这些文件。

答案 1 :(得分:0)

******在没有BouncyCastle的情况下,我按照mkl的回答步骤调整了您的解决方案******

import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateCrtKey;

import sun.security.rsa.RSACore;
import sun.security.rsa.RSAPadding;
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
import sun.security.x509.AlgIdDSA;
import sun.security.x509.AlgorithmId;

public class MySHA256 {

    public static void main(String[] args) throws Exception {

        String s = "1234";
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        messageDigest.update(s.getBytes());
        byte[] outputDigest = messageDigest.digest(); 
        //compute SHA256 first
        DerOutputStream out = new DerOutputStream();
        new AlgorithmId(AlgIdDSA.SHA256_oid).encode(out);
        out.putOctetString(outputDigest);
        DerValue result = new DerValue(DerValue.tag_Sequence, out.toByteArray());
        byte[] encoded = result.toByteArray();      
        //sign SHA256 with RSA
        PrivateKey privateKey = Share.loadPk8("D:/key.pk8");
        RSAPrivateCrtKey pkRSA = (RSAPrivateCrtKey)privateKey;
        int keySize = RSACore.getByteLength(pkRSA);
        RSAPadding padding = RSAPadding.getInstance(RSAPadding.PAD_BLOCKTYPE_1, keySize, null);
        byte[] padded = padding.pad(encoded);
        byte[] signed = RSACore.rsa(padded, pkRSA, true);
        System.out.println(bytesToHex(signed));


        //compute SHA256withRSA as a single step
        Signature rsaSha256Signature = Signature.getInstance("SHA256withRSA");
        rsaSha256Signature.initSign(privateKey);
        rsaSha256Signature.update(s.getBytes());
        byte[] signed2 = rsaSha256Signature.sign();
        System.out.println(bytesToHex(signed2));
    }

    public static String bytesToHex(byte[] bytes) {
        final char[] hexArray = "0123456789ABCDEF".toCharArray();
        char[] hexChars = new char[bytes.length * 2];
        for ( int j = 0; j < bytes.length; j++ ) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }



}