Google/Tink: как использовать открытый ключ для проверки подписи

Мы хотим использовать библиотеку Tink в нашем проекте, чтобы иметь возможность проверять некоторые входящие подписи, используя открытый ключ.

Что у нас есть следующие:

  1. Открытый ключ в виде строки
  2. Сама подпись
  3. Открытый текст

Изучив документацию Tink, мы не можем понять, как загрузить строку открытого ключа, чтобы она могла использоваться PublicKeyVerifyFactory,

Кто-нибудь делал что-нибудь подобное? Вы нашли какие-либо примеры в Интернете, которые могли бы указать нам правильное направление?

0 ответов

Вы можете создать KeysetHandle от открытого ключа через CleartextKeysetHandle.read(), затем получите примитив и проверьте подпись. Для этого вам не нужно знать приватную часть KeysetHandle, что и является целью использования асимметричных ключей в первую очередь.

Вопрос в том, что мне экспортировать, чтобы использовать этот read()позже? Есть несколько способов, но один из них - экспортироватьPublicKeysetHandleв формат JSON. Вы экспортируете его, используяCleartextKeysetHandle.write() с JsonKeysetWriter.withOutputStream(), и позже вы можете преобразовать его обратно в KeysetHandle с помощью CleartextKeysetHandle.read() с JsonKeysetReader.withBytes().

Итак, вы Боб и хотите раскрыть свой открытый ключ Алисе. В своей службе вы сгенерируете свой закрытый ключ, извлечете открытый ключ, преобразуете его в формат JSON и каким-то образом экспортируете его, например, конечную точку REST:

Приложение Боба

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

Приложение Алисы

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

В приложении Боба секретный ключ генерируется каждый раз. Возможно, вы захотите использовать тот же закрытый ключ, поэтому вам нужно будет сохранить этот закрытый ключ и восстановить его так же, как приложение Алисы, но вместо этого, используя PublicKeysetHandle, вы должны использовать PrivateKeysetHandle. Приведенные выше примеры просто показывают, как экспортировать открытый ключ в строковый формат и восстановить его позже в другом приложении.

Некоторые примеры кода для иллюстрации:

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

Я предполагаю, что мой ответ будет слишком поздно для Алекса, но он может быть полезен другим. После большого количества кода и анализа ключей я написал решение, которое проверяет внешнюю сгенерированную подпись ECDSA с внешним сгенерированным открытым ключом ECDSA с крипто-подпрограммами Tink. Чтобы проверить это, существует вспомогательная программа, которая генерирует "внешнюю часть" с помощью обычных JCE-инструментов и сохраняет открытый ключ, сообщение и подпись в текстовом файле (все данные в кодировке Base64).

Мое решение загружает файл данных (всего есть 3 файла данных для тестирования всех 3 доступных ECDSA-Curves (P256, P384 и P521)). Затем он создает новый файл с открытым ключом в собственном JSON-формате Tink (опять же: 3 файла для 3 длин ключей) - этот файл является ручным решением, перезагружает ключевой файл и создает новую подпись, соответствующую Tink.

В итоге программа проверяет правильность подписей. Пожалуйста, имейте в виду, что мой исходный код является "Подтверждением концепции" и ни для чего не оптимизирован:-) Любые предложения по лучшему кодированию всегда запрашиваются!

Анализируя исходный код Tink, я увидел, что есть и "RAW"-форматы, но в документации Tink я не нашел ни слова, как их использовать:-(

Вы также можете найти полный исходный код в моем Github-Archive: https://github.com/java-crypto/H-Google-Tink и более подробное описание всех программ на моем веб-сайте http://javacrypto.bplaced. сеть / ч-проверка-внешняя-подпись-в-тинк /. Программы протестированы с 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);
    }

}

Тинк хранит открытые ключи в protobuf. На днях я напишу некоторый код, который позволяет конвертировать обычные форматы открытых ключей, такие как PEM или JWK, в protobuf, но до тех пор я боюсь, что вам придется писать код самостоятельно (и вносить свой вклад!).

Другие вопросы по тегам