Creating and validating digital signatures

This topic provides information about creating and validating digital signatures based on asymmetric keys.

A digital signature is created using the private key portion of an asymmetric key. The signature is validated using the public key portion of the same asymmetric key.

Before you begin

  • If you are going to create digital signatures, you need a key with the appropriate key purpose. When you create the key, use ASYMMETRIC_SIGN. Note the full algorithm specified by the key, because you will need to pass that information to the OpenSSL command to validate the signature.

  • Grant the cloudkms.cryptoKeyVersions.useToSign permission on the asymmetric key to the user or service that will perform the signing. You can learn about permissions in Key Management Service at Permissions and roles.

  • If you are going to validate a signature, grant cloudkms.cryptoKeyVersions.viewPublicKey permission on the asymmetric key to the user or service that will download the public key to use for validation.

  • If you are going to use the command line, install OpenSSL if you do not already have it. If you use Cloud Shell, OpenSSL is already installed.

Creating a signature

Command-line

For example purposes, create a file named ~/my-message-file with sample text.

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

To sign data:

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] must be a key with purpose of ASYMMETRIC_SIGN.

The output file ~/my-signature-file contains the binary signature.

API

Use the CryptoKeys.asymmetricSign method to perform the signing. The response from this method contains the base64-encoded signature.

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)
	}
	// Return the signature bytes.
	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

Validating an elliptic curve signature

Command-line

If you don't already have it, retrieve the public key that was used to create the signature.

This example assumes the public key is in a file named signing_key.pub and the message that was signed is in a file named my-message-file.

The OpenSSL commands to validate the signature depend on what signature type was created. For example, to validate a SHA-256 elliptic curve signature using OpenSSL:

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

If the signature is valid, you should see:

Verified OK

API

Use the CryptoKeyVersions.getPublicKey method to retrieve the public key, and then use the commands shown for the command-line example to validate the signature.

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

Validating an RSA signature

Command-line

If you don't already have it, retrieve the public key that was used to create the signature.

This example assumes the public key is in a file named signing_key.pub and the message that was signed is in a file named my-message-file.

The OpenSSL commands to validate the signature depend on what signature type was created. To validate a SHA-256 RSA signature with PSS padding, using OpenSSL:

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

If the signature is valid, you should see:

Verified OK

API

Use the CryptoKeyVersions.getPublicKey method to retrieve the public key, and then use the commands shown for the command-line example to validate the signature.

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

Оцените, насколько информация на этой странице была вам полезна:

Оставить отзыв о...

Текущей странице
Cloud KMS Documentation