В большинстве случаев мы рекомендуем использовать примитив цифровой подписи с типом ключа ML_DSA_44 или ECDSA_P256.
Примитив цифровой подписи гарантирует, что никто не изменял ваши данные и что они были отправлены именно вами. Он асимметричный: для подписи данных используется закрытый ключ, а для проверки – открытый.
Ниже приведены примеры использования примитива цифровой подписи.
C++
// A utility for signing and verifying files using digital signatures. #include <iostream> #include <memory> #include <ostream> #include <string> #include "absl/flags/flag.h" #include "absl/flags/parse.h" #include "absl/log/absl_check.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "tink/config/global_registry.h" #include "util/util.h" #include "tink/keyset_handle.h" #include "tink/public_key_sign.h" #include "tink/public_key_verify.h" #include "tink/signature/signature_config.h" ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format"); ABSL_FLAG(std::string, mode, "", "Mode of operation (sign|verify)"); ABSL_FLAG(std::string, input_filename, "", "Filename to operate on"); ABSL_FLAG(std::string, signature_filename, "", "Path to the signature file"); namespace { using ::crypto::tink::KeysetHandle; using ::crypto::tink::PublicKeySign; using ::crypto::tink::PublicKeyVerify; constexpr absl::string_view kSign = "sign"; constexpr absl::string_view kVerify = "verify"; void ValidateParams() { // ... } } // namespace namespace tink_cc_examples { // Digital signature example CLI implementation. absl::Status DigitalSignatureCli(absl::string_view mode, const std::string& keyset_filename, const std::string& input_filename, const std::string& signature_filename) { absl::Status result = crypto::tink::SignatureConfig::Register(); if (!result.ok()) return result; // Read the keyset from file. absl::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle = ReadJsonCleartextKeyset(keyset_filename); if (!keyset_handle.ok()) return keyset_handle.status(); // Read the input. absl::StatusOr<std::string> input_file_content = ReadFile(input_filename); if (!input_file_content.ok()) return input_file_content.status(); if (mode == kSign) { absl::StatusOr<std::unique_ptr<PublicKeySign>> public_key_sign = (*keyset_handle) ->GetPrimitive<crypto::tink::PublicKeySign>( crypto::tink::ConfigGlobalRegistry()); if (!public_key_sign.ok()) return public_key_sign.status(); absl::StatusOr<std::string> signature = (*public_key_sign)->Sign(*input_file_content); if (!signature.ok()) return signature.status(); return WriteToFile(*signature, signature_filename); } else { // mode == kVerify absl::StatusOr<std::unique_ptr<PublicKeyVerify>> public_key_verify = (*keyset_handle) ->GetPrimitive<crypto::tink::PublicKeyVerify>( crypto::tink::ConfigGlobalRegistry()); if (!public_key_verify.ok()) return public_key_verify.status(); // Read the signature. absl::StatusOr<std::string> signature_file_content = ReadFile(signature_filename); if (!signature_file_content.ok()) return signature_file_content.status(); return (*public_key_verify) ->Verify(*signature_file_content, *input_file_content); } } } // namespace tink_cc_examples int main(int argc, char** argv) { absl::ParseCommandLine(argc, argv); ValidateParams(); std::string mode = absl::GetFlag(FLAGS_mode); std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename); std::string input_filename = absl::GetFlag(FLAGS_input_filename); std::string signature_filename = absl::GetFlag(FLAGS_signature_filename); std::clog << "Using keyset in " << keyset_filename << " to " << mode; if (mode == kSign) { std::clog << " file " << input_filename << "; the resulting signature is written to " << signature_filename << '\n'; } else { // mode == kVerify std::clog << " the signature in " << signature_filename << " over the content of " << input_filename << '\n'; } ABSL_CHECK_OK(tink_cc_examples::DigitalSignatureCli( mode, keyset_filename, input_filename, signature_filename)); return 0; }
Проложить маршрут
import ( "bytes" "fmt" "log" "github.com/tink-crypto/tink-go/v2/insecurecleartextkeyset" "github.com/tink-crypto/tink-go/v2/keyset" "github.com/tink-crypto/tink-go/v2/signature" ) func Example() { // A private keyset created with // "tinkey create-keyset --key-template=ECDSA_P256 --out private_keyset.cfg". // Note that this keyset has the secret key information in cleartext. privateJSONKeyset := `{ "key": [{ "keyData": { "keyMaterialType": "ASYMMETRIC_PRIVATE", "typeUrl": "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey", "value": "EkwSBggDEAIYAhogEiSZ9u2nDtvZuDgWgGsVTIZ5/V08N4ycUspTX0RYRrkiIHpEwHxQd1bImkyMvV2bqtUbgMh5uPSTdnUEGrPXdt56GiEA3iUi+CRN71qy0fOCK66xAW/IvFyjOGtxjppRhSFUneo=" }, "keyId": 611814836, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 611814836 }` // The corresponding public keyset created with // "tinkey create-public-keyset --in private_keyset.cfg" publicJSONKeyset := `{ "key": [{ "keyData": { "keyMaterialType": "ASYMMETRIC_PUBLIC", "typeUrl": "type.googleapis.com/google.crypto.tink.EcdsaPublicKey", "value": "EgYIAxACGAIaIBIkmfbtpw7b2bg4FoBrFUyGef1dPDeMnFLKU19EWEa5IiB6RMB8UHdWyJpMjL1dm6rVG4DIebj0k3Z1BBqz13beeg==" }, "keyId": 611814836, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 611814836 }` // Create a keyset handle from the cleartext private keyset in the previous // step. The keyset handle provides abstract access to the underlying keyset to // limit the access of the raw key material. // // WARNING: In practice, it is unlikely you will want to use a insecurecleartextkeyset, // as it implies that your key material is passed in cleartext, which is a security risk. // Consider encrypting it with a remote key in Cloud KMS, AWS KMS or HashiCorp Vault. // See https://developers.google.com/tink/key-management-overview. privateKeysetHandle, err := insecurecleartextkeyset.Read( keyset.NewJSONReader(bytes.NewBufferString(privateJSONKeyset))) if err != nil { log.Fatal(err) } // Retrieve the Signer primitive from privateKeysetHandle. signer, err := signature.NewSigner(privateKeysetHandle) if err != nil { log.Fatal(err) } // Use the primitive to sign a message. In this case, the primary key of the // keyset will be used (which is also the only key in this example). data := []byte("data") sig, err := signer.Sign(data) if err != nil { log.Fatal(err) } // Create a keyset handle from the keyset containing the public key. Because the // public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets]. publicKeysetHandle, err := keyset.ReadWithNoSecrets( keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset))) if err != nil { log.Fatal(err) } // Retrieve the Verifier primitive from publicKeysetHandle. verifier, err := signature.NewVerifier(publicKeysetHandle) if err != nil { log.Fatal(err) } if err = verifier.Verify(sig, data); err != nil { log.Fatal(err) } fmt.Printf("sig is valid") // Output: sig is valid }
Java
package signature; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.crypto.tink.InsecureSecretKeyAccess; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.PublicKeySign; import com.google.crypto.tink.PublicKeyVerify; import com.google.crypto.tink.RegistryConfiguration; import com.google.crypto.tink.TinkJsonProtoKeysetFormat; import com.google.crypto.tink.signature.SignatureConfig; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * A command-line utility for digitally signing and verifying a file. * * <p>It loads cleartext keys from disk - this is not recommended! * * <p>It requires the following arguments: * * <ul> * <li>mode: either 'sign' or 'verify'. * <li>key-file: Read the key material from this file. * <li>input-file: Read the input from this file. * <li>signature-file: name of the file containing a hexadecimal signature of the input file. */ public final class SignatureExample { public static void main(String[] args) throws Exception { if (args.length != 4) { System.err.printf("Expected 4 parameters, got %d\n", args.length); System.err.println( "Usage: java SignatureExample sign/verify key-file input-file signature-file"); System.exit(1); } String mode = args[0]; if (!mode.equals("sign") && !mode.equals("verify")) { System.err.println("Incorrect mode. Please select sign or verify."); System.exit(1); } Path keyFile = Paths.get(args[1]); byte[] msg = Files.readAllBytes(Paths.get(args[2])); Path signatureFile = Paths.get(args[3]); // Register all signature key types with the Tink runtime. SignatureConfig.register(); // Read the keyset into a KeysetHandle. KeysetHandle handle = TinkJsonProtoKeysetFormat.parseKeyset( new String(Files.readAllBytes(keyFile), UTF_8), InsecureSecretKeyAccess.get()); if (mode.equals("sign")) { // Get the primitive. PublicKeySign signer = handle.getPrimitive(RegistryConfiguration.get(), PublicKeySign.class); // Use the primitive to sign data. byte[] signature = signer.sign(msg); Files.write(signatureFile, signature); } else { byte[] signature = Files.readAllBytes(signatureFile); // Get the primitive. PublicKeyVerify verifier = handle.getPrimitive(RegistryConfiguration.get(), PublicKeyVerify.class); verifier.verify(signature, msg); } } private SignatureExample() {} }
Obj-C
Python
import tink from tink import secret_key_access from tink import signature def example(): """Sign and verify using digital signatures.""" # Register the signature key managers. This is needed to create # PublicKeySign and PublicKeyVerify primitives later. signature.register() # A private keyset created with # "tinkey create-keyset --key-template=ECDSA_P256 --out private_keyset.cfg". # Note that this keyset has the secret key information in cleartext. private_keyset = r"""{ "key": [{ "keyData": { "keyMaterialType": "ASYMMETRIC_PRIVATE", "typeUrl": "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey", "value": "EkwSBggDEAIYAhogEiSZ9u2nDtvZuDgWgGsVTIZ5/V08N4ycUspTX0RYRrkiIHpEwHxQd1bImkyMvV2bqtUbgMh5uPSTdnUEGrPXdt56GiEA3iUi+CRN71qy0fOCK66xAW/IvFyjOGtxjppRhSFUneo=" }, "keyId": 611814836, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 611814836 }""" # The corresponding public keyset created with # "tinkey create-public-keyset --in private_keyset.cfg" public_keyset = r"""{ "key": [{ "keyData": { "keyMaterialType": "ASYMMETRIC_PUBLIC", "typeUrl": "type.googleapis.com/google.crypto.tink.EcdsaPublicKey", "value": "EgYIAxACGAIaIBIkmfbtpw7b2bg4FoBrFUyGef1dPDeMnFLKU19EWEa5IiB6RMB8UHdWyJpMjL1dm6rVG4DIebj0k3Z1BBqz13beeg==" }, "keyId": 611814836, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 611814836 }""" # Create a keyset handle from the cleartext keyset in the previous # step. The keyset handle provides abstract access to the underlying keyset to # limit the exposure of accessing the raw key material. WARNING: In practice, # it is unlikely you will want to use tink.json_proto_keyset_format.parse, as # it implies that your key material is passed in cleartext which is a security # risk. private_keyset_handle = tink.json_proto_keyset_format.parse( private_keyset, secret_key_access.TOKEN ) # Retrieve the PublicKeySign primitive we want to use from the keyset # handle. sign_primitive = private_keyset_handle.primitive(signature.PublicKeySign) # Use the primitive to sign a message. In this case the primary key of the # keyset will be used (which is also the only key in this example). sig = sign_primitive.sign(b'msg') # Create a keyset handle from the keyset containing the public key. Because # this keyset does not contain any secrets, we can use # `parse_without_secret`. public_keyset_handle = tink.json_proto_keyset_format.parse_without_secret( public_keyset ) # Retrieve the PublicKeyVerify primitive we want to use from the keyset # handle. verify_primitive = public_keyset_handle.primitive(signature.PublicKeyVerify) # Use the primitive to verify that `sig` is valid signature for the message. # Verify finds the correct key in the keyset. If no key is found or # verification fails, it raises an error. verify_primitive.verify(sig, b'msg') # Note that we can also get the public keyset handle from the private keyset # handle. The verification works the same as above. public_keyset_handle2 = private_keyset_handle.public_keyset_handle() verify_primitive2 = public_keyset_handle2.primitive(signature.PublicKeyVerify) verify_primitive2.verify(sig, b'msg')
Цифровая подпись
Примитив цифровой подписи позволяет проверить, не были ли изменены ваши данные. Она обеспечивает подлинность и целостность подписанных данных, но не их конфиденциальность. Оно асимметричное, то есть использует пару ключей (открытый и закрытый).
Примитив "Цифровая подпись" имеет следующие свойства:
- Подлинность. Создать подпись, которую сможет проверить
PublicKeyVerify.Verify(signature, message), можно только с помощью закрытого ключа. - Асимметричная. Для создания и проверки подписи используются разные ключи. Это позволяет распространять открытый ключ для проверки подписей среди сторон, которые не могут создавать подписи самостоятельно.
Если асимметрия не нужна, используйте более простой и эффективный примитив MAC.
Функциональность цифровых подписей в Tink представлена парой примитивов:
- PublicKeySign для подписи данных.
- PublicKeyVerify для проверки подписи.
Если закрытый ключ хранится в месте, куда сообщение не может быть легко отправлено, например в HSM или KMS, ознакомьтесь с разделом Предварительное хеширование и подпись хеша, в котором описано, как разделить подпись на этап хеширования с использованием только открытого ключа и этап подписи с использованием только закрытого ключа.
Выберите тип ключа
В большинстве случаев мы рекомендуем использовать ML_DSA_44 или ECDSA_P256, но есть и другие варианты. Как правило, верно следующее:
- ML-DSA-44 защищен от квантовых атак. Мы активно внедряем его, и теперь он рекомендуется для языков программирования, где доступен.
В ближайшем будущем вам потребуется изменить тип ключа для следующих алгоритмов, не относящихся к постквантовым:
- ECDSA_P256 – самый распространенный вариант, который можно использовать по умолчанию. Однако обратите внимание, что подписи ECDSA изменяемы.
- Алгоритм ED25519 создает детерминированные подписи и обеспечивает более высокую производительность, чем ECDSA_P256.
- RSA_SSA_PKCS1_3072_SHA256_F4 создает детерминированные подписи и обеспечивает лучшую производительность при проверке (но подписание выполняется гораздо медленнее, чем при использовании ECDSA_P256 или ED25519).
Минимальные гарантии безопасности
- Данные, которые нужно подписать, могут иметь произвольную длину.
- Уровень безопасности 128 бит против адаптивных атак с выбранным сообщением для схем на основе эллиптических кривых
- Уровень безопасности 112 бит против адаптивных атак с выбранным сообщением для схем на основе RSA (допускаются 2048-битные ключи)
Гибкость
Схема подписи считается гибкой, если злоумышленник может создать другую действительную подпись для уже подписанного сообщения. В большинстве случаев это не приводит к проблемам, но иногда программисты неявно предполагают, что действительные подписи уникальны, и это может привести к неожиданным результатам.