Sample code:
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!");
}
}
}
No comments:
Post a Comment