代码之家  ›  专栏  ›  技术社区  ›  Kothar

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

  •  65
  • Kothar  · 技术社区  · 16 年前

    班上有。如果我计算一个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。。。

    签名 正在做-我已经跟踪了它,它似乎正在调用一个 消息摘要 对象,将算法设置为SHA1,然后获取摘要,然后进行加密。是什么使结果不同?

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

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

    这个 second algorithm

    编辑:

    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();
    }
    
    6 回复  |  直到 11 年前
        1
  •  53
  •   G Bisconcini    4 年前

    好的,我已经弄清楚发生了什么。Leonidas是对的,加密的不仅仅是散列(在Cipher类方法的情况下),而是与摘要连接的散列算法的ID:

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

    这就是为什么密码和签名的加密是不同的。

        2
  •  11
  •   Maarten Bodewes    8 年前

    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);
    
        3
  •  5
  •   magiconair    14 年前

    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();
    }
    
        4
  •  4
  •   Leonidas    16 年前

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

        5
  •  2
  •   always_a_rookie    8 年前

    /**
     * @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提供程序。

        6
  •  0
  •   Lance Roberts    12 年前

    通过我添加的这段代码,我可以推断,根据要使用的“提供商”,公司可能会有所不同?(因为加密中包含的数据在所有提供程序中并不总是相等的)。

    我的测试结果。

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

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

    摘要:62b0a9ef15461c82766fb5bdaae9edbe4ac2e067

    摘要信息:302130090652B0E03021A0500041462B0A9EF15461C82766FB5BDAAE9EDBE4AC2E067

    密码

    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));
    }
    
        7
  •  0
  •   TodayGuessWhat    4 年前

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

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

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