Google / Tink:如何使用公钥验证签名

时间:2018-11-09 15:18:49

标签: java cryptography digital-signature public-key tink

我们希望在项目中使用Tink库,以便能够在给定公共密钥的情况下验证某些传入签名。

我们拥有以下内容:

  1. 公钥作为字符串
  2. 签名本身
  3. 纯文本

在阅读了Tink的文档之后,我们无法弄清楚如何加载公钥字符串,以便PublicKeyVerifyFactory可以使用它。

有人做过类似的事情吗?您在网上找到任何示例可以为我们指明正确的方向吗?

4 个答案:

答案 0 :(得分:1)

一些代码示例片段说明:

public static boolean verify(byte[] data, byte[] signature, KeysetHandle publicKeysetHandle, CIPHER_ASYMMETRIC_ALGOS algo_chosen) throws IOException, GeneralSecurityException {
    TinkConfig.register();
    boolean status_verification = False;


    try {
        PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive( publicKeysetHandle);
    verifier.verify(signature, data);
        status_verification = True;
    } catch (GeneralSecurityException e) {
       status_verification = False;
    }


    return status_verification;
}

//假设您已经具有以字节为单位的签名。

用法:



boolean status_verification = verify(data, signature, publicKeysetHandle);

if(status_verification == True){
    System.out.println(“status_verification: PASS”);
} else {
    System.out.println(“status_verification: FAIL”);
}

答案 1 :(得分:1)

您可以通过try { this.viewer.frame(); } catch (AccessViolationException exception) { Console.WriteLine(exception); } 通过公用密钥创建KeysetHandle,然后获取它的原始内容,然后验证签名。您不需要知道KeysetHandle的私有部分就可以做到这一点,这就是首先使用非对称密钥的关键所在。

问题是,我应该导出什么才能在以后使用此CleartextKeysetHandle.read()? 有几种方法,但是一种方法是将read()导出为JSON格式。您可以将PublicKeysetHandleCleartextKeysetHandle.write()一起导出,然后再通过JsonKeysetWriter.withOutputStream()KeysetHandle将其转换回CleartextKeysetHandle.read()

因此,您是Bob,并且想向Alice公开您的公钥。在您的服务中,您将生成私钥,提取公钥,将其转换为JSON格式,然后以某种方式导出,例如REST端点:

鲍勃的申请

JsonKeysetReader.withBytes()

爱丽丝的申请

SignatureConfig.register();

// This is your, and only yours, private key.
KeysetHandle privateKeysetHandle =
    KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256);

// This is the public key extracted from the private key.
KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();

ByteArrayOutputStream publicKeyStream = new ByteArrayOutputStream();

CleartextKeysetHandle.write(publicKeysetHandle,
    JsonKeysetWriter.withOutputStream(publicKeyStream));

// And this is the public key in JSON format.
// You can publish this in a REST endpoint.
return publicKeyStream.toString();

在Bob的应用程序中,每次都会生成私钥。您可能希望使用相同的私钥,因此您需要存储该私钥并像Alice的应用程序一样还原它,但是使用PublicKeysetHandle代替使用PublicKeysetHandle。上面的示例只是为了展示如何将公共密钥导出为字符串格式,然后在其他应用程序中将其还原。

答案 2 :(得分:0)

Tink将公共密钥存储在protobuf中。这些天之一,我将编写一些代码,以将诸如PEM或JWK之类的通用公钥格式转换为protobuf,但在那之前,我担心您将不得不自己编写代码(并做出贡献!)。

答案 3 :(得分:0)

我认为我的回答对于Alex来说为时已晚,但对其他人可能会有帮助。经过大量代码和密钥分析之后,我编写了一个解决方案,该解决方案使用外部生成的带有Tink密码例程的ECDSA公钥来验证外部生成的ECDSA签名。为了对此进行测试,有一个帮助程序,该程序使用常规的JCE工具生成“外部”,并将公共密钥,消息和签名保存在文本文件中(所有数据均经过Base64编码)。

我的解决方案加载了数据文件(总共有3个数据文件可以测试所有3个可用的ECDSA曲线(P256,P384和P521))。然后,它以Tink拥有的JSON格式创建了一个新的公钥文件(同样:3个文件,具有3个密钥长度)-该文件是手工制作的解决方案,重新加载密钥文件并构造一个新的签名,使其符合Tink。 >

最后,程序将验证签名正确无误。。请记住,我的源代码是“概念证明”,并未针对任何内容进行优化:-)任何有关更好编码的建议均应包含在文档中。总是要求!

在分析Tink源代码时,我看到也有“ RAW”格式,但是在Tink文档中,我找不到任何单词来使用它们:-(

您也可以在我的Github-Archive:https://github.com/java-crypto/H-Google-Tink中找到完整的源代码,并在我的网站http://javacrypto.bplaced.net/h-verify-external-signature-in-tink/上找到所有程序的更详细说明。 这些程序已通过Java 8-191和Java 11-0-1进行了测试。

package tinkExternalSignatureVerification;
/*
* Herkunft/Origin: http://javacrypto.bplaced.net/
* Programmierer/Programmer: Michael Fehr
* Copyright/Copyright: frei verwendbares Programm (Public Domain)
* Copyright: This is free and unencumbered software released into the public domain.
* Lizenttext/Licence: <http://unlicense.org>
* getestet mit/tested with: Java Runtime Environment 8 Update 191 x64
* getestet mit/tested with: Java Runtime Environment 11.0.1 x64
* Datum/Date (dd.mm.jjjj): 18.11.2019
* Funktion: überprüft eine extern erzeugte ecdsa-signatur mittels google tink
* Function: verifies an external generated ecdsa-signature with google tink
*
* Sicherheitshinweis/Security notice
* Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, 
* insbesondere mit Blick auf die Sicherheit ! 
* Prüfen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird.
* The program routines just show the function but please be aware of the security part - 
* check yourself before using in the real world !
* 
* Das Programm benötigt die nachfolgenden Bibliotheken (siehe Github Archiv):
* The programm uses these external libraries (see Github Archive):
* jar-Datei/-File: tink-1.2.2.jar
* https://mvnrepository.com/artifact/com.google.crypto.tink/tink/1.2.2
* jar-Datei/-File: protobuf-java-3.10.0.jar
* https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java/3.10.0
* jar-Datei/-File: json-20190722.jar
* https://mvnrepository.com/artifact/org.json/json/20190722
*  
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.JsonKeysetReader;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.PublicKeyVerify;
import com.google.crypto.tink.config.TinkConfig;
import com.google.crypto.tink.signature.PublicKeyVerifyFactory;

public class VerifyEcdsaTinkSignature {

    static String pubKeyString = "";
    static String messageString = "";
    static String signatureString = "";
    public static byte[] xRec = null; // x-value of recoded public key
    public static byte[] yRec = null; // y-value of recoded public key

    public static void main(String[] args) throws IOException, GeneralSecurityException {
        System.out.println("Verify a Classic ECDSA-signed message in Google Tink");
        TinkConfig.register();

        String publicKeyJsonFilenameTemplate = "ecdsa_tink_publickey_";
        String publicKeyJsonFilename = "";
        String filenameTemplate = "ecdsa_classic_data_";
        String filename;
        byte[] message = null;
        PublicKey pubKey;
        byte[] pubKeyByte = null;
        byte[] signatureClassic = null; // the signature from classic ecdsa
        boolean signatureVerification = false;
        int[] keylength = new int[] { 256, 384, 521 };
        // iterate through keylength
        for (int myKeylength : keylength) {
            filename = filenameTemplate + String.valueOf(myKeylength) + ".txt";
            publicKeyJsonFilename = publicKeyJsonFilenameTemplate + String.valueOf(myKeylength) + ".txt";
            pubKeyString = "";
            messageString = "";
            signatureString = "";
            // load data
            switch (myKeylength) {
            case 256: {
                loadData(filename);
                break;
            }
            case 384: {
                loadData(filename);
                break;
            }
            case 521: {
                loadData(filename);
                break;
            }
            default: {
                System.out.println("Error - signature keylength not supported");
                System.exit(0);
            }

            }
            // convert data from base64 to byte[]
            pubKeyByte = Base64.getDecoder().decode(pubKeyString);
            message = Base64.getDecoder().decode(messageString);
            signatureClassic = Base64.getDecoder().decode(signatureString);
            // rebuild publicKey
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pubKeyByte);
            pubKey = keyFactory.generatePublic(publicKeySpec);
            // get x + y value of public key
            returnPublicKeyXY(pubKey); // writes to variables xRec and yRec
            // construct a tink-style public key value for json-file
            byte[] keyValueClassic = generateKeyValue(myKeylength);
            String keyValueClassicString = Base64.getEncoder().encodeToString(keyValueClassic); // saved in value-field
                                                                                                // of json-file
            // save tink public key in json-format, gets the generated primaryKeyId
            int keyId = SaveJson.writeJson(publicKeyJsonFilename, keyValueClassicString);
            // construct a tink-style signature
            byte[] signatureTink = generateSignature(keyId, signatureClassic);
            // reload the self created public key
            KeysetHandle keysetHandle = CleartextKeysetHandle
                    .read(JsonKeysetReader.withFile(new File(publicKeyJsonFilename)));
            // verify signature
            signatureVerification = verifyMessage(keysetHandle, signatureTink, message);
            System.out.println("Data loaded from:" + filename + " The message is:" + new String(message, "UTF-8"));
            System.out.println("The provided signature is correct ?:" + signatureVerification);
        }
    }

    public static void loadData(String filenameLoad) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(filenameLoad));
        pubKeyString = reader.readLine();
        messageString = reader.readLine();
        signatureString = reader.readLine();
        reader.close();
    }

    public static String printHexBinary(byte[] bytes) {
        final char[] hexArray = "0123456789ABCDEF".toCharArray();
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    // source:
    // https://github.com/google/tink/blob/master/java/src/main/java/com/google/crypto/tink/subtle/EllipticCurves.java
    /**
     * Transforms a big integer to its minimal signed form, i.e., no extra zero byte
     * at the beginning except single one when the highest bit is set.
     */
    private static byte[] toMinimalSignedNumber(byte[] bs) {
        // Remove zero prefixes.
        int start = 0;
        while (start < bs.length && bs[start] == 0) {
            start++;
        }
        if (start == bs.length) {
            start = bs.length - 1;
        }

        int extraZero = 0;
        // If the 1st bit is not zero, add 1 zero byte.
        if ((bs[start] & 0x80) == 0x80) {
            // Add extra zero.
            extraZero = 1;
        }
        byte[] res = new byte[bs.length - start + extraZero];
        System.arraycopy(bs, start, res, extraZero, bs.length - start);
        return res;
    }

    public static void returnPublicKeyXY(PublicKey pub) {
        ECPublicKey key = (ECPublicKey) pub;
        ECPoint ecp = key.getW();
        BigInteger x = ecp.getAffineX();
        BigInteger y = ecp.getAffineY();
        // convert big integer to byte[]
        byte[] x_array = x.toByteArray();
        if (x_array[0] == 0) {
            byte[] tmp = new byte[x_array.length - 1];
            System.arraycopy(x_array, 1, tmp, 0, tmp.length);
            x_array = tmp;
        }
        byte[] y_array = y.toByteArray();
        if (y_array[0] == 0) {
            byte[] tmp = new byte[y_array.length - 1];
            System.arraycopy(y_array, 1, tmp, 0, tmp.length);
            y_array = tmp;
        }
        // some byte[] need an additional x00 in the beginning
        xRec = toMinimalSignedNumber(x_array);
        yRec = toMinimalSignedNumber(y_array);
    }

    public static byte[] generateKeyValue(int keylength) {
        // header depends on keylength
        byte[] header = null;
        switch (keylength) {
        case 256: {
            header = fromHexString("12060803100218021A"); // only for ECDSA_P256
            break;
        }
        case 384: {
            header = fromHexString("12060804100318021A"); // only for ECDSA_P384
            break;
        }
        case 521: {
            header = fromHexString("12060804100418021A"); // only for ECDSA_P521
            break;
        }
        }
        int x_length = xRec.length;
        int y_length = yRec.length;
        // build the value-field with public key in x-/y-notation
        byte[] x_header = new byte[] { (byte) x_length };
        byte[] y_preheader = fromHexString("22");
        byte[] y_header = new byte[] { (byte) y_length };
        // join arrays
        byte[] kv = new byte[header.length + x_header.length + xRec.length + +y_preheader.length + y_header.length
                + yRec.length];
        System.arraycopy(header, 0, kv, 0, header.length);
        System.arraycopy(x_header, 0, kv, header.length, x_header.length);
        System.arraycopy(xRec, 0, kv, (header.length + x_header.length), xRec.length);
        System.arraycopy(y_preheader, 0, kv, (header.length + x_header.length + xRec.length), y_preheader.length);
        System.arraycopy(y_header, 0, kv, (header.length + x_header.length + xRec.length + y_preheader.length),
                y_header.length);
        System.arraycopy(yRec, 0, kv,
                (header.length + x_header.length + xRec.length + y_preheader.length + y_header.length), yRec.length);
        return kv;
    }

    // this routine converts a Hex Dump String to a byte array
    private static byte[] fromHexString(final String encoded) {
        if ((encoded.length() % 2) != 0)
            throw new IllegalArgumentException("Input string must contain an even number of characters");
        final byte result[] = new byte[encoded.length() / 2];
        final char enc[] = encoded.toCharArray();
        for (int i = 0; i < enc.length; i += 2) {
            StringBuilder curr = new StringBuilder(2);
            curr.append(enc[i]).append(enc[i + 1]);
            result[i / 2] = (byte) Integer.parseInt(curr.toString(), 16);
        }
        return result;
    }

    public static byte[] generateSignature(int keyId, byte[] signatureByte) {
        byte[] header = fromHexString("01");
        // convert keyId from int to 4-byte byte[]
        byte[] keyIdBytes = ByteBuffer.allocate(4).putInt(keyId).array();
        // build the signature in tink-style with keyId included
        byte[] si = new byte[header.length + keyIdBytes.length + signatureByte.length];
        System.arraycopy(header, 0, si, 0, header.length);
        System.arraycopy(keyIdBytes, 0, si, header.length, keyIdBytes.length);
        System.arraycopy(signatureByte, 0, si, (header.length + keyIdBytes.length), signatureByte.length);
        return si;
    }

    public static boolean verifyMessage(KeysetHandle publicKeysetHandle, byte[] signature, byte[] message)
            throws UnsupportedEncodingException, GeneralSecurityException {
        Boolean verifiedBool = false;
        PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive(publicKeysetHandle);
        try {
            verifier.verify(signature, message);
            verifiedBool = true;
        } catch (GeneralSecurityException e) {
            verifiedBool = false;
        }
        return verifiedBool;
    }
}

您需要此附加帮助器类来保存JSON文件:

package tinkExternalSignatureVerification;

/*
 * Diese Klasse gehört zu VerifyEcdsaTinkSignature.java
 * This class belongs to VerifyEcdsaTinkSignature.java
 * Herkunft/Origin: http://javacrypto.bplaced.net/
 * Programmierer/Programmer: Michael Fehr
 * Copyright/Copyright: frei verwendbares Programm (Public Domain)
 * Copyright: This is free and unencumbered software released into the public domain.
 * Lizenttext/Licence: <http://unlicense.org>
 */

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.security.SecureRandom;

public class SaveJson {

    public static int writeJson(String filename, String value) throws IOException {
        BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
        int keyId = newKeyId();
        String str = "{";
        writer.write(str + "\n");
        str = "    \"primaryKeyId\": " + keyId + ",";
        writer.append(str + "\n");
        str = "    \"key\": [{";
        writer.append(str + "\n");
        str = "        \"keyData\": {";
        writer.append(str + "\n");
        str = "            \"typeUrl\": \"type.googleapis.com/google.crypto.tink.EcdsaPublicKey\",";
        writer.append(str + "\n");
        str = "            \"keyMaterialType\": \"ASYMMETRIC_PUBLIC\",";
        writer.append(str + "\n");
        str = "            \"value\": \"" + value + "\"";
        writer.append(str + "\n");
        str = "        },";
        writer.append(str + "\n");
        str = "        \"outputPrefixType\": \"TINK\",";
        writer.append(str + "\n");
        str = "        \"keyId\": " + keyId + ",";
        writer.append(str + "\n");
        str = "        \"status\": \"ENABLED\"";
        writer.append(str + "\n");
        str = "    }]";
        writer.append(str + "\n");
        str = "}";
        writer.append(str);
        writer.close();

        return keyId;
    }

    // routines for keyId
    private static int newKeyId() {
        int keyId = randPositiveInt();
        keyId = randPositiveInt();
        return keyId;
    }

    // source:
    // https://github.com/google/tink/blob/08405fb55ba695b60b41f7f9ae198e5748152604/java/src/main/java/com/google/crypto/tink/KeysetManager.java
    /** @return positive random int */
    private static int randPositiveInt() {
        SecureRandom secureRandom = new SecureRandom();
        byte[] rand = new byte[4];
        int result = 0;
        while (result == 0) {
            secureRandom.nextBytes(rand);
            result = ((rand[0] & 0x7f) << 24) | ((rand[1] & 0xff) << 16) | ((rand[2] & 0xff) << 8) | (rand[3] & 0xff);
        }
        return result;
    }
}

数据文件是由以下简短程序生成的:

package tinkExternalSignatureVerification;

/*
* Herkunft/Origin: http://javacrypto.bplaced.net/
* Programmierer/Programmer: Michael Fehr
* Copyright/Copyright: frei verwendbares Programm (Public Domain)
* Copyright: This is free and unencumbered software released into the public domain.
* Lizenttext/Licence: <http://unlicense.org>
* getestet mit/tested with: Java Runtime Environment 8 Update 191 x64
* getestet mit/tested with: Java Runtime Environment 11.0.1 x64
* Datum/Date (dd.mm.jjjj): 18.11.2019
* Funktion: erzeugt eine ecdsa-signatur mittels jce
* Function: generates an ecdsa-signature with jce
*
* Sicherheitshinweis/Security notice
* Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, 
* insbesondere mit Blick auf die Sicherheit ! 
* Prüfen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird.
* The program routines just show the function but please be aware of the security part - 
* check yourself before using in the real world !
*  
*/

import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Base64;

public class GenerateEcdsaClassicSignature {

    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException,
            InvalidKeyException, SignatureException, IOException {
        System.out.println("Generate a ECDSA Private-/PublicKey and signs a message");

        byte[] message = "This is the message".getBytes("utf-8");
        String messageString = "";
        String filenameTemplate = "ecdsa_classic_data_";
        String filename;
        byte[] signature = null;
        String signatureString = "";
        PrivateKey privKey;
        PublicKey pubKey;
        String pubKeyString = "";
        int[] keylength = new int[] { 256, 384, 521 };
        // iterate through keylength
        for (int myKeylength : keylength) {
            filename = filenameTemplate + String.valueOf(myKeylength) + ".txt";
            // generate keypair
            KeyPair keyPair = generateEcdsaClassicKeyPair(myKeylength);
            privKey = keyPair.getPrivate();
            pubKey = keyPair.getPublic();
            signature = null;
            // sign the message
            switch (myKeylength) {
            case 256: {
                signature = signEcdsaClassic(privKey, message, "SHA256withECDSA");
                break;
            }
            case 384: {
                signature = signEcdsaClassic(privKey, message, "SHA512withECDSA");
                break;
            }
            case 521: {
                signature = signEcdsaClassic(privKey, message, "SHA512withECDSA");
                break;
            }
            default: {
                System.out.println("Error - signature keylength not supported");
                System.exit(0);
            }

            }
            // convert data to base64
            pubKeyString = Base64.getEncoder().encodeToString(pubKey.getEncoded());
            messageString = Base64.getEncoder().encodeToString(message);
            signatureString = Base64.getEncoder().encodeToString(signature);
            // save data to file
            writeData(filename, pubKeyString, messageString, signatureString);
            System.out.println("Data written to:" + filename);
        }

    }

    public static KeyPair generateEcdsaClassicKeyPair(int keylengthInt)
            throws NoSuchAlgorithmException, NoSuchProviderException {
        KeyPairGenerator keypairGenerator = KeyPairGenerator.getInstance("EC");
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        keypairGenerator.initialize(keylengthInt, random);
        return keypairGenerator.generateKeyPair();
    }

    public static byte[] signEcdsaClassic(PrivateKey privateKey, byte[] message, String ecdsaHashtype)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
        Signature signature = Signature.getInstance(ecdsaHashtype);
        signature.initSign(privateKey);
        signature.update(message);
        byte[] sigByte = signature.sign();
        return sigByte;
    }

    public static void writeData(String filenameWrite, String pubKeyWrite, String messageWrite, String signatureWrite)
            throws IOException {
        FileWriter fw = new FileWriter(filenameWrite);
        fw.write(pubKeyWrite + "\n");
        fw.write(messageWrite + "\n");
        fw.write(signatureWrite + "\n");
        fw.write(
                "This file contains data in base64-format: publicKey, message, signature. Number in filename is keylength.");
        fw.flush();
        fw.close();
    }

}

最后-如果您想使用JCE验证数据文件,请使用以下程序:

package tinkExternalSignatureVerification;

/*
* Herkunft/Origin: http://javacrypto.bplaced.net/
* Programmierer/Programmer: Michael Fehr
* Copyright/Copyright: frei verwendbares Programm (Public Domain)
* Copyright: This is free and unencumbered software released into the public domain.
* Lizenttext/Licence: <http://unlicense.org>
* getestet mit/tested with: Java Runtime Environment 8 Update 191 x64
* getestet mit/tested with: Java Runtime Environment 11.0.1 x64
* Datum/Date (dd.mm.jjjj): 18.11.2019
* Funktion: überprüft eine ecdsa-signatur mittels jce
* Function: verifies an ecdsa-signature with jce
*
* Sicherheitshinweis/Security notice
* Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, 
* insbesondere mit Blick auf die Sicherheit ! 
* Prüfen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird.
* The program routines just show the function but please be aware of the security part - 
* check yourself before using in the real world !
*  
*/

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class VerifyEcdsaClassicSignature {

    static String pubKeyString = "";
    static String messageString = "";
    static String signatureString = "";

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException,
            InvalidKeyException, SignatureException {
        System.out.println("Verify a ECDSA-signed message");
        String filenameTemplate = "ecdsa_classic_data_";
        String filename;
        byte[] message = null;
        PublicKey pubKey;
        byte[] pubKeyByte = null;
        byte[] signature = null;
        String ecdsaHashtype = "";
        boolean signatureVerification = false;
        int[] keylength = new int[] { 256, 384, 521 };
        // iterate through keylength
        for (int myKeylength : keylength) {
            filename = filenameTemplate + String.valueOf(myKeylength) + ".txt";
            pubKeyString = "";
            messageString = "";
            signatureString = "";
            // load data
            switch (myKeylength) {
            case 256: {
                loadData(filename);
                ecdsaHashtype = "SHA256withECDSA";
                break;
            }
            case 384: {
                loadData(filename);
                ecdsaHashtype = "SHA512withECDSA";
                break;
            }
            case 521: {
                loadData(filename);
                ecdsaHashtype = "SHA512withECDSA";
                break;
            }
            default: {
                System.out.println("Error - signature keylength not supported");
                System.exit(0);
            }

            }
            // convert data from base64 to byte[]
            pubKeyByte = Base64.getDecoder().decode(pubKeyString);
            message = Base64.getDecoder().decode(messageString);
            signature = Base64.getDecoder().decode(signatureString);
            // rebuild publicKey
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pubKeyByte);
            pubKey = keyFactory.generatePublic(publicKeySpec);
            // verify signature
            signatureVerification = verifySignature(pubKey, ecdsaHashtype, message, signature);
            System.out.println("Data loaded from:" + filename + " The message is:" + new String(message, "UTF-8"));
            System.out.println("The provided signature is correct ?:" + signatureVerification);
        }
    }

    public static void loadData(String filenameLoad) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(filenameLoad));
        pubKeyString = reader.readLine();
        messageString = reader.readLine();
        signatureString = reader.readLine();
        reader.close();
    }

    public static Boolean verifySignature(PublicKey publicKey, String ecdsaHashtype, byte[] messageByte,
            byte[] signatureByte) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        Signature publicSignature = Signature.getInstance(ecdsaHashtype);
        publicSignature.initVerify(publicKey);
        publicSignature.update(messageByte);
        return publicSignature.verify(signatureByte);
    }

}