Search this blog ...

Thursday, January 28, 2021

Manually verifying RSA SHA Signature in Java using Cipher

The post below may be helpful in understanding how a signature is generated and verified.

Sample code:

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 mshannon";

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

}

}

}