LoginSignup
1
1

More than 3 years have passed since last update.

java.security.Providerを使った署名と検証

Last updated at Posted at 2019-10-14

java.security.Providerの使い方メモ

java.securityにはProviderという仕組みがあって暗号化や署名の実装を追加できるのですが、たぶんごく一部の人達しか使わないので、説明しているサイトもあまりありません。

まずは普通にJavaで署名して検証してみる

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");
        // 署名するよ
        sig.initSign(pair.getPrivate());
        sig.update(data);
        byte[] s = sig.sign();
        // Base64で出力してみるよ
        System.out.println(java.util.Base64.getEncoder().encodeToString(s));
        // 検証するよ
        sig.initVerify(pair.getPublic());
        sig.update(data);
        System.out.println(sig.verify(s));
    }
    public static void main(String[] args) throws Exception {
        // 鍵作るよ
        KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
        KeyPair pair = gen.generateKeyPair();
        // "hello"を署名して検証するよ
        test("hello".getBytes(), pair);
    }
}

Signatureのサブクラスを実装してみる

署名の場合はSignatureクラスが担当します。

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;
    }
}

鍵の長さは2048bit(256byte)固定にしました。
ハッシュを作ってパディング付けてexponent乗してmodulusで割った余りを計算するだけです。
パディングはおまじないというか決まり事です。

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

ってやって作りました。
「exponent乗してmodulusで割った余り」ってがRSAのキモで、昔偉い学者さん達が見つけました。
でこれを使ってもらうために

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

というProviderのサブクラスを作って

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");
        // 署名するよ
        sig.initSign(pair.getPrivate());
        sig.update(data);
        byte[] s = sig.sign();
        // Base64で出力してみるよ
        System.out.println(java.util.Base64.getEncoder().encodeToString(s));
        // 検証するよ
        sig.initVerify(pair.getPublic());
        sig.update(data);
        System.out.println(sig.verify(s));
    }
    public static void main(String[] args) throws Exception {
        // 鍵作るよ
        KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
        KeyPair pair = gen.generateKeyPair();
        // "hello"を署名して検証するよ
        test("hello".getBytes(), pair);
        // MyProviderを使ってみるよ
        Security.insertProviderAt(new MyProvider(), 1);
        test("hello".getBytes(), pair);
    }
}

という感じで署名や検証の前に登録しておきます。insertProviderAtの第2引数は優先順位で1にしておけば最優先で使われます。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1