Digital signature in KMS
Digital signature is a product of a cryptographic operation that provides additional data protection. Digital signatures serve these main purposes:
- Validating data
- Checking data integrity
- Protecting data against modification
- Identifying the data source
The digital signature algorithm supports two operations: creating a signature and verifying a signature.
Digital signatures are based on asymmetric cryptography. An asymmetric key pair of a digital signature consists of two parts: a public key and a private key. The private key is used to create a signature and the public key to verify it.
You can use a digital signature to validate the source code, binary files, and container images. For example, you can validate an image signed with a digital signature. If the verification shows that the signature is invalid, it means that the image was changed or damaged. You can also use a digital signature to verify the subject of a certificate issued by a Certificate Authority
Each key pair counts towards KMS quotas as a single key.
Using a digital signature
The digital signature process actors are the owner of a key pair's private key and the recipients of signed data. Digital signatures are created and verified as follows:
- The signature owner creates an asymmetric key pair with digital signature support.
- The owner creates a digital signature for their own data. At this step, a hash value of user data is calculated, which is then signed by the private key based on a specified algorithm. The employed hash function is specified in the name of the algorithm.
- The signature owner transmits the data, the digital signature, and the public key of the asymmetric key pair to a recipient.
- The recipient uses the public key to verify the digital signature.
- If the hash decrypted by the recipient matches that of the data, it means the signature is correct.
Supported digital signature algorithms
KMS provides ECDSA
RSA_2048_SIGN_PSS_SHA_256
RSA_2048_SIGN_PSS_SHA_384
RSA_2048_SIGN_PSS_SHA_512
RSA_3072_SIGN_PSS_SHA_256
RSA_3072_SIGN_PSS_SHA_384
RSA_3072_SIGN_PSS_SHA_512
RSA_4096_SIGN_PSS_SHA_256
RSA_4096_SIGN_PSS_SHA_384
RSA_4096_SIGN_PSS_SHA_512
ECDSA_NIST_P256_SHA_256
ECDSA_NIST_P384_SHA_384
ECDSA_NIST_P521_SHA_512
ECDSA_NIST_SECP256_K1_SHA_256
Verifying digital signatures
Digital signature verification is performed by the side that has no access to the digital signature's private key. The signature is verified using the public key.
ECDSA signature
To verify a digital signature:
-
Get the public key of the digital signature.
-
Perform verification:
BashJavaGoPythonVerify the digital signature using OpenSSL
:openssl dgst \ -<hashing_algorithm> \ -verify <path_to_public_key_file> \ -signature <path_to_signature_file> \ <path_to_signed_file>
Where:
<hashing_algorithm>
: Hashing algorithm used when creating a signature key pair. The possible values include:sha256
for SHA-256 algorithmssha384
for SHA-384 algorithmssha512
for SHA-512 algorithms
-verify
: Path to the file with a public signature key.-signature
: Path to the digital signature file.<path_to_signed_file>
: Path to the file whose digital signature is being verified.
If the signature is correct, the OpenSSL utility returns the
Verified OK
status.import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.IOException; import java.io.StringReader; import java.security.*; import java.security.spec.*; import java.util.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; public class VerifyEcdsaSign { public static void main(String[] args) throws Exception { String publicKeyPem = """ -- -- - BEGIN PUBLIC KEY-- -- - <public_key_contents> -- -- - END PUBLIC KEY-- -- - """; String signatureStr = "<signature_string>"; byte[] signatureDer = Base64.getDecoder().decode(signatureStr); System.out.println(verifyEcdsaSignature(publicKeyPem, signatureDer, "<message_string>", "<algorithm_type>")); } public static boolean verifyEcdsaSignature(String publicKeyPem, byte[] signatureDer, String message, String hash_algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException, IOException { // Public key and subscription decoding PemReader pemReader = new PemReader(new StringReader(publicKeyPem)); PemObject pemObject = pemReader.readPemObject(); byte[] publicKeyBytes = pemObject.getContent(); // Creating a PublicKey object from the decoded public key KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider()); EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes); PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); // Creating a Signature object and initializing it with a public key Signature signature = Signature.getInstance(hash_algorithm + "withECDSA", new BouncyCastleProvider()); signature.initVerify(publicKey); // Updating a Signature Object with Message Data byte[] messageBytes = message.getBytes(); signature.update(messageBytes); // Signature verification using original message and decoded signature return signature.verify(signatureDer); } }
Where:
<public_key_contents>
: Contents of the public signature key.<signature_string>
: Contents of the digital signature inbase64
encoding.<message_string>
: String with the source message signed with the digital signature or the hash of the file signed with the digital signature.<algorithm_type>
: Hash function used for the signature. The possible values areSHA256
,SHA384
, andSHA512
.
The code verifies the ECDSA signature. It returns
true
if the signature is correct andfalse
if it is not.import ( "crypto/ecdsa" "crypto/sha256" "crypto/x509" "encoding/asn1" "encoding/base64" "encoding/pem" "fmt" "hash" "log" "math/big" ) func runEcdsaSignTest() { publicKeyPem := `-----BEGIN PUBLIC KEY----- <public_key_contents> -----END PUBLIC KEY-----` signatureB64 := "<signature_string>" signatureDER, _ := base64.StdEncoding.DecodeString(signatureB64) message := "<message_string>" fmt.Println(verifyEcdsa(publicKeyPem, signatureDER, message, <algorithm_type>)) } type ECDSASignature struct { R, S *big.Int } func verifyEcdsa(publicKeyPem string, signatureDER []byte, message string, hashFunc hash.Hash) bool { // Decode the public key block, _ := pem.Decode([]byte(publicKeyPem)) if block == nil { log.Fatal("failed to decode PEM block containing public key") } // Parse the public key pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { log.Fatal(err) } publicKey, ok := pub.(*ecdsa.PublicKey) if !ok { log.Fatal("not ECDSA public key") } // Parse the signature var signature ECDSASignature _, err = asn1.Unmarshal(signatureDER, &signature) if err != nil { log.Fatal(err) } // Compute the hash of the message hashFunc.Write([]byte(message)) hashed := hashFunc.Sum(nil) // Verify the signature return ecdsa.Verify(publicKey, hashed, signature.R, signature.S) }
Where:
<public_key_contents>
: Contents of the public signature key inbase64
encoding.<signature_string>
: Contents of the digital signature inbase64
encoding.<message_string>
: String with the source message signed with the digital signature or the hash of the file signed with the digital signature.<algorithm_type>
: Hash function used for the signature. The possible values aresha256.New()
,sha512.New384()
, andsha512.New()
.
The code verifies the ECDSA signature. It returns
true
if the signature is correct andfalse
if it is not.import base64 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives import hashes from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend # Define hash algorithms def verify_ecdsa_signature(public_key_b64, signature_der, message, hash_algorithm): hash_algorithms = { 'SHA256': hashes.SHA256, 'SHA384': hashes.SHA384, 'SHA512': hashes.SHA512 } # Check if the provided hash algorithm is supported if hash_algorithm not in hash_algorithms: raise ValueError('Unsupported hash algorithm: ' + hash_algorithm) # Loading a PEM Encoded Public Key public_key = serialization.load_pem_public_key( public_key_b64.encode(), backend = default_backend() ) # Create Signature object and initialize it with the public key signature = ec.ECDSA(hash_algorithms[hash_algorithm]()) # Update the Signature object with the message data message_bytes = message.encode() # Verify the signature using the original message and the decoded signature try: public_key.verify(signature_der, message_bytes, signature) return True except InvalidSignature: return False def test_verify_signature(): public_key_b64 = """ -----BEGIN PUBLIC KEY----- <public_key_content> -----END PUBLIC KEY-----""" signature_b64 = "<signature>" signature_der = base64.b64decode(signature_b64) message = '<message>' print(verify_ecdsa_signature(public_key_b64, signature_der, message, "<algorithm_type>"))
Where:
<public_key_contents>
: Contents of the public signature key.<signature_string>
: Contents of the digital signature inbase64
encoding.<message_string>
: String with the source message signed with the digital signature or the hash of the file signed with the digital signature.<algorithm_type>
: Hash function used for the signature. The possible values areSHA256
,SHA384
, andSHA512
.
The code verifies the ECDSA signature. It returns
true
if the signature is correct andfalse
if it is not.
RSA signature
To verify a digital signature:
-
Get the public key of the digital signature.
-
Perform verification:
BashJavaGoPythonVerify the digital signature using OpenSSL
:openssl dgst \ -<hashing_algorithm> \ -sigopt rsa_padding_mode:pss \ -sigopt rsa_pss_saltlen:-1 \ -verify <path_to_public_key_file> \ -signature <path_to_signature_file> \ <path_to_signed_file>
Where:
<hashing_algorithm>
: Hashing algorithm used when creating a signature key pair. The possible values include:sha256
for SHA-256 algorithmssha384
for SHA-384 algorithmssha512
for SHA-512 algorithms
-verify
: Path to the file with a public signature key.-signature
: Path to the digital signature file.<path_to_signed_file>
: Path to the file whose digital signature is being verified.
If the signature is correct, the OpenSSL utility returns the
Verified OK
status.import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.IOException; import java.io.StringReader; import java.security.*; import java.security.spec.*; import java.util.Base64; public class VerifyRsaSign { public static void main(String[] args) throws Exception { String publicKeyPem = """ -----BEGIN PUBLIC KEY----- <public_key_contents> -----END PUBLIC KEY-----"""; String signatureStr = "<signature_string>"; byte[] signatureBytes = Base64.getDecoder().decode(signatureStr); String message = "<message_string>"; System.out.println(verifyRsaSignature(publicKeyPem, signatureBytes, message, "<algorithm_type>")); } private static boolean verifyRsaSignature(String publicKeyPem, byte[] signatureBytes, String message, String hashAlgorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException, InvalidAlgorithmParameterException, IOException { // Get the public key PemReader pemReader = new PemReader(new StringReader(publicKeyPem)); PemObject pemObject = pemReader.readPemObject(); byte[] publicKeyBytes = pemObject.getContent(); // Create a PublicKey object using the decoded public key KeyFactory keyFactory = KeyFactory.getInstance("RSA", new BouncyCastleProvider()); EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes); PublicKey pubKey = keyFactory.generatePublic(publicKeySpec); MessageDigest messageDigest = MessageDigest.getInstance(hashAlgorithm); int saltLength = messageDigest.getDigestLength(); // Initialize the PSS signer PSSParameterSpec pssSpec = new PSSParameterSpec(hashAlgorithm, "MGF1", new MGF1ParameterSpec(hashAlgorithm), saltLength, 1); Signature signer = Signature.getInstance("RSASSA-PSS"); signer.setParameter(pssSpec); signer.initVerify(pubKey); // Update the signature with the hash of the message byte[] messageBytes = message.getBytes(); signer.update(messageBytes); // Verify the signature return signer.verify(signatureBytes); } }
Where:
<public_key_contents>
: Contents of the public signature key.<signature_string>
: Contents of the digital signature inbase64
encoding.<message_string>
: String with the source message signed with the digital signature or the hash of the file signed with the digital signature.<algorithm_type>
: Hash function used for the signature. The possible values areSHA256
,SHA384
, andSHA512
.
The code verifies the RSA digital signature. It returns
true
if the signature is correct andfalse
if it is not.import ( "crypto" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/pem" "fmt" "log" ) func runRsaSignTest() { publicKeyB64 := "<public_key_contents>" signatureB64 := "<signature_string>" signatureBytes, _ := base64.StdEncoding.DecodeString(signatureB64) message := "<message_string>" fmt.Println(verifyRsa(publicKeyB64, signatureBytes, message, <algorithm_type>)) } func verifyRsa(publicKeyPem string, signatureBytes []byte, message string, hash crypto.Hash) bool { // Decode the public key block, _ := pem.Decode([]byte(publicKeyPem)) if block == nil { log.Fatal("failed to decode PEM block containing public key") } // Parse the public key pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { log.Fatal(err) } publicKey, ok := pub.(*rsa.PublicKey) if !ok { log.Fatal("not RSA public key") } // Calculate the hash of the message hasher := hash.New() hasher.Write([]byte(message)) hashed := hasher.Sum(nil) // Set the PSS options: salt length auto, and the hash function pssOptions := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthAuto, Hash: hash} // Verify the signature err = rsa.VerifyPSS(publicKey, hash, hashed, signatureBytes, pssOptions) if err != nil { fmt.Println("Verification failed:", err) return false } else { return true } }
Where:
<public_key_contents>
: Contents of the public signature key inbase64
encoding.<signature_string>
: Contents of the digital signature inbase64
encoding.<message_string>
: String with the source message signed with the digital signature or the hash of the file signed with the digital signature.<algorithm_type>
: Hash function used for the signature. The possible values arecrypto.SHA256
,crypto.SHA384
, andcrypto.SHA512
.
The code verifies the RSA digital signature. It returns
true
if the signature is correct andfalse
if it is not.import base64 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import serialization from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend # Define hash algorithms and corresponding salt lengths def verify_rsa_signature(public_key_b64, signature_bytes, message, hash_algorithm): hash_algorithms = { 'SHA256': hashes.SHA256, 'SHA384': hashes.SHA384, 'SHA512': hashes.SHA512 } # Check if the provided hash algorithm is supported if hash_algorithm not in hash_algorithms: raise ValueError('Unsupported hash algorithm: ' + hash_algorithm) # Loading a PEM Encoded Public Key public_key = serialization.load_pem_public_key( public_key_b64.encode(), backend=default_backend() ) # Update the Signature object with the message data message_bytes = message.encode() # Automatically calculate salt length based on hash digest size salt_length = hash_algorithms[hash_algorithm]().digest_size # Verify the signature using the original message and the decoded signature try: public_key.verify( signature_bytes, message_bytes, padding.PSS( mgf = padding.MGF1(hash_algorithms[hash_algorithm]()), salt_length = salt_length ), hash_algorithms[hash_algorithm]() ) return True except InvalidSignature: return False def test_verify_signature(): public_key_b64 = """ -----BEGIN PUBLIC KEY----- <public_key_contents> -----END PUBLIC KEY-----""" signature_b64 = '<signature>' signature_bytes = base64.b64decode(signature_b64) message = '<message>' print(verify_rsa_signature(public_key_b64, signature_bytes, message, '<algorithm_type>'))
Where:
<public_key_contents>
: Contents of the public signature key inbase64
encoding.<signature_string>
: Contents of the digital signature inbase64
encoding.<message_string>
: String with the source message signed with the digital signature or the hash of the file signed with the digital signature.<algorithm_type>
: Hash function used for the signature. The possible values areSHA256
,SHA384
, andSHA512
.
The code verifies the RSA digital signature. It returns
true
if the signature is correct andfalse
if it is not.