Signing and validation using java.security.Provider

Notes on how to use java.security.Provider

There is a mechanism called Provider in java.security that allows you to add encryption and signature implementations, but probably only a few people use it, so there aren't many sites that explain it.

First of all, try signing with Java and verifying

Sample.java


package io.github.tshibata.sample;

import java.security.*;

public class Sample {
	static void test(byte[] data, KeyPair pair) throws Exception {
		Signature sig = Signature.getInstance("SHA256withRSA");
		//I'll sign
		sig.initSign(pair.getPrivate());
		sig.update(data);
		byte[] s = sig.sign();
		//I will output it in Base64
		System.out.println(java.util.Base64.getEncoder().encodeToString​(s));
		//I'll verify
		sig.initVerify(pair.getPublic());
		sig.update(data);
		System.out.println(sig.verify(s));
	}
	public static void main(String[] args) throws Exception {
		//I'll make a key
		KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
		KeyPair pair = gen.generateKeyPair();
		// "hello"I will sign and verify
		test("hello".getBytes(), pair);
	}
}

Try implementing a subclass of Signature

For signatures, the Signature class is in charge.

MySignature.java


package io.github.tshibata.sample;

import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

import java.math.BigInteger;

public class MySignature extends Signature {

// echo | openssl dgst -sign private.pem | openssl rsautl -verify -inkey private.pem -raw | head --bytes=-32 | base64

	private static byte[] padding = java.util.Base64.getDecoder().decode(
		"AAH/////////////////////////////////////////////////////////////////////////" +
		"////////////////////////////////////////////////////////////////////////////" +
		"////////////////////////////////////////////////////////////////////////////" +
		"////////////////////////////////////////////ADAxMA0GCWCGSAFlAwQCAQUABCA=");

	private RSAPrivateKey privateKey;

	private RSAPublicKey publicKey;

	private MessageDigest messageDigest;

	public MySignature() {
		super("SHA256withRSA");
		try {
			messageDigest = MessageDigest.getInstance("SHA-256");
		} catch (NoSuchAlgorithmException exc) {
			throw new UnsupportedOperationException(exc);
		}
	}

	protected Object engineGetParameter(String param) throws InvalidParameterException {
		throw new UnsupportedOperationException("engineGetParameter");
	}

	protected void engineSetParameter(String param, Object value) throws InvalidParameterException {
		throw new UnsupportedOperationException("engineSetParameter");
	}

	protected void engineInitSign(PrivateKey key) throws InvalidKeyException {
		messageDigest.reset();
		privateKey = (RSAPrivateKey) key;
	}

	protected void engineInitVerify(PublicKey key) throws InvalidKeyException {
		messageDigest.reset();
		publicKey = (RSAPublicKey) key;
	}

	protected void engineUpdate(byte[] data, int offset, int length) throws SignatureException {
		messageDigest.update(data, offset, length);
	}

	protected void engineUpdate(byte data) throws SignatureException {
		messageDigest.update(data);
	}

	protected byte[] engineSign() throws SignatureException {
		byte[] data = new byte[256];
		System.arraycopy(padding, 0, data, 0, padding.length);
		byte[] digest = messageDigest.digest();
		System.arraycopy(digest, 0, data, padding.length, digest.length);
		BigInteger m = new BigInteger(1, data);
		BigInteger d = privateKey.getPrivateExponent();
		BigInteger n = privateKey.getModulus();
		BigInteger c = m.modPow(d, n);

		byte[] signed = new byte[256];
		for (int i = signed.length - 1; 0 <= i; i--) {
			signed[i] = c.byteValue();
			c = c.shiftRight(8);
		}
		return signed;
	}

	protected boolean engineVerify(byte[] signed) throws SignatureException {
		BigInteger c = new BigInteger(1, signed);
		BigInteger e = publicKey.getPublicExponent();
		BigInteger n = publicKey.getModulus();
		BigInteger m = c.modPow(e, n);

		byte[] data = new byte[256];
		System.arraycopy(padding, 0, data, 0, padding.length);
		byte[] digest = messageDigest.digest();
		System.arraycopy(digest, 0, data, padding.length, digest.length);
		for (int i = data.length - 1; 0 <= i; i--) {
			if (data[i] != m.byteValue()) {
				return false;
			}
			m = m.shiftRight(8);
		}
		return true;
	}
}

The key length is fixed at 2048bit (256byte). All you have to do is create a hash, pad it, raise it to the exponent, divide it by modulus, and calculate the remainder. Padding is a magic or a rule.

echo | openssl dgst -sign private.pem | openssl rsautl -verify -inkey private.pem -raw | head --bytes=-32 | base64

I made it. "The remainder of riding exponent and dividing by modulus" is the key to RSA, and was found by great scholars in the past. To get this used in

MyProvider.java


package io.github.tshibata.sample;

import java.security.*;

public class MyProvider extends Provider {

	public MyProvider() {
		super("MyProvider", "1.0", "My provider");
		put("Alg.Alias.Signature.OID.1.2.840.113549.1.1.11", "SHA256withRSA");
		put("Signature.SHA256withRSA SupportedKeyClasses", "java.security.interfaces.RSAPublicKey|java.security.interfaces.RSAPrivateKey");
		put("Signature.SHA256withRSA", "io.github.tshibata.sample.MySignature");
		put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256withRSA");
	}
}

Create a subclass of Provider called

Sample.java


package io.github.tshibata.sample;

import java.security.*;

public class Sample {
	static void test(byte[] data, KeyPair pair) throws Exception {
		Signature sig = Signature.getInstance("SHA256withRSA");
		//I'll sign
		sig.initSign(pair.getPrivate());
		sig.update(data);
		byte[] s = sig.sign();
		//I will output it in Base64
		System.out.println(java.util.Base64.getEncoder().encodeToString​(s));
		//I'll verify
		sig.initVerify(pair.getPublic());
		sig.update(data);
		System.out.println(sig.verify(s));
	}
	public static void main(String[] args) throws Exception {
		//I'll make a key
		KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
		KeyPair pair = gen.generateKeyPair();
		// "hello"I will sign and verify
		test("hello".getBytes(), pair);
		//I'll try using MyProvider
		Security.insertProviderAt(new MyProvider(), 1);
		test("hello".getBytes(), pair);
	}
}

I will register it before signing and verification. If the second argument of insertProviderAt is set to 1 in priority, it will be used with the highest priority.

Recommended Posts

Signing and validation using java.security.Provider
Spring validation and error code
Animation using matchedGeometryEffect and @Namespace
Password hashing and authentication using JBcrypt
Validation of log messages using mockito
[Rails] Validation settings and Japanese localization
Implementation of validation using regular expressions
Infrastructure monitoring using Graphite and StatsD
Installing and using Ansible on Ubuntu 16.04