Google/Tink: как использовать открытый ключ для проверки подписи
Мы хотим использовать библиотеку Tink в нашем проекте, чтобы иметь возможность проверять некоторые входящие подписи, используя открытый ключ.
Что у нас есть следующие:
- Открытый ключ в виде строки
- Сама подпись
- Открытый текст
Изучив документацию 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, но до тех пор я боюсь, что вам придется писать код самостоятельно (и вносить свой вклад!).