如何制作" MessageDigest SHA-1和签名NONEwithRSA"相当于"签名SHA1withRSA"

时间:2014-04-03 14:11:05

标签: java hash rsa keystore sha

我有兴趣将带有RSA签名的SHA-1哈希应用于某些数据,但我需要分两步完成 - 首先应用哈希然后对数据进行签名。 Signature.sign()函数似乎创建了一个最终签名的更复杂(ASN.1?)的数据结构(参见this question)。如何在不使用BouncyCastle等外部库的情况下制作两个等效的文件?

使用签名:

单步应用哈希并签名
PrivateKey privatekey = (PrivateKey) keyStore.getKey(alias, null);
...
sig = Signature.getInstance("SHA1withRSA", "SunMSCAPI");
sig.initSign(privatekey);
sig.update(data_to_sign);
byte[] bSignedData_CAPISHA1_CAPIRSA = sig.sign();

通过MessageDigest应用哈希,然后使用签名:

进行签名
PrivateKey privatekey = (PrivateKey) keyStore.getKey(alias, null);
...
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
byte[] data_to_sign = sha1.digest(bdataToSign);
Signature sig = Signature.getInstance("NONEwithRSA", "SunMSCAPI");
sig.initSign(privatekey);
sig.update(data_to_sign);
byte[] bSignedData_JAVASHA1_CAPIRSA = sig.sign();
...

我正在寻找以下等价物:

bSignedData_JAVASHA1_CAPIRSA == bSignedData_CAPISHA1_CAPIRSA

我的最终目标是创建哈希,然后使用PKCS11令牌进行签名,但我要求签名数据与旧数据格式相同,以便进行验证。

3 个答案:

答案 0 :(得分:2)

我能够通过以下方式解决这个问题:

  1. 要签名的数据需要在a中正确格式化 DigestInfo DER编码的字节数组。签名SHA1withRSA需要 为你照顾这个,但如果你想分两步完成它 过程中,您需要创建自己的DigestInfo。我最终复制了 从BouncyCastle到我的最少量的ASN.1类 尽管我不想使用第三个,但我还是想要实现这个目标 party lib。

  2. 如果您尝试使用Cipher API来加密DigestInfo,那么 PKCS1填充将是随机的,不适合数字 签名。我需要静态填充。

  3. Signature.getInstance(" NONEwithRSA"," SunMSCAPI")拒绝 DER编码的DigestInfo格式,如果您尝试,将返回错误 签署该数据。但是,因为我最终想要使用PKCS11 API生成签名,我最终签署了DER编码 具有PKCS11 C_SignInit和C_Sign函数的DigestInfo。

  4. 总结一下,对我有用的是:

    1. 使用Java MessageDigest API
    2. 生成要签名的数据的SHA-1哈希值
    3. 生成DigestInfo DER编码的ASN.1对象,其中SHA-1哈希和SHA-1 OID嵌入在对象中。
    4. 使用来自第三方库的PKCS11 C_Sign函数签署了DigestInfo。
    5. 以下链接最有助于解决我的问题:

      Oracle Forums: SHA1withRSA - how to do that in 2 steps?

      StackOverflow: Using SHA1 and RSA with java.security.Signature vs. MessageDigest and Cipher

答案 1 :(得分:1)

emsworth的回答是,当我遇到同样的问题(但使用SHA512)时,对我有很大的帮助。然而,它仍然缺少一个暗示,这需要我多花几天时间才能找到自己。

签名的构建方式有很多种。例如,当使用RSASSA-PKCS1-v1_5(来自RFC 5246,TLS 1.2)时,不能以通常的方式获得DER编码的DigestInfo。例如,如果使用BouncyCastle

DigestInfo digestInfo = new DigestInfo(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512), hash);
byte[] digestedHash = digestInfo.getEncoded(ASN1Encoding.DER);

不会产生预期的结果。 RFC 3447定义了如何构造DER编码(第42页)。例如,在SHA-512的情况下,DER编码如下:

// code example includes MessageDigest for the sake of completeness
byte[] input = ... // the raw data
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(input);
byte[] hash = md.digest();

// Taken from RFC 3447, page 42 for SHA-512, create input for signing
byte[] modifierBytes = { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 };
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(modifierBytes);
baos2.write(hash);

// create signature
Signature s = Signature.getInstance("NONEwithRSA");
s.initSign(MyTlsCredentials.THE_CLIENT_KEY);
s.update(baos.toByteArray());
byte[] signature = s.sign();

// add prefix as specified in RFC 3447, im my case it had always been the shown values
// but I have not understand the RFC completely in this point as the code seems to be
// contradictious to the interpretation of the byte values for the hash function from page 42.
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
baos2.write(new byte[] { 1, 0 });
baos2.write(signature5);

答案 2 :(得分:0)

根据其他同事的回答实现的示例(可能对某人有用):

class SignatureTest {
    @Test
    void signAndVerify() throws Exception {
        byte[] message = "some text".getBytes();
        KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();

        byte[] digest = MessageDigest.getInstance("SHA1").digest(message);
        // or encodeToPKCS1BySunJCE() may be called
        byte[] pkcs1EncodedDigest = encodeToPKCS1ByBounceCastle(digest);

        Signature signature = Signature.getInstance("NONEwithRSA");
        signature.initSign(keyPair.getPrivate());
        signature.update(pkcs1EncodedDigest);
        byte[] digestSignature = signature.sign();

        signature.initVerify(keyPair.getPublic());
        signature.update(pkcs1EncodedDigest);
        assertTrue(signature.verify(digestSignature));
    }

    @Test
    void signDigestAndMessage() throws Exception {
        byte[] message = "some text".getBytes();
        KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();

        byte[] digest = MessageDigest.getInstance("SHA1").digest(message);
        // or encodeToPKCS1BySunJCE() may be called
        byte[] pkcs1EncodedDigest = encodeToPKCS1BySunJCE(digest);

        Signature signature = Signature.getInstance("NONEwithRSA");
        signature.initSign(keyPair.getPrivate());
        signature.update(pkcs1EncodedDigest);
        byte[] signedDigest = signature.sign();

        signature = Signature.getInstance("SHA1WithRSA");
        signature.initSign(keyPair.getPrivate());
        signature.update(message);
        byte[] signedMessage = signature.sign();

        assertEquals(signedDigest, signedMessage);
    }

    private byte[] encodeToPKCS1ByBounceCastle(byte[] digest) throws IOException {
        return new DigestInfo(new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, DERNull.INSTANCE), digest).getEncoded();
    }

    private byte[] encodeToPKCS1BySunJCE(byte[] digest) throws IOException {
        DerOutputStream out = new DerOutputStream();
        new AlgorithmId(AlgorithmId.SHA_oid).encode(out);
        out.putOctetString(digest);
        return new DerValue(DerValue.tag_Sequence, out.toByteArray()).toByteArray();
    }
}