Crear y validar firmas digitales

En este tema se explica cómo crear y validar firmas digitales basadas en claves asimétricas.

Una firma digital se crea con la parte de clave privada de una clave asimétrica. La firma se valida mediante la parte de clave pública de la misma clave asimétrica.

Antes de empezar

  • Cuando crees firmas digitales, debes usar una clave que tenga el propósito de clave ASYMMETRIC_SIGN. Cuando crees la clave, usa ASYMMETRIC_SIGN.

  • Para validar una firma, debes conocer el algoritmo completo que se usó al crear la clave. Para seguir las instrucciones de la línea de comandos que aparecen más abajo y que usan el comando openssl, debes proporcionar esta información a esos comandos.

  • Concede el permiso cloudkms.cryptoKeyVersions.useToSign en la clave asimétrica al usuario o servicio que vaya a realizar la firma. Puedes consultar información sobre los permisos de Cloud Key Management Service en Permisos y roles.

  • Si vas a validar una firma, concede permiso cloudkms.cryptoKeyVersions.viewPublicKey en la clave asimétrica al usuario o servicio que descargará la clave pública para usarla en la validación.

  • Si vas a usar la línea de comandos, instala OpenSSL si aún no lo tienes. Si usas Cloud Shell, OpenSSL ya está instalado.

Datos frente a resumen

La entrada proporcionada para las solicitudes de AsymmetricSign se puede transferir a través del campo data o del campo digest. No se pueden especificar ambos campos al mismo tiempo. Hay algunos algoritmos que requieren el campo de datos, como los algoritmos sin procesar y la firma con una clave de Cloud External Key Manager.

Algoritmos sin procesar

Los algoritmos "sin formato", identificados por el prefijo RSA_SIGN_RAW_, son una variante de la firma PKCS #1 que omite la codificación en un DigestInfo. En la variante:

  • Se calcula un resumen del mensaje que se va a firmar.
  • El relleno PKCS #1 se aplica directamente al resumen.
  • Se calcula una firma del resumen con relleno mediante la clave privada RSA.

Para usar estos algoritmos, sigue estos pasos:

  • Los datos sin procesar deben proporcionarse (en lugar de un resumen) como parte del campo data.
  • Los datos tienen un límite de longitud de 11 bytes menos que el tamaño de la clave RSA. Por ejemplo, PKCS #1 con una clave RSA de 2048 bits puede firmar un máximo de 245 bytes.
  • Asigna el rol cloudkms.expertRawPKCS1 al usuario o servicio correspondiente. Puedes consultar información sobre los permisos de Cloud Key Management Service en Permisos y roles.

Si usas algoritmos sin procesar, también puedes firmar un tipo de resumen para el que no haya disponible un algoritmo predefinido. Por ejemplo, puedes usar una clave RSA_SIGN_RAW_2048 para firmar una estructura SHA-512 PKCS #1 DigestInfo que ya hayas calculado externamente. Este proceso da los mismos resultados que un algoritmo RSA_SIGN_PKCS1_2048_SHA512 estándar.

Compatibilidad con ECDSA para otros algoritmos hash

Nuestros algoritmos de firma ECDSA tienen el siguiente formato general:

EC_SIGN_ELLIPTIC_CURVE_[DIGEST_ALGORITHM]

DIGEST_ALGORITHM tiene el valor SHA256, SHA384 o SHA512. Como el hash se realiza antes de crear la firma, estos algoritmos de firma también se pueden usar con resúmenes distintos de SHA, como Keccak. Para usar un resumen de Keccak, proporcione un valor de hash de Keccak y use el algoritmo de resumen SHA con la misma longitud. Por ejemplo, puedes usar un resumen KECCAK256 en una solicitud con el algoritmo EC_SIGN_P256_SHA256.

Crear una firma

gcloud

Para usar Cloud KMS en la línea de comandos, primero instala o actualiza a la versión más reciente de la CLI de Google Cloud.

gcloud kms asymmetric-sign \
    --version key-version \
    --key key \
    --keyring key-ring \
    --location location \
    --digest-algorithm digest-algorithm \
    --input-file input-file \
    --signature-file signature-file

Sustituye key-version por la versión de la clave que quieras usar para firmar. Sustituye key por el nombre de la clave. Sustituye key-ring por el nombre del conjunto de claves en el que se encuentra la clave. Sustituye location por la ubicación de Cloud KMS del conjunto de claves. Sustituye digest-algorithm por el algoritmo que quieras usar. Omite digest-algorithm para enviar input-file a Cloud KMS para firmar. Sustituye input-file y signature-file por las rutas locales del archivo que se va a firmar y del archivo de firma.

Para obtener información sobre todas las marcas y los valores posibles, ejecuta el comando con la marca --help.

C#

Para ejecutar este código, primero debes configurar un entorno de desarrollo de C# e instalar el SDK de Cloud KMS para C#.


using Google.Cloud.Kms.V1;
using Google.Protobuf;
using System.Security.Cryptography;
using System.Text;

public class SignAsymmetricSample
{
    public byte[] SignAsymmetric(
      string projectId = "my-project", string locationId = "us-east1", string keyRingId = "my-key-ring", string keyId = "my-key", string keyVersionId = "123",
      string message = "Sample message")
    {
        // Create the client.
        KeyManagementServiceClient client = KeyManagementServiceClient.Create();

        // Build the key version name.
        CryptoKeyVersionName keyVersionName = new CryptoKeyVersionName(projectId, locationId, keyRingId, keyId, keyVersionId);

        // Convert the message into bytes. Cryptographic plaintexts and
        // ciphertexts are always byte arrays.
        byte[] plaintext = Encoding.UTF8.GetBytes(message);

        // Calculate the digest.
        SHA256 sha256 = SHA256.Create();
        byte[] hash = sha256.ComputeHash(plaintext);

        // Build the digest.
        //
        // Note: Key algorithms will require a varying hash function. For
        // example, EC_SIGN_P384_SHA384 requires SHA-384.
        Digest digest = new Digest
        {
            Sha256 = ByteString.CopyFrom(hash),
        };

        // Call the API.
        AsymmetricSignResponse result = client.AsymmetricSign(keyVersionName, digest);

        // Get the signature.
        byte[] signature = result.Signature.ToByteArray();

        // Return the result.
        return signature;
    }
}

Go

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Go e instalar el SDK de Go de Cloud KMS.

import (
	"context"
	"crypto/sha256"
	"fmt"
	"hash/crc32"
	"io"

	kms "cloud.google.com/go/kms/apiv1"
	"cloud.google.com/go/kms/apiv1/kmspb"
	"google.golang.org/protobuf/types/known/wrapperspb"
)

// signAsymmetric will sign a plaintext message using a saved asymmetric private
// key stored in Cloud KMS.
func signAsymmetric(w io.Writer, name string, message string) error {
	// name := "projects/my-project/locations/us-east1/keyRings/my-key-ring/cryptoKeys/my-key/cryptoKeyVersions/123"
	// message := "my message"

	// Create the client.
	ctx := context.Background()
	client, err := kms.NewKeyManagementClient(ctx)
	if err != nil {
		return fmt.Errorf("failed to create kms client: %w", err)
	}
	defer client.Close()

	// Convert the message into bytes. Cryptographic plaintexts and
	// ciphertexts are always byte arrays.
	plaintext := []byte(message)

	// Calculate the digest of the message.
	digest := sha256.New()
	if _, err := digest.Write(plaintext); err != nil {
		return fmt.Errorf("failed to create digest: %w", err)
	}

	// Optional but recommended: Compute digest's CRC32C.
	crc32c := func(data []byte) uint32 {
		t := crc32.MakeTable(crc32.Castagnoli)
		return crc32.Checksum(data, t)

	}
	digestCRC32C := crc32c(digest.Sum(nil))

	// Build the signing request.
	//
	// Note: Key algorithms will require a varying hash function. For example,
	// EC_SIGN_P384_SHA384 requires SHA-384.
	req := &kmspb.AsymmetricSignRequest{
		Name: name,
		Digest: &kmspb.Digest{
			Digest: &kmspb.Digest_Sha256{
				Sha256: digest.Sum(nil),
			},
		},
		DigestCrc32C: wrapperspb.Int64(int64(digestCRC32C)),
	}

	// Call the API.
	result, err := client.AsymmetricSign(ctx, req)
	if err != nil {
		return fmt.Errorf("failed to sign digest: %w", err)
	}

	// Optional, but recommended: perform integrity verification on result.
	// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
	// https://cloud.google.com/kms/docs/data-integrity-guidelines
	if result.VerifiedDigestCrc32C == false {
		return fmt.Errorf("AsymmetricSign: request corrupted in-transit")
	}
	if result.Name != req.Name {
		return fmt.Errorf("AsymmetricSign: request corrupted in-transit")
	}
	if int64(crc32c(result.Signature)) != result.SignatureCrc32C.Value {
		return fmt.Errorf("AsymmetricSign: response corrupted in-transit")
	}

	fmt.Fprintf(w, "Signed digest: %s", result.Signature)
	return nil
}

Java

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Java e instalar el SDK de Java de Cloud KMS.

import com.google.cloud.kms.v1.AsymmetricSignResponse;
import com.google.cloud.kms.v1.CryptoKeyVersionName;
import com.google.cloud.kms.v1.Digest;
import com.google.cloud.kms.v1.KeyManagementServiceClient;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;

public class SignAsymmetric {

  public void signAsymmetric() throws IOException, GeneralSecurityException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "your-project-id";
    String locationId = "us-east1";
    String keyRingId = "my-key-ring";
    String keyId = "my-key";
    String keyVersionId = "123";
    String message = "my message";
    signAsymmetric(projectId, locationId, keyRingId, keyId, keyVersionId, message);
  }

  // Get the public key associated with an asymmetric key.
  public void signAsymmetric(
      String projectId,
      String locationId,
      String keyRingId,
      String keyId,
      String keyVersionId,
      String message)
      throws IOException, GeneralSecurityException {
    // Initialize client that will be used to send requests. This client only
    // needs to be created once, and can be reused for multiple requests. After
    // completing all of your requests, call the "close" method on the client to
    // safely clean up any remaining background resources.
    try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
      // Build the key version name from the project, location, key ring, key,
      // and key version.
      CryptoKeyVersionName keyVersionName =
          CryptoKeyVersionName.of(projectId, locationId, keyRingId, keyId, keyVersionId);

      // Convert the message into bytes. Cryptographic plaintexts and
      // ciphertexts are always byte arrays.
      byte[] plaintext = message.getBytes(StandardCharsets.UTF_8);

      // Calculate the digest.
      MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
      byte[] hash = sha256.digest(plaintext);

      // Build the digest object.
      Digest digest = Digest.newBuilder().setSha256(ByteString.copyFrom(hash)).build();

      // Sign the digest.
      AsymmetricSignResponse result = client.asymmetricSign(keyVersionName, digest);

      // Get the signature.
      byte[] signature = result.getSignature().toByteArray();

      System.out.printf("Signature %s%n", signature);
    }
  }
}

Node.js

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Node.js e instalar el SDK de Node.js de Cloud KMS.

//
// TODO(developer): Uncomment these variables before running the sample.
//
// const projectId = 'your-project-id';
// const locationId = 'us-east1';
// const keyRingId = 'my-key-ring';
// const keyId = 'my-key';
// const versionId = '123';
// const message = Buffer.from('...');

// Imports the Cloud KMS library
const {KeyManagementServiceClient} = require('@google-cloud/kms');

// Instantiates a client
const client = new KeyManagementServiceClient();

// Build the version name
const versionName = client.cryptoKeyVersionPath(
  projectId,
  locationId,
  keyRingId,
  keyId,
  versionId
);

async function signAsymmetric() {
  // Create a digest of the message. The digest needs to match the digest
  // configured for the Cloud KMS key.
  const crypto = require('crypto');
  const hash = crypto.createHash('sha256');
  hash.update(message);
  const digest = hash.digest();

  // Optional but recommended: Compute digest's CRC32C.
  // Ensure fast-crc32c has been installed, `npm i fast-crc32c`.
  const crc32c = require('fast-crc32c');
  const digestCrc32c = crc32c.calculate(digest);

  // Sign the message with Cloud KMS
  const [signResponse] = await client.asymmetricSign({
    name: versionName,
    digest: {
      sha256: digest,
    },
    digestCrc32c: {
      value: digestCrc32c,
    },
  });

  // Optional, but recommended: perform integrity verification on signResponse.
  // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
  // https://cloud.google.com/kms/docs/data-integrity-guidelines
  if (signResponse.name !== versionName) {
    throw new Error('AsymmetricSign: request corrupted in-transit');
  }
  if (!signResponse.verifiedDigestCrc32c) {
    throw new Error('AsymmetricSign: request corrupted in-transit');
  }
  if (
    crc32c.calculate(signResponse.signature) !==
    Number(signResponse.signatureCrc32c.value)
  ) {
    throw new Error('AsymmetricSign: response corrupted in-transit');
  }

  // Example of how to display signature. Because the signature is in a binary
  // format, you need to encode the output before printing it to a console or
  // displaying it on a screen.
  const encoded = signResponse.signature.toString('base64');
  console.log(`Signature: ${encoded}`);

  return signResponse.signature;
}

return signAsymmetric();

PHP

Para ejecutar este código, primero debes consultar información sobre cómo usar PHP en Google Cloud e instalar el SDK de PHP de Cloud KMS.

use Google\Cloud\Kms\V1\AsymmetricSignRequest;
use Google\Cloud\Kms\V1\Client\KeyManagementServiceClient;
use Google\Cloud\Kms\V1\Digest;

function sign_asymmetric(
    string $projectId = 'my-project',
    string $locationId = 'us-east1',
    string $keyRingId = 'my-key-ring',
    string $keyId = 'my-key',
    string $versionId = '123',
    string $message = '...'
) {
    // Create the Cloud KMS client.
    $client = new KeyManagementServiceClient();

    // Build the key version name.
    $keyVersionName = $client->cryptoKeyVersionName($projectId, $locationId, $keyRingId, $keyId, $versionId);

    // Calculate the hash.
    $hash = hash('sha256', $message, true);

    // Build the digest.
    //
    // Note: Key algorithms will require a varying hash function. For
    // example, EC_SIGN_P384_SHA384 requires SHA-384.
    $digest = (new Digest())
        ->setSha256($hash);

    // Call the API.
    $asymmetricSignRequest = (new AsymmetricSignRequest())
        ->setName($keyVersionName)
        ->setDigest($digest);
    $signResponse = $client->asymmetricSign($asymmetricSignRequest);
    printf('Signature: %s' . PHP_EOL, $signResponse->getSignature());

    return $signResponse;
}

Python

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Python e instalar el SDK de Python de Cloud KMS.

# Import base64 for printing the ciphertext.
import base64

# Import hashlib for calculating hashes.
import hashlib

# Import the client library.
from google.cloud import kms


def sign_asymmetric(
    project_id: str,
    location_id: str,
    key_ring_id: str,
    key_id: str,
    version_id: str,
    message: str,
) -> kms.AsymmetricSignResponse:
    """
    Sign a message using the private key part of an asymmetric key.

    Args:
        project_id (string): Google Cloud project ID (e.g. 'my-project').
        location_id (string): Cloud KMS location (e.g. 'us-east1').
        key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring').
        key_id (string): ID of the key to use (e.g. 'my-key').
        version_id (string): Version to use (e.g. '1').
        message (string): Message to sign.

    Returns:
        AsymmetricSignResponse: Signature.
    """

    # Create the client.
    client = kms.KeyManagementServiceClient()

    # Build the key version name.
    key_version_name = client.crypto_key_version_path(
        project_id, location_id, key_ring_id, key_id, version_id
    )

    # Convert the message to bytes.
    message_bytes = message.encode("utf-8")

    # Calculate the hash.
    hash_ = hashlib.sha256(message_bytes).digest()

    # Build the digest.
    #
    # Note: Key algorithms will require a varying hash function. For
    # example, EC_SIGN_P384_SHA384 requires SHA-384.
    digest = {"sha256": hash_}

    # Optional, but recommended: compute digest's CRC32C.
    # See crc32c() function defined below.
    digest_crc32c = crc32c(hash_)

    # Call the API
    sign_response = client.asymmetric_sign(
        request={
            "name": key_version_name,
            "digest": digest,
            "digest_crc32c": digest_crc32c,
        }
    )

    # Optional, but recommended: perform integrity verification on sign_response.
    # For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
    # https://cloud.google.com/kms/docs/data-integrity-guidelines
    if not sign_response.verified_digest_crc32c:
        raise Exception("The request sent to the server was corrupted in-transit.")
    if not sign_response.name == key_version_name:
        raise Exception("The request sent to the server was corrupted in-transit.")
    if not sign_response.signature_crc32c == crc32c(sign_response.signature):
        raise Exception(
            "The response received from the server was corrupted in-transit."
        )
    # End integrity verification

    print(f"Signature: {base64.b64encode(sign_response.signature)!r}")
    return sign_response


def crc32c(data: bytes) -> int:
    """
    Calculates the CRC32C checksum of the provided data.
    Args:
        data: the bytes over which the checksum should be calculated.
    Returns:
        An int representing the CRC32C checksum of the provided bytes.
    """
    import crcmod  # type: ignore

    crc32c_fun = crcmod.predefined.mkPredefinedCrcFun("crc-32c")
    return crc32c_fun(data)

Ruby

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Ruby e instalar el SDK de Ruby de Cloud KMS.

# TODO(developer): uncomment these values before running the sample.
# project_id  = "my-project"
# location_id = "us-east1"
# key_ring_id = "my-key-ring"
# key_id      = "my-key"
# version_id  = "123"
# message     = "my message"

# Require the library.
require "google/cloud/kms"

# Require digest.
require "digest"

# Create the client.
client = Google::Cloud::Kms.key_management_service

# Build the key version name.
key_version_name = client.crypto_key_version_path project:            project_id,
                                                  location:           location_id,
                                                  key_ring:           key_ring_id,
                                                  crypto_key:         key_id,
                                                  crypto_key_version: version_id

# Calculate the hash.
#
# Note: Key algorithms will require a varying hash function. For
# example, EC_SIGN_P384_SHA384 requires SHA-384.
digest = { sha256: Digest::SHA256.digest(message) }

# Call the API.
sign_response = client.asymmetric_sign name: key_version_name, digest: digest
puts "Signature: #{Base64.strict_encode64 sign_response.signature}"

API

En estos ejemplos se usa curl como cliente HTTP para mostrar cómo se usa la API. Para obtener más información sobre el control de acceso, consulta el artículo sobre cómo acceder a la API Cloud KMS.

Usa el método CryptoKeyVersions.asymmetricSign para realizar la firma. La respuesta de este método contiene la firma codificada en base64.

Validar una firma de curva elíptica

gcloud

Para usar Cloud KMS en la línea de comandos, primero instala o actualiza a la versión más reciente de la CLI de Google Cloud.

Obtener la clave pública

gcloud kms keys versions get-public-key key-version \
    --key key \
    --keyring key-ring \
    --location location \
    --output-file output-file

Sustituye key-version por la versión de la clave. Sustituye key por el nombre de la clave. Sustituye key-ring por el nombre del conjunto de claves en el que se encuentra la clave. Sustituye location por la ubicación de Cloud KMS del conjunto de claves. Sustituye output-file por la ruta del archivo para guardar la clave pública en el sistema local.

Para obtener información sobre todas las marcas y los valores posibles, ejecuta el comando con la marca --help.

Verificar la firma

Los comandos de OpenSSL para validar la firma dependen del tipo de firma que se haya creado. Por ejemplo, para validar una firma de curva elíptica SHA-256 con OpenSSL, debes especificar -sha256. Para validar una firma de curva elíptica SHA-384, debes especificar -sha384.

openssl dgst \
    -sha256 \
    -verify public-key-file \
    -signature signature-file \
    message-file

Sustituye las variables por tus propios valores:

  • public-key-file. Ruta a un archivo que contiene la clave pública (por ejemplo, "./my-key.pub").

  • signature-file. Ruta a un archivo que contiene la firma que se va a verificar (por ejemplo, "./my-data.sig").

  • message-file. Ruta a un archivo que contiene el mensaje (por ejemplo, "./my-data.txt").

Si la firma es válida, el comando muestra la cadena Verified OK.

Para obtener información sobre todas las marcas y los valores posibles, ejecuta el comando con el subcomando help.

C#

Para ejecutar este código, primero debes configurar un entorno de desarrollo de C# e instalar el SDK de Cloud KMS para C#.


public class VerifyAsymmetricSignatureEcSample
{
    // Cloud KMS returns signatures in a DER-encoded format. .NET requires
    // signatures to be in IEEE 1363 format, and converting between these formats
    // is a few hundred lines of code.
    //
    // https://github.com/dotnet/runtime/pull/1612 exposes these helpers, but will
    // not be available until .NET 5. Until then, you will need to use an external
    // library or package to validate signatures.
}

Go

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Go e instalar el SDK de Go de Cloud KMS.

import (
	"context"
	"crypto/ecdsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/asn1"
	"encoding/pem"
	"fmt"
	"io"
	"math/big"

	kms "cloud.google.com/go/kms/apiv1"
	"cloud.google.com/go/kms/apiv1/kmspb"
)

// verifyAsymmetricSignatureEC will verify that an 'EC_SIGN_P256_SHA256' signature is
// valid for a given message.
func verifyAsymmetricSignatureEC(w io.Writer, name string, message, signature []byte) error {
	// name := "projects/my-project/locations/us-east1/keyRings/my-key-ring/cryptoKeys/my-key/cryptoKeyVersions/123"
	// message := "my message"
	// signature := []byte("...")  // Response from a sign request

	// Create the client.
	ctx := context.Background()
	client, err := kms.NewKeyManagementClient(ctx)
	if err != nil {
		return fmt.Errorf("failed to create kms client: %w", err)
	}
	defer client.Close()

	// Retrieve the public key from KMS.
	response, err := client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: name})
	if err != nil {
		return fmt.Errorf("failed to get public key: %w", err)
	}

	// Parse the public key. Note, this example assumes the public key is in the
	// ECDSA format.
	block, _ := pem.Decode([]byte(response.Pem))
	publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return fmt.Errorf("failed to parse public key: %w", err)
	}
	ecKey, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		return fmt.Errorf("public key is not elliptic curve")
	}

	// Verify Elliptic Curve signature.
	var parsedSig struct{ R, S *big.Int }
	if _, err = asn1.Unmarshal(signature, &parsedSig); err != nil {
		return fmt.Errorf("asn1.Unmarshal: %w", err)
	}

	digest := sha256.Sum256(message)
	if !ecdsa.Verify(ecKey, digest[:], parsedSig.R, parsedSig.S) {
		return fmt.Errorf("failed to verify signature")
	}
	fmt.Fprintf(w, "Verified signature!")
	return nil
}

Java

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Java e instalar el SDK de Java de Cloud KMS.

import com.google.cloud.kms.v1.CryptoKeyVersionName;
import com.google.cloud.kms.v1.KeyManagementServiceClient;
import com.google.cloud.kms.v1.PublicKey;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.stream.Collectors;

public class VerifyAsymmetricEc {

  public void verifyAsymmetricEc() throws IOException, GeneralSecurityException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "your-project-id";
    String locationId = "us-east1";
    String keyRingId = "my-key-ring";
    String keyId = "my-key";
    String keyVersionId = "123";
    String message = "my message";
    byte[] signature = null;
    verifyAsymmetricEc(projectId, locationId, keyRingId, keyId, keyVersionId, message, signature);
  }

  // Verify the signature of a message signed with an RSA key.
  public void verifyAsymmetricEc(
      String projectId,
      String locationId,
      String keyRingId,
      String keyId,
      String keyVersionId,
      String message,
      byte[] signature)
      throws IOException, GeneralSecurityException {
    // Initialize client that will be used to send requests. This client only
    // needs to be created once, and can be reused for multiple requests. After
    // completing all of your requests, call the "close" method on the client to
    // safely clean up any remaining background resources.
    try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
      // Build the name from the project, location, and key ring, key, and key version.
      CryptoKeyVersionName keyVersionName =
          CryptoKeyVersionName.of(projectId, locationId, keyRingId, keyId, keyVersionId);

      // Convert the message into bytes. Cryptographic plaintexts and
      // ciphertexts are always byte arrays.
      byte[] plaintext = message.getBytes(StandardCharsets.UTF_8);

      // Get the public key.
      PublicKey publicKey = client.getPublicKey(keyVersionName);

      // Convert the public PEM key to a DER key (see helper below).
      byte[] derKey = convertPemToDer(publicKey.getPem());
      X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
      java.security.PublicKey ecKey = KeyFactory.getInstance("EC").generatePublic(keySpec);

      // Verify the 'RSA_SIGN_PKCS1_2048_SHA256' signature.
      // For other key algorithms:
      // http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Signature
      Signature ecVerify = Signature.getInstance("SHA256withECDSA");
      ecVerify.initVerify(ecKey);
      ecVerify.update(plaintext);

      // Verify the signature.
      boolean verified = ecVerify.verify(signature);
      System.out.printf("Signature verified: %s", verified);
    }
  }

  // Converts a base64-encoded PEM certificate like the one returned from Cloud
  // KMS into a DER formatted certificate for use with the Java APIs.
  private byte[] convertPemToDer(String pem) {
    BufferedReader bufferedReader = new BufferedReader(new StringReader(pem));
    String encoded =
        bufferedReader
            .lines()
            .filter(line -> !line.startsWith("-----BEGIN") && !line.startsWith("-----END"))
            .collect(Collectors.joining());
    return Base64.getDecoder().decode(encoded);
  }
}

Node.js

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Node.js e instalar el SDK de Node.js de Cloud KMS.

//
// TODO(developer): Uncomment these variables before running the sample.
//
// const projectId = 'your-project-id';
// const locationId = 'us-east1';
// const keyRingId = 'my-key-ring';
// const keyId = 'my-key';
// const versionId = '1';
// const message = 'my message to verify';
// const signatureBuffer = Buffer.from('...');

// Imports the Cloud KMS library
const {KeyManagementServiceClient} = require('@google-cloud/kms');

// Instantiates a client
const client = new KeyManagementServiceClient();

// Build the key name
const versionName = client.cryptoKeyVersionPath(
  projectId,
  locationId,
  keyRingId,
  keyId,
  versionId
);

async function verifyAsymmetricSignatureEc() {
  // Get public key
  const [publicKey] = await client.getPublicKey({
    name: versionName,
  });

  // Create the verifier. The algorithm must match the algorithm of the key.
  const crypto = require('crypto');
  const verify = crypto.createVerify('sha256');
  verify.update(message);
  verify.end();

  // Build the key object
  const key = {
    key: publicKey.pem,
  };

  // Verify the signature using the public key
  const verified = verify.verify(key, signatureBuffer);
  return verified;
}

return verifyAsymmetricSignatureEc();

PHP

Para ejecutar este código, primero debes consultar información sobre cómo usar PHP en Google Cloud e instalar el SDK de PHP de Cloud KMS.

use Google\Cloud\Kms\V1\Client\KeyManagementServiceClient;
use Google\Cloud\Kms\V1\GetPublicKeyRequest;

function verify_asymmetric_ec(
    string $projectId = 'my-project',
    string $locationId = 'us-east1',
    string $keyRingId = 'my-key-ring',
    string $keyId = 'my-key',
    string $versionId = '123',
    string $message = '...',
    string $signature = '...'
): bool {
    // Create the Cloud KMS client.
    $client = new KeyManagementServiceClient();

    // Build the key version name.
    $keyVersionName = $client->cryptoKeyVersionName($projectId, $locationId, $keyRingId, $keyId, $versionId);

    // Get the public key.
    $getPublicKeyRequest = (new GetPublicKeyRequest())
        ->setName($keyVersionName);
    $publicKey = $client->getPublicKey($getPublicKeyRequest);

    // Verify the signature. The hash algorithm must correspond to the key
    // algorithm. The openssl_verify command returns 1 on success, 0 on falure.
    $verified = openssl_verify($message, $signature, $publicKey->getPem(), OPENSSL_ALGO_SHA256) === 1;
    printf('Signature verified: %s', $verified);

    return $verified;
}

Python

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Python e instalar el SDK de Python de Cloud KMS.

# Import hashlib.
import hashlib

# Import cryptographic helpers from the cryptography package.
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import utils

# Import the client library.
from google.cloud import kms


def verify_asymmetric_ec(
    project_id: str,
    location_id: str,
    key_ring_id: str,
    key_id: str,
    version_id: str,
    message: str,
    signature: str,
) -> bool:
    """
    Verify the signature of an message signed with an asymmetric EC key.

    Args:
        project_id (string): Google Cloud project ID (e.g. 'my-project').
        location_id (string): Cloud KMS location (e.g. 'us-east1').
        key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring').
        key_id (string): ID of the key to use (e.g. 'my-key').
        version_id (string): ID of the version to use (e.g. '1').
        message (string): Original message (e.g. 'my message')
        signature (bytes): Signature from a sign request.

    Returns:
        bool: True if verified, False otherwise

    """

    # Convert the message to bytes.
    message_bytes = message.encode("utf-8")

    # Create the client.
    client = kms.KeyManagementServiceClient()

    # Build the key version name.
    key_version_name = client.crypto_key_version_path(
        project_id, location_id, key_ring_id, key_id, version_id
    )

    # Get the public key.
    public_key = client.get_public_key(request={"name": key_version_name})

    # Extract and parse the public key as a PEM-encoded EC key.
    pem = public_key.pem.encode("utf-8")
    ec_key = serialization.load_pem_public_key(pem, default_backend())
    hash_ = hashlib.sha256(message_bytes).digest()

    # Attempt to verify.
    try:
        sha256 = hashes.SHA256()
        ec_key.verify(signature, hash_, ec.ECDSA(utils.Prehashed(sha256)))
        print("Signature verified")
        return True
    except InvalidSignature:
        print("Signature failed to verify")
        return False

Ruby

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Ruby e instalar el SDK de Ruby de Cloud KMS.

# TODO(developer): uncomment these values before running the sample.
# project_id  = "my-project"
# location_id = "us-east1"
# key_ring_id = "my-key-ring"
# key_id      = "my-key"
# version_id  = "123"
# message     = "my message"
# signature   = "..."

# Require the library.
require "google/cloud/kms"
require "openssl"

# Create the client.
client = Google::Cloud::Kms.key_management_service

# Build the key version name.
key_version_name = client.crypto_key_version_path project:            project_id,
                                                  location:           location_id,
                                                  key_ring:           key_ring_id,
                                                  crypto_key:         key_id,
                                                  crypto_key_version: version_id

# Get the public key.
public_key = client.get_public_key name: key_version_name

# Parse the public key.
ec_key = OpenSSL::PKey::EC.new public_key.pem

# Verify the signature.
verified = ec_key.verify "sha256", signature, message
puts "Verified: #{verified}"

API

En estos ejemplos se usa curl como cliente HTTP para mostrar cómo se usa la API. Para obtener más información sobre el control de acceso, consulta el artículo sobre cómo acceder a la API Cloud KMS.

Usa el método CryptoKeyVersions.getPublicKey para recuperar la clave pública y, a continuación, usa los comandos que se muestran en el ejemplo de línea de comandos para validar la firma.

Validar una firma RSA

gcloud

Para usar Cloud KMS en la línea de comandos, primero instala o actualiza a la versión más reciente de la CLI de Google Cloud.

Obtener la clave pública

gcloud kms keys versions get-public-key key-version \
    --key key \
    --keyring key-ring \
    --location location \
    --output-file output-file

Sustituye key-version por la versión de la clave. Sustituye key por el nombre de la clave. Sustituye key-ring por el nombre del conjunto de claves en el que se encuentra la clave. Sustituye location por la ubicación de Cloud KMS del conjunto de claves. Sustituye output-file por la ruta para guardar la clave pública en el sistema local.

Para obtener información sobre todas las marcas y los valores posibles, ejecuta el comando con la marca --help.

Verificar la firma

Los comandos de OpenSSL para validar la firma dependen del tipo de firma que se haya creado. Por ejemplo, para validar una firma RSA SHA-256 con relleno PSS, debes especificar -sha256 y -sigopt rsa_padding_mode:pss. Para validar una firma SHA-512 RSA con relleno PSS, debes especificar -sha512 y -sigopt rsa_padding_mode:pss.

openssl dgst \
    -sha256 \
    -sigopt rsa_padding_mode:pss \
    -sigopt rsa_pss_saltlen:-1 \
    -verify public-key-file \
    -signature signature-file \
    message-file

Sustituye las variables por tus propios valores:

  • public-key-file. Ruta a un archivo que contiene la clave pública (por ejemplo, "./my-key.pub").

  • signature-file. Ruta a un archivo que contiene la firma que se va a verificar (por ejemplo, "./my-data.sig").

  • message-file. Ruta a un archivo que contiene el mensaje (por ejemplo, "./my-data.txt").

Si la firma es válida, el comando muestra la cadena Verified OK.

Para obtener información sobre todas las marcas y los valores posibles, ejecuta el comando con el subcomando help.

C#

Para ejecutar este código, primero debes configurar un entorno de desarrollo de C# e instalar el SDK de Cloud KMS para C#.


using Google.Cloud.Kms.V1;
using System;
using System.Security.Cryptography;
using System.Text;

public class VerifyAsymmetricSignatureRsaSample
{
    public bool VerifyAsymmetricSignatureRsa(
      string projectId = "my-project", string locationId = "us-east1", string keyRingId = "my-key-ring", string keyId = "my-key", string keyVersionId = "123",
      string message = "my message",
      byte[] signature = null)
    {
        // Build the key version name.
        CryptoKeyVersionName keyVersionName = new CryptoKeyVersionName(projectId, locationId, keyRingId, keyId, keyVersionId);

        // Calculate the digest of the message.
        SHA256 sha256 = SHA256.Create();
        byte[] digest = sha256.ComputeHash(Encoding.UTF8.GetBytes(message));

        // Get the public key.
        KeyManagementServiceClient client = KeyManagementServiceClient.Create();
        PublicKey publicKey = client.GetPublicKey(keyVersionName);

        // Split the key into blocks and base64-decode the PEM parts.
        string[] blocks = publicKey.Pem.Split("-", StringSplitOptions.RemoveEmptyEntries);
        byte[] pem = Convert.FromBase64String(blocks[1]);

        // Create a new RSA key.
        RSA rsa = RSA.Create();
        rsa.ImportSubjectPublicKeyInfo(pem, out _);

        // Verify the signature.
        bool verified = rsa.VerifyHash(digest, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);

        // Return the result.
        return verified;
    }
}

Go

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Go e instalar el SDK de Go de Cloud KMS.

import (
	"context"
	"crypto"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io"

	kms "cloud.google.com/go/kms/apiv1"
	"cloud.google.com/go/kms/apiv1/kmspb"
)

// verifyAsymmetricSignatureRSA will verify that an 'RSA_SIGN_PSS_2048_SHA256' signature
// is valid for a given message.
func verifyAsymmetricSignatureRSA(w io.Writer, name string, message, signature []byte) error {
	// name := "projects/my-project/locations/us-east1/keyRings/my-key-ring/cryptoKeys/my-key/cryptoKeyVersions/123"
	// message := "my message"
	// signature := []byte("...")  // Response from a sign request

	// Create the client.
	ctx := context.Background()
	client, err := kms.NewKeyManagementClient(ctx)
	if err != nil {
		return fmt.Errorf("failed to create kms client: %w", err)
	}
	defer client.Close()

	// Retrieve the public key from KMS.
	response, err := client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: name})
	if err != nil {
		return fmt.Errorf("failed to get public key: %w", err)
	}

	// Parse the public key. Note, this example assumes the public key is in the
	// RSA format.
	block, _ := pem.Decode([]byte(response.Pem))
	publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return fmt.Errorf("failed to parse public key: %w", err)
	}
	rsaKey, ok := publicKey.(*rsa.PublicKey)
	if !ok {
		return fmt.Errorf("public key is not rsa")
	}

	// Verify the RSA signature.
	digest := sha256.Sum256(message)
	if err := rsa.VerifyPSS(rsaKey, crypto.SHA256, digest[:], signature, &rsa.PSSOptions{
		SaltLength: len(digest),
		Hash:       crypto.SHA256,
	}); err != nil {
		return fmt.Errorf("failed to verify signature: %w", err)
	}

	fmt.Fprint(w, "Verified signature!\n")
	return nil
}

Java

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Java e instalar el SDK de Java de Cloud KMS.

import com.google.cloud.kms.v1.CryptoKeyVersionName;
import com.google.cloud.kms.v1.KeyManagementServiceClient;
import com.google.cloud.kms.v1.PublicKey;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.stream.Collectors;

public class VerifyAsymmetricRsa {

  public void verifyAsymmetricRsa() throws IOException, GeneralSecurityException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "your-project-id";
    String locationId = "us-east1";
    String keyRingId = "my-key-ring";
    String keyId = "my-key";
    String keyVersionId = "123";
    String message = "my message";
    byte[] signature = null;
    verifyAsymmetricRsa(projectId, locationId, keyRingId, keyId, keyVersionId, message, signature);
  }

  // Verify the signature of a message signed with an RSA key.
  public void verifyAsymmetricRsa(
      String projectId,
      String locationId,
      String keyRingId,
      String keyId,
      String keyVersionId,
      String message,
      byte[] signature)
      throws IOException, GeneralSecurityException {
    // Initialize client that will be used to send requests. This client only
    // needs to be created once, and can be reused for multiple requests. After
    // completing all of your requests, call the "close" method on the client to
    // safely clean up any remaining background resources.
    try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
      // Build the name from the project, location, and key ring, key, and key version.
      CryptoKeyVersionName keyVersionName =
          CryptoKeyVersionName.of(projectId, locationId, keyRingId, keyId, keyVersionId);

      // Convert the message into bytes. Cryptographic plaintexts and
      // ciphertexts are always byte arrays.
      byte[] plaintext = message.getBytes(StandardCharsets.UTF_8);

      // Get the public key.
      PublicKey publicKey = client.getPublicKey(keyVersionName);

      // Convert the public PEM key to a DER key (see helper below).
      byte[] derKey = convertPemToDer(publicKey.getPem());
      X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
      java.security.PublicKey rsaKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);

      // Verify the 'RSA_SIGN_PKCS1_2048_SHA256' signature.
      // For other key algorithms:
      // http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Signature
      Signature rsaVerify = Signature.getInstance("SHA256withRSA");
      rsaVerify.initVerify(rsaKey);
      rsaVerify.update(plaintext);

      // Verify the signature.
      boolean verified = rsaVerify.verify(signature);
      System.out.printf("Signature verified: %s", verified);
    }
  }

  // Converts a base64-encoded PEM certificate like the one returned from Cloud
  // KMS into a DER formatted certificate for use with the Java APIs.
  private byte[] convertPemToDer(String pem) {
    BufferedReader bufferedReader = new BufferedReader(new StringReader(pem));
    String encoded =
        bufferedReader
            .lines()
            .filter(line -> !line.startsWith("-----BEGIN") && !line.startsWith("-----END"))
            .collect(Collectors.joining());
    return Base64.getDecoder().decode(encoded);
  }
}

Node.js

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Node.js e instalar el SDK de Node.js de Cloud KMS.

//
// TODO(developer): Uncomment these variables before running the sample.
//
// const projectId = 'your-project-id';
// const locationId = 'us-east1';
// const keyRingId = 'my-key-ring';
// const keyId = 'my-key';
// const versionId = '1';
// const message = 'my message to verify';
// const signatureBuffer = Buffer.from('...');

// Imports the Cloud KMS library
const {KeyManagementServiceClient} = require('@google-cloud/kms');

// Instantiates a client
const client = new KeyManagementServiceClient();

// Build the key name
const versionName = client.cryptoKeyVersionPath(
  projectId,
  locationId,
  keyRingId,
  keyId,
  versionId
);

async function verifyAsymmetricSignatureRsa() {
  // Get public key
  const [publicKey] = await client.getPublicKey({
    name: versionName,
  });

  // Create the verifier. The algorithm must match the algorithm of the key.
  const crypto = require('crypto');
  const verify = crypto.createVerify('sha256');
  verify.update(message);
  verify.end();

  // Build the key object
  const key = {
    key: publicKey.pem,
    padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
  };

  // Verify the signature using the public key
  const verified = verify.verify(key, signatureBuffer);
  return verified;
}

return verifyAsymmetricSignatureRsa();

PHP

Para ejecutar este código, primero debes consultar información sobre cómo usar PHP en Google Cloud e instalar el SDK de PHP de Cloud KMS.

function verify_asymmetric_rsa(
    string $projectId = 'my-project',
    string $locationId = 'us-east1',
    string $keyRingId = 'my-key-ring',
    string $keyId = 'my-key',
    string $versionId = '123',
    string $message = '...',
    string $signature = '...'
): void {
    // PHP has limited support for asymmetric encryption operations.
    // Specifically, openssl_public_encrypt() does not allow customizing
    // algorithms or padding. Thus, it is not currently possible to use PHP
    // core for asymmetric operations on RSA keys.
    //
    // Third party libraries like phpseclib may provide the required
    // functionality. Google does not endorse this external library.
}

Python

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Python e instalar el SDK de Python de Cloud KMS.


# Import hashlib.
import hashlib

# Import cryptographic helpers from the cryptography package.
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import utils

# Import the client library.
from google.cloud import kms


def verify_asymmetric_rsa(
    project_id: str,
    location_id: str,
    key_ring_id: str,
    key_id: str,
    version_id: str,
    message: str,
    signature: str,
) -> bool:
    """
    Verify the signature of an message signed with an asymmetric RSA key.

    Args:
        project_id (string): Google Cloud project ID (e.g. 'my-project').
        location_id (string): Cloud KMS location (e.g. 'us-east1').
        key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring').
        key_id (string): ID of the key to use (e.g. 'my-key').
        version_id (string): ID of the version to use (e.g. '1').
        message (string): Original message (e.g. 'my message')
        signature (bytes): Signature from a sign request.

    Returns:
        bool: True if verified, False otherwise

    """

    # Convert the message to bytes.
    message_bytes = message.encode("utf-8")

    # Create the client.
    client = kms.KeyManagementServiceClient()

    # Build the key version name.
    key_version_name = client.crypto_key_version_path(
        project_id, location_id, key_ring_id, key_id, version_id
    )

    # Get the public key.
    public_key = client.get_public_key(request={"name": key_version_name})

    # Extract and parse the public key as a PEM-encoded RSA key.
    pem = public_key.pem.encode("utf-8")
    rsa_key = serialization.load_pem_public_key(pem, default_backend())
    hash_ = hashlib.sha256(message_bytes).digest()

    # Attempt to verify.
    try:
        sha256 = hashes.SHA256()
        pad = padding.PKCS1v15()
        rsa_key.verify(signature, hash_, pad, utils.Prehashed(sha256))
        print("Signature verified")
        return True
    except InvalidSignature:
        print("Signature failed to verify")
        return False

Ruby

Para ejecutar este código, primero debes configurar un entorno de desarrollo de Ruby e instalar el SDK de Ruby de Cloud KMS.

# TODO(developer): uncomment these values before running the sample.
# project_id  = "my-project"
# location_id = "us-east1"
# key_ring_id = "my-key-ring"
# key_id      = "my-key"
# version_id  = "123"
# message     = "my message"
# signature   = "..."

# Require the library.
require "google/cloud/kms"
require "openssl"

# Create the client.
client = Google::Cloud::Kms.key_management_service

# Build the key version name.
key_version_name = client.crypto_key_version_path project:            project_id,
                                                  location:           location_id,
                                                  key_ring:           key_ring_id,
                                                  crypto_key:         key_id,
                                                  crypto_key_version: version_id

# Get the public key.
public_key = client.get_public_key name: key_version_name

# Parse the public key.
rsa_key = OpenSSL::PKey::RSA.new public_key.pem

# Verify the signature.
#
# Note: The verify_pss() method only exists in Ruby 2.5+.
verified = rsa_key.verify_pss "sha256", signature, message, salt_length: :digest, mgf1_hash: "sha256"
puts "Verified: #{verified}"

API

En estos ejemplos se usa curl como cliente HTTP para mostrar cómo se usa la API. Para obtener más información sobre el control de acceso, consulta el artículo sobre cómo acceder a la API Cloud KMS.

Usa el método CryptoKeyVersions.getPublicKey para obtener la clave pública y, a continuación, usa los comandos que se muestran en el ejemplo de línea de comandos para validar la firma.