简体   繁体   English

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

[英]Google/Tink: How use public key to verify signature

We want to use the Tink library in our project to be able to verify some incoming signatures given a public key.我们希望在我们的项目中使用Tink库,以便能够在给定公钥的情况下验证一些传入的签名。

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 .在阅读了 Tink 的文档后,我们无法弄清楚如何加载公钥字符串以便PublicKeyVerifyFactory可以使用它。

Has anybody done anything similar?有没有人做过类似的事情? Have you found any examples online that could point us to the right direction?您是否在网上找到任何可以为我们指明正确方向的示例?

You can create a KeysetHandle from a public key through CleartextKeysetHandle.read() , then get it's primitive and then verify the signature.您可以通过CleartextKeysetHandle.read()从公钥创建KeysetHandle ,然后获取它的原语,然后验证签名。 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.你不需要知道 KeysetHandle 的私有部分来做到这一点,这是首先使用非对称密钥的重点。

The question is, what should I export in order to use this read() later?问题是,我应该导出什么以便稍后使用这个read() There are several ways, but one way is to export the PublicKeysetHandle to a JSON format.有多种方法,但一种方法是将PublicKeysetHandle导出为 JSON 格式。 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() .导出它使用CleartextKeysetHandle.write()JsonKeysetWriter.withOutputStream()并且以后可以将其转换回KeysetHandle使用CleartextKeysetHandle.read()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:在您的服务中,您将生成您的私钥,提取公钥,将其转换为 JSON 格式并以某种方式导出,如 REST 端点:

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.在 Bob 的应用程序中,每次都会生成私钥。 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.您可能想要坚持使用相同的私钥,因此您需要存储该私钥并像 Alice 的应用程序一样恢复它,但您将使用 PrivateKeysetHandle,而不是使用 PublicKeysetHandle。 The examples above are just to show how to export the public key into a string format and restore it later in another application.上面的例子只是为了展示如何将公钥导出为字符串格式并稍后在另一个应用程序中恢复它。

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

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.经过大量代码和密钥分析后,我编写了一个解决方案,该解决方案使用外部生成的 ECDSA 公钥和 Tink 加密程序验证外部生成的 ECDSA 签名。 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).为了测试这一点,有一个帮助程序使用常规 JCE 工具生成“外部部分”,并将公钥、消息和签名保存在文本文件中(所有数据都是 Base64 编码的)。

My solution loads the datafile (there are 3 datafiles in total to test all 3 available ECDSA-Curves (P256, P384 and P521)).我的解决方案加载数据文件(总共有 3 个数据文件来测试所有 3 个可用的 ECDSA 曲线(P256、P384 和 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.然后它以 Tink 自己的 JSON 格式创建一个新的公钥文件(同样:3 个文件,3 个密钥长度)——这个文件是一个手工制作的解决方案,重新加载密钥文件并构建一个新的签名以符合 Tink 的要求。

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 :-(在分析 Tink 源代码时,我看到也有“RAW”格式,但在 Tink 文档中我找不到任何如何使用它们的词:-(

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/ .您也可以在我的 Github-Archive 中找到完整的源代码: https : //github.com/java-crypto/H-Google-Tink以及我的网站http://javacrypto.bplaced上对所有程序的更详细描述。 net/h-verify-external-signature-in-tink/ The programs are tested with Java 8-191 and Java 11-0-1.这些程序使用 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;
    }
}

You need this additional helper class to save the JSON-file:你需要这个额外的帮助类来保存 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;
    }
}

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:最后 - 如果您想使用 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);
    }

}

Tink stores public keys in protobuf. Tink 将公钥存储在 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!).这几天我将编写一些代码,允许将常见的公钥格式(例如 PEM 或 JWK)转换为 protobuf,但在此之前,我担心您必须自己编写代码(并做出贡献!)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM