Crea y valida firmas digitales

En este tema, se proporciona información sobre cómo crear y validar firmas digitales con base en claves asimétricas.

Una firma digital se crea usando la porción de la clave privada de una clave asimétrica. La firma se valida con la porción de la clave pública de la misma clave asimétrica.

Antes de comenzar

  • Si vas a crear firmas digitales, debes tener una clave con el propósito de clave adecuado. Cuando crees la clave, usa ASYMMETRIC_SIGN. Ten en cuenta el algoritmo completo que especifica la clave, ya que tendrás que pasar esa información al comando de OpenSSL para validar la firma.

  • Otorga el permiso cloudkms.cryptoKeyVersions.useToSign sobre la clave asimétrica al usuario o servicio que realizará la firma. Puedes obtener información sobre los permisos en Cloud KMS en Permisos y funciones.

  • Si vas a validar una firma, otorga el permiso cloudkms.cryptoKeyVersions.viewPublicKey sobre 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 en caso de no tenerlo aún. Si usas Cloud Shell, OpenSSL ya está instalado.

Crea una firma

Línea de comandos

A modo de ejemplo, crea un archivo llamado~/my-message-file con texto de muestra.

echo "my message to sign" > ~/my-message-file

Para firmar los datos, usa el siguiente comando:

gcloud kms asymmetric-sign \
--location [LOCATION] \
--keyring [KEY_RING] \
--key [KEY] \
--version [VERSION] \
--digest-algorithm sha256 \
--input-file ~/my-message-file \
--signature-file ~/my-signature-file

[KEY] debe ser una clave con el propósito ASYMMETRIC_SIGN.

El archivo de salida ~/my-signature-file contiene la firma binaria.

API

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

Go

import (
	"context"
	"crypto/sha256"
	"fmt"

	cloudkms "cloud.google.com/go/kms/apiv1"
	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

// signAsymmetric will sign a plaintext message using a saved asymmetric private key.
func signAsymmetric(name string, message []byte) ([]byte, error) {
	// name: "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
	// Note: some key algorithms will require a different hash function.
	// For example, EC_SIGN_P384_SHA384 requires SHA-384.
	ctx := context.Background()
	client, err := cloudkms.NewKeyManagementClient(ctx)
	if err != nil {
		return nil, fmt.Errorf("cloudkms.NewKeyManagementClient: %v", err)
	}
	// Find the digest of the message.
	digest := sha256.New()
	digest.Write(message)
	// Build the signing request.
	req := &kmspb.AsymmetricSignRequest{
		Name: name,
		Digest: &kmspb.Digest{
			Digest: &kmspb.Digest_Sha256{
				Sha256: digest.Sum(nil),
			},
		},
	}
	// Call the API.
	response, err := client.AsymmetricSign(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("AsymmetricSign: %v", err)
	}
	// Signature is base64 encoded.
	return response.Signature, nil
}

Java

/**
 *  Create a signature for a message using a private key stored on Cloud KMS
 *
 * Example keyName:
 *   "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
 */
public static byte[] signAsymmetric(String keyName, byte[] message)
    throws IOException, NoSuchAlgorithmException {
  // Create the Cloud KMS client.
  try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {

    // Note: some key algorithms will require a different hash function
    // For example, EC_SIGN_P384_SHA384 requires SHA-384
    byte[] messageHash = MessageDigest.getInstance("SHA-256").digest(message);

    AsymmetricSignRequest request = AsymmetricSignRequest.newBuilder()
        .setName(keyName)
        .setDigest(Digest.newBuilder().setSha256(ByteString.copyFrom(messageHash)))
        .build();

    AsymmetricSignResponse response = client.asymmetricSign(request);
    return response.getSignature().toByteArray();
  }
}

Python

def sign_asymmetric(message, key_name):
    """
    Create a signature for a message using a private key stored on Cloud KMS

    Example key_name:
      "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys\
              /KEY_ID/cryptoKeyVersions/1"

    Requires:
      hashlib
    """
    # Note: some key algorithms will require a different hash function
    # For example, EC_SIGN_P384_SHA384 requires SHA384
    client = kms_v1.KeyManagementServiceClient()
    digest_bytes = hashlib.sha256(message).digest()

    digest_json = {'sha256': digest_bytes}

    response = client.asymmetric_sign(key_name, digest_json)
    return response.signature

Valida una firma de curva elíptica

Línea de comandos

Si aún no la tienes, recupera la clave pública que se usó para crear la firma.

En este ejemplo, se supone que la clave pública está en un archivo llamado signing_key.pub y el mensaje que se firmó está en uno llamado my-message-file.

Los comandos de OpenSSL para validar la firma dependen del tipo de firma que se creó. Por ejemplo, para validar una firma de curva elíptica SHA-256 con OpenSSL, se usan los siguientes comandos:

openssl dgst -sha256 \
-verify ~/signing_key.pub \
-signature ~/my-signature-file ~/my-message-file

Si la firma es válida, deberías ver lo siguiente:

Verified OK

API

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

Go

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

	cloudkms "cloud.google.com/go/kms/apiv1"
	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

// verifySignatureEC will verify that an 'EC_SIGN_P256_SHA256' signature is valid for a given message.
func verifySignatureEC(name string, signature, message []byte) error {
	// name := "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
	ctx := context.Background()
	client, err := cloudkms.NewKeyManagementClient(ctx)
	if err != nil {
		return fmt.Errorf("cloudkms.NewKeyManagementClient: %v", err)
	}
	// Retrieve the public key from KMS.
	response, err := client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: name})
	if err != nil {
		return fmt.Errorf("GetPublicKey: %v", err)
	}
	// Parse the key.
	block, _ := pem.Decode([]byte(response.Pem))
	abstractKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return fmt.Errorf("x509.ParsePKIXPublicKey: %v", err)
	}
	ecKey, ok := abstractKey.(*ecdsa.PublicKey)
	if !ok {
		return fmt.Errorf("key '%s' is not EC", name)
	}
	// Verify Elliptic Curve signature.
	var parsedSig struct{ R, S *big.Int }
	if _, err = asn1.Unmarshal(signature, &parsedSig); err != nil {
		return fmt.Errorf("asn1.Unmarshal: %v", err)
	}
	hash := sha256.New()
	hash.Write(message)
	digest := hash.Sum(nil)
	if !ecdsa.Verify(ecKey, digest, parsedSig.R, parsedSig.S) {
		return fmt.Errorf("ecdsa.Verify failed on key: %s", name)
	}
	return nil
}

Java

/**
 * Verify the validity of an 'EC_SIGN_P256_SHA256' signature for the
 * specified message
 *
 * Example keyName:
 *   "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
 */
public static boolean verifySignatureEC(String keyName, byte[] message, byte[] signature)
    throws IOException, GeneralSecurityException {

  // Create the Cloud KMS client.
  try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
    // Get the public key
    com.google.cloud.kms.v1.PublicKey pub = client.getPublicKey(keyName);
    String pemKey = pub.getPem();
    pemKey = pemKey.replaceFirst("-----BEGIN PUBLIC KEY-----", "");
    pemKey = pemKey.replaceFirst("-----END PUBLIC KEY-----", "");
    pemKey = pemKey.replaceAll("\\s", "");
    byte[] derKey = BaseEncoding.base64().decode(pemKey);
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
    PublicKey ecKey = KeyFactory.getInstance("EC").generatePublic(keySpec);

    // Verify the 'EC_SIGN_P256_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(message);
    return ecVerify.verify(signature);
  }
}

Python

def verify_signature_ec(signature, message, key_name):
    """
    Verify the validity of an 'EC_SIGN_P256_SHA256' signature
    for the specified message

    Example key_name:
      "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys\
              /KEY_ID/cryptoKeyVersions/1"

    Requires:
      cryptography.exceptions.InvalidSignature
      cryptography.hazmat.primitives.asymmetric.ec
      cryptography.hazmat.primitives.asymmetric.utils
      cryptography.hazmat.primitives.hashes
      hashlib
    """
    # get the public key
    client = kms_v1.KeyManagementServiceClient()
    response = client.get_public_key(key_name)
    key_txt = response.pem.encode('ascii')
    public_key = serialization.load_pem_public_key(key_txt, default_backend())

    # get the digest of the message
    digest_bytes = hashlib.sha256(message).digest()

    try:
        # Attempt verification
        public_key.verify(signature,
                          digest_bytes,
                          ec.ECDSA(utils.Prehashed(hashes.SHA256())))
        # No errors were thrown. Verification was successful
        return True
    except InvalidSignature:
        return False

Valida una firma de RSA

Línea de comandos

Si aún no la tienes, recupera la clave pública que se usó para crear la firma.

En este ejemplo, se supone que la clave pública está en un archivo llamado signing_key.pub y el mensaje que se firmó está en uno llamado my-message-file.

Los comandos de OpenSSL para validar la firma dependen del tipo de firma que se creó. Para validar una firma de RSA SHA-256 con relleno de PSS mediante OpenSSL, se usan los siguientes comandos:

openssl dgst -sha256 \
-sigopt rsa_padding_mode:pss \
-sigopt rsa_pss_saltlen:-1 \
-signature ~/my-signature-file \
-verify ~/signing_key.pub ~/my-message-file

Si la firma es válida, deberías ver lo siguiente:

Verified OK

API

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

Go

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

	cloudkms "cloud.google.com/go/kms/apiv1"
	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

// verifySignatureRSA will verify that an 'RSA_SIGN_PSS_2048_SHA256' signature is valid for a given message.
func verifySignatureRSA(name string, signature, message []byte) error {
	// name: "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
	ctx := context.Background()
	client, err := cloudkms.NewKeyManagementClient(ctx)
	if err != nil {
		return fmt.Errorf("cloudkms.NewKeyManagementClient: %v", err)
	}
	// Retrieve the public key from KMS.
	response, err := client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: name})
	if err != nil {
		return fmt.Errorf("GetPublicKey: %v", err)
	}
	// Parse the key.
	block, _ := pem.Decode([]byte(response.Pem))
	abstractKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return fmt.Errorf("x509.ParsePKIXPublicKey: %v", err)
	}
	rsaKey, ok := abstractKey.(*rsa.PublicKey)
	if !ok {
		return fmt.Errorf("key '%s' is not RSA", name)
	}
	// Verify RSA signature.
	hash := sha256.New()
	hash.Write(message)
	digest := hash.Sum(nil)
	pssOptions := rsa.PSSOptions{SaltLength: len(digest), Hash: crypto.SHA256}
	if err = rsa.VerifyPSS(rsaKey, crypto.SHA256, digest, signature, &pssOptions); err != nil {
		return fmt.Errorf("rsa.VerifyPSS: %v", err)
	}
	return nil
}

Java

/**
 * Verify the validity of an 'RSA_SIGN_PKCS1_2048_SHA256' signature for the
 * specified message
 *
 * Example keyName:
 *   "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
 */
public static boolean verifySignatureRSA(String keyName, byte[] message, byte[] signature)
    throws IOException, GeneralSecurityException {

  // Create the Cloud KMS client.
  try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
    // Get the public key
    com.google.cloud.kms.v1.PublicKey pub = client.getPublicKey(keyName);
    String pemKey = pub.getPem();
    pemKey = pemKey.replaceFirst("-----BEGIN PUBLIC KEY-----", "");
    pemKey = pemKey.replaceFirst("-----END PUBLIC KEY-----", "");
    pemKey = pemKey.replaceAll("\\s", "");
    byte[] derKey = BaseEncoding.base64().decode(pemKey);
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
    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(message);
    return rsaVerify.verify(signature);
  }
}

Python

def verify_signature_rsa(signature, message, key_name):
    """
    Verify the validity of an 'RSA_SIGN_PSS_2048_SHA256' signature for the
    specified message

    Example key_name:
      "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys\
              /KEY_ID/cryptoKeyVersions/1"

    Requires:
      cryptography.exceptions.InvalidSignature
      cryptography.hazmat.primitives.asymmetric.padding
      cryptography.hazmat.primitives.asymmetric.utils
      cryptography.hazmat.primitives.hashes
      hashlib
    """
    # get the public key
    client = kms_v1.KeyManagementServiceClient()
    response = client.get_public_key(key_name)
    key_txt = response.pem.encode('ascii')
    public_key = serialization.load_pem_public_key(key_txt, default_backend())

    # get the digest of the message
    digest_bytes = hashlib.sha256(message).digest()

    try:
        # Attempt verification
        public_key.verify(signature,
                          digest_bytes,
                          padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
                                      salt_length=32),
                          utils.Prehashed(hashes.SHA256()))
        # No errors were thrown. Verification was successful
        return True
    except InvalidSignature:
        return False

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Documentación de Cloud KMS