6

We want to use the Tink library in our project to be able to verify some incoming signatures given a public key.

What we have are the following:

  1. The public key as a string
  2. The signature itself
  3. The plaintext

After going through Tink's documentation, we cannot figure out how to load the public key string so that it can be used by PublicKeyVerifyFactory.

Has anybody done anything similar? Have you found any examples online that could point us to the right direction?

Alex Ntousias
  • 8,264
  • 7
  • 34
  • 46
  • have you tried storing the public key in the export JSON format and trying to load it using `CleartextKeysetHandle.read`? – Jos Nov 09 '18 at 19:04
  • There is not a single public key format, so to answer the question fully it is required to publish the public key. – Maarten Bodewes Nov 10 '18 at 00:34
  • I am also searching for an easy way to do this, the closest thing I have found is the 'EllipticCurve' class in the 'com.google.crypto.tink.subtle' package. – hitch.united Mar 05 '19 at 00:57

4 Answers4

3

You can create a KeysetHandle from a public key through CleartextKeysetHandle.read(), then get it's primitive and then verify the signature. You don't need to know the private part of the KeysetHandle to do that, which is the point of using asymmetric keys in the first place.

The question is, what should I export in order to use this read() later? There are several ways, but one way is to export the PublicKeysetHandle to a JSON format. You export it using CleartextKeysetHandle.write() with JsonKeysetWriter.withOutputStream(), and you can later convert it back to a KeysetHandle using CleartextKeysetHandle.read() with JsonKeysetReader.withBytes().

So, you are Bob and want to public your public key to Alice. In your service you will generate your private key, extract the public key, convert it to a JSON format and export that in some way, like a REST endpoint:

Bob's application

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();

Alice's application

String publicKey = getThatJsonPublicKeyFromBobsEndpoint();

// Here the JSON with only the public key is converted into a KeysetHandle.
KeysetHandle keysetHandle = CleartextKeysetHandle
    .read(JsonKeysetReader.withBytes(publicKey.getBytes()));

// Getting the signature verifier from the public keyset handler.
PublicKeyVerify verifier = keysetHandle.getPrimitive(PublicKeyVerify.class);

// And finally verify Bob's signature for his message.
verifier.verify(bobsMessage.getSignature(), bobsMessage.getData());

In Bob's application the private key is being generated every time. You may want to stick with the same private key, so you'll need to store that private key and restore it just like the Alice's application, but instead using the PublicKeysetHandle you would use the PrivateKeysetHandle. The examples above are just to show how to export the public key into a string format and restore it later in another application.

Rafael Renan Pacheco
  • 1,202
  • 13
  • 18
1

Some code sample snippet to illustrate:

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

// Assuming you already have the signature in bytes.

Usage:



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

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

Ursa Major
  • 803
  • 6
  • 20
  • 46
1

I assume my answer will be too late for Alex but it might be helpfull to others. After a lot of code and key-analysing I wrote a solution that verifies an external generated ECDSA signature with an external generated ECDSA Public Key with Tink cryptoroutines. To test this there is a helper program that generates the "external part" with regular JCE-tools and saves the public key, the message and the signature in a textfile (all data is Base64-encoded).

My solution loads the datafile (there are 3 datafiles in total to test all 3 available ECDSA-Curves (P256, P384 and P521)). Then it creates a new Public Key-file in Tink-own JSON-format (again: 3 files for 3 key lengths) - this file is a handmade solution, reloads the keyfile and constructs a new signature to be Tink-conform.

In the end the program verifies the signatures as correct. Please keep in mind that my sourcecode is a "Proof of concept" and not optimized for anything :-) Any suggestions for a better coding is always requested!

While analysing the Tink sourcecode I saw that there are "RAW"-formats as well but in the Tink documentation I could not find any word how to use them :-(

You can find the complete sourcecode as well in my Github-Archive: https://github.com/java-crypto/H-Google-Tink and a more detailed description to all programs on my website http://javacrypto.bplaced.net/h-verify-external-signature-in-tink/. The programs are tested with Java 8-191 and 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;
    }
}

You need this additional helper class to save the JSON-file:

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

The datafiles are generated by this short program:

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

}

Last - if you would like to verify the datafiles with JCE use this program:

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

}
Michael Fehr
  • 3,946
  • 2
  • 9
  • 25
0

Tink stores public keys in protobuf. One of these days I'll write some code that allows converting common public key formats such as PEM or JWK to protobuf, but until then I'm afraid that you'll have to write the code yourself (and contribute!).

Roman Skydan
  • 3,620
  • 2
  • 15
  • 37
Thai Duong
  • 149
  • 7