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 alpha 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

func signAsymmetric(ctx context.Context, client *cloudkms.Service, keyPath string, message []byte) ([]byte, error) {
	// Note: some key algorithms will require a different hash function.
	// For example, EC_SIGN_P384_SHA384 requires SHA-384.
	digest := sha256.New()
	digest.Write(message)
	digestStr := base64.StdEncoding.EncodeToString(digest.Sum(nil))

	asymmetricSignRequest := &cloudkms.AsymmetricSignRequest{
		Digest: &cloudkms.Digest{
			Sha256: digestStr,
		},
	}

	response, err := client.Projects.Locations.KeyRings.CryptoKeys.CryptoKeyVersions.
		AsymmetricSign(keyPath, asymmetricSignRequest).Context(ctx).Do()
	if err != nil {
		return nil, fmt.Errorf("asymmetric sign request failed: %+v", err)

	}
	return base64.StdEncoding.DecodeString(response.Signature)
}

Java

/** Create a signature for a message using a private key stored on Cloud KMS 
  *
  * Requires:
  *   java.security.MessageDigest
  *   java.util.Base64
  */
public static byte[] signAsymmetric(byte[] message, CloudKMS client, String keyPath)
    throws IOException, NoSuchAlgorithmException {
  Digest digest = new Digest();
  // Note: some key algorithms will require a different hash function
  // For example, EC_SIGN_P384_SHA384 requires SHA-384
  digest.encodeSha256(MessageDigest.getInstance("SHA-256").digest(message));

  AsymmetricSignRequest signRequest = new AsymmetricSignRequest();
  signRequest.setDigest(digest);

  AsymmetricSignResponse response = client.projects()
                                          .locations()
                                          .keyRings()
                                          .cryptoKeys()
                                          .cryptoKeyVersions()
                                          .asymmetricSign(keyPath, signRequest)
                                          .execute();
  return Base64.getMimeDecoder().decode(response.getSignature());
}

Python

def signAsymmetric(message, client, key_path):
    """
    Create a signature for a message using a private key stored on Cloud KMS

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

    digest_JSON = {'sha256': digest64.decode('utf-8')}
    request = client.projects() \
                    .locations() \
                    .keyRings() \
                    .cryptoKeys() \
                    .cryptoKeyVersions() \
                    .asymmetricSign(name=key_path,
                                    body={'digest': digest_JSON})
    response = request.execute()
    return base64.b64decode(response.get('signature', None))

Validating an elliptic curve signature

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

Command-line

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

func verifySignatureEC(ctx context.Context, client *cloudkms.Service, keyPath string, signature, message []byte) error {
	abstractKey, err := getAsymmetricPublicKey(ctx, client, keyPath)
	if err != nil {
		return err
	}
	// Perform type assertion to get the elliptic curve key.
	ecKey := abstractKey.(*ecdsa.PublicKey)
	var parsedSig struct{ R, S *big.Int }
	_, err = asn1.Unmarshal(signature, &parsedSig)
	if err != nil {
		return fmt.Errorf("failed to parse signature bytes: %+v", err)
	}

	digest := sha256.New()
	digest.Write(message)
	hash := digest.Sum(nil)

	if !ecdsa.Verify(ecKey, hash, parsedSig.R, parsedSig.S) {
		return errors.New("signature verification failed")
	}
	return nil
}

Java

/** 
 * Verify the validity of an 'EC_SIGN_P256_SHA256' signature for the 
 * specified message
 * 
 * Requires:
 *   java.security.PublicKey
 *   java.security.Security
 *   java.security.Signature
 *   org.bouncycastle.jce.provider.BouncyCastleProvider
 */
public static boolean verifySignatureEC(byte[] signature, byte[] message, CloudKMS client, 
    String keyPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, 
    SignatureException, NoSuchProviderException, InvalidKeyException {
  Security.addProvider(new BouncyCastleProvider());
  PublicKey ecKey = getAsymmetricPublicKey(client, keyPath);

  Signature ecVerify = Signature.getInstance("SHA256withECDSA", "BC");

  ecVerify.initVerify(ecKey);
  ecVerify.update(message);
  return ecVerify.verify(signature);
}

Python

def verifySignatureEC(signature, message, client, key_path):
    """
    Verify the validity of an 'EC_SIGN_P256_SHA256' signature
    for the specified message

    Requires:
      cryptography.exceptions.InvalidSignature
      cryptography.hazmat.primitives.asymmetric.ec
      cryptography.hazmat.primitives.asymmetric.utils
      cryptography.hazmat.primitives.hashes
      hashlib
    """
    public_key = getAsymmetricPublicKey(client, key_path)
    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

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

Command-line

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

func verifySignatureRSA(ctx context.Context, client *cloudkms.Service, keyPath string, signature, message []byte) error {
	abstractKey, err := getAsymmetricPublicKey(ctx, client, keyPath)
	if err != nil {
		return err
	}
	// Perform type assertion to get the RSA key.
	rsaKey := abstractKey.(*rsa.PublicKey)
	digest := sha256.New()
	digest.Write(message)
	hash := digest.Sum(nil)

	pssOptions := rsa.PSSOptions{SaltLength: len(hash), Hash: crypto.SHA256}
	err = rsa.VerifyPSS(rsaKey, crypto.SHA256, hash, signature, &pssOptions)
	if err != nil {
		return fmt.Errorf("signature verification failed: %+v", err)
	}
	return nil
}

Java

/**
 * Verify the validity of an 'RSA_SIGN_PSS_2048_SHA256' signature for the 
 * specified message
 *
 * Requires:
 *   java.security.PublicKey
 *   java.security.Security
 *   java.security.Signature
 *   org.bouncycastle.jce.provider.BouncyCastleProvider
 */
public static boolean verifySignatureRSA(byte[] signature, byte[] message, CloudKMS client, 
    String keyPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, 
    SignatureException, NoSuchProviderException, InvalidKeyException {
  Security.addProvider(new BouncyCastleProvider());
  PublicKey rsaKey = getAsymmetricPublicKey(client, keyPath);

  Signature rsaVerify = Signature.getInstance("SHA256withRSA/PSS");

  rsaVerify.initVerify(rsaKey);
  rsaVerify.update(message);
  return rsaVerify.verify(signature);
}

Python

def verifySignatureRSA(signature, message, client, key_path):
    """
    Verify the validity of an 'RSA_SIGN_PSS_2048_SHA256' signature for the
    specified message

    Requires:
      cryptography.exceptions.InvalidSignature
      cryptography.hazmat.primitives.asymmetric.padding
      cryptography.hazmat.primitives.asymmetric.utils
      cryptography.hazmat.primitives.hashes
      hashlib
    """
    public_key = getAsymmetricPublicKey(client, key_path)
    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

Was this page helpful? Let us know how we did:

Send feedback about...

Cloud KMS Documentation