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 la clave apropiado. Cuando crees la clave, usa ASYMMETRIC_SIGN. Toma nota de todo el algoritmo que especifica la clave, ya que tendrás que pasar esa información al comando de OpenSSL para validar la firma.

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

  • Si vas a validar una firma, concede el 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 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:

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

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

El archivo resultante ~/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 Base 64.

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

Valida una firma de curva elíptica

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

Línea de comandos

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 de qué tipo de firma 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

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

Valida una firma de RSA

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

Línea de comandos

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 de qué tipo de firma 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

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

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

Enviar comentarios sobre...

Documentación de Cloud KMS