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 Cloud KMS 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)
	}
	// 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

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

Bu sayfayı yararlı buldunuz mu? Lütfen görüşünüzü bildirin:

Şunun hakkında geri bildirim gönderin...