Digital signature and its hash-based verification
In Key Management Service, you can create a digital signature that can be used to verify data authenticity and integrity, as well as to protect the signed data from modification.
Getting started
This guide uses OpenSSL
Run this command:
sudo apt-get install openssl
Use the Chocolatey
choco install openssl
Create a digital signature
Depending on the size of the message or file to sign, KMS allows creating a private key-based or hash-based message signature.
Private key-based message signature
Note
Private key-based signature is used for messages of up to 32 KB.
-
If you do not have a digital signature key pair, create one.
-
Get a signature public key and save it:
Management consoleCLI- In the management console
, select the folder containing the digital signature key pair. - Navigate
to Key Management Service. - In the left-hand panel, select Asymmetric keys.
- Navigate to the Signature tab.
- In the key pair row, click
and select Public key. - In the window that opens, click
Download to download the digital signature public key.
If you do not have the Yandex Cloud CLI yet, install and initialize it.
The folder used by default is the one specified when creating the CLI profile. To change the default folder, use the
yc config set folder-id <folder_ID>command. You can also specify a different folder for any command using--folder-nameor--folder-id.If you access a resource by its name, the search will be limited to the default folder. If you access a resource by its ID, the search will be global, i.e., through all folders based on access permissions.
-
View the description of the CLI command for getting a signature public key:
yc kms asymmetric-signature-crypto get-public-key --help -
Get the ID of the folder containing the digital signature key pair.
-
Get the ID of the required digital signature key pair by specifying the folder ID:
yc kms asymmetric-signature-key list \ --folder-id <folder_ID>Result:
+----------------------+----------------------+---------------------------+---------------------+--------+ | ID | NAME | SIGNATURE ALGORITHM | CREATED AT | STATUS | +----------------------+----------------------+---------------------------+---------------------+--------+ | abj9g2dil5sj******** | sample-signature-key | RSA_2048_SIGN_PSS_SHA_512 | 2023-08-16 09:06:57 | ACTIVE | +----------------------+----------------------+---------------------------+---------------------+--------+ -
Get a digital signature public key by specifying the previously obtained key pair ID:
yc kms asymmetric-signature-crypto get-public-key \ --id <key_pair_ID>Result:
key_id: abj9g2dil5sj******** public_key: | -----BEGIN PUBLIC KEY----- MIIB... ...QAB -----END PUBLIC KEY-----Save the obtained key to a file, such as
public.key. Make sure that lines in the file do not start with spaces.
- In the management console
-
Create a file with a
base64-encoded message:-
Create a text file, e.g.,
message.txt:cat > message.txt My sample message. It will be used to verify ECDSA signature.The message size must not exceed 32 KB.
-
Change the message encoding to
base64by specifying the path to the createdbase64-encoded message file:base64 message.txt > <base64_message_file>
-
-
Create a message signature:
CLI-
View the description of the CLI command for getting a digital signature:
yc kms asymmetric-signature-crypto sign --help -
Get a digital signature for the message:
yc kms asymmetric-signature-crypto sign \ --id <key_pair_ID> \ --signature-output-file <signature_file_path> \ --message-file <message_file_path> \ --inform base64 \ --outform base64Where:
--id: ID of the digital signature key pair.--signature-output-file: Path to the file to save the digital signature to.--message-file: Path to the previously created file with thebase64-encoded message.--inform: Message file format. The possible values areraw(default),base64, andhex.--outform: Signature file format. Possible values:raw(default),base64, andhex.
Result:
key_id: abjcg4mhmdfe******** signature: MAa7C...imw== -
Change the digital signature format to DER
, which is required forOpenSSL:echo -n "$(< <signature_file_path>)" | base64 -d > <signature_file>Where:
<signature_file_path>: Path to the signature file you got in the previous step.<signature_file>: Path to the new signature file inDERformat.
The signature file in
DERformat can be used to verify the signature usingOpenSSL. -
Hash-based file signature
Note
Hash-based signature is used for messages or files over 32 KB.
-
If you do not have a digital signature key pair, create one.
-
Get a digital signature public key and save it:
Management consoleCLI- In the management console
, select the folder containing the digital signature key pair. - Navigate
to Key Management Service. - In the left-hand panel, select Asymmetric keys.
- Navigate to the Signature tab.
- In the key pair row, click
and select Public key. - In the window that opens, click
Download to download the signature public key.
If you do not have the Yandex Cloud CLI yet, install and initialize it.
The folder used by default is the one specified when creating the CLI profile. To change the default folder, use the
yc config set folder-id <folder_ID>command. You can also specify a different folder for any command using--folder-nameor--folder-id.If you access a resource by its name, the search will be limited to the default folder. If you access a resource by its ID, the search will be global, i.e., through all folders based on access permissions.
-
View the description of the CLI command for getting a signature public key:
yc kms asymmetric-signature-crypto get-public-key --help -
Get the ID of the folder containing the digital signature key pair.
-
Get the ID of the required digital signature key pair by specifying the folder ID:
yc kms asymmetric-signature-key list \ --folder-id <folder_ID>Result:
+----------------------+----------------------+---------------------------+---------------------+--------+ | ID | NAME | SIGNATURE ALGORITHM | CREATED AT | STATUS | +----------------------+----------------------+---------------------------+---------------------+--------+ | abj9g2dil5sj******** | sample-signature-key | RSA_2048_SIGN_PSS_SHA_512 | 2023-08-16 09:06:57 | ACTIVE | +----------------------+----------------------+---------------------------+---------------------+--------+ -
Get a digital signature public key by specifying the previously obtained key pair ID:
yc kms asymmetric-signature-crypto get-public-key \ --id <key_pair_ID>Result:
key_id: abj9g2dil5sj******** public_key: | -----BEGIN PUBLIC KEY----- MIIB... ...QAB -----END PUBLIC KEY-----Save the obtained key to a file, such as
public.key. Make sure that lines in the file do not start with spaces.
- In the management console
-
Get the file hash:
BashPowerShellRun this command:
echo -n \ $(<hashing_algorithm> <source_file_path> | cut -d " " -f 1) > \ <hash_file_path>Where:
-
<hashing_algorithm>: Hashing algorithm used to create the digital signature key pair. The hashing algorithm is specified above in theSIGNATURE ALGORITHMfield of the results of getting the list of key pairs. The possible values are as follows:sha256sum: For SHA-256 algorithms.sha384sum: For SHA-384 algorithms.sha512sum: For SHA-512 algorithms.
-
<path_to_source_file>: Path to the file to hash. -
<path_to_hash_file>: Path to the file to save the hash to.
Run this command:
(Get-FileHash -Path <source_file_path> -Algorithm <hashing_algorithm>).Hash.ToLower() | ` Out-File -FilePath <hash_file_path> ` -encoding ASCII ` -NoNewlineWhere:
-
<hashing_algorithm>: Hashing algorithm used to create the signature key pair. The hashing algorithm is specified above in theSIGNATURE ALGORITHMfield of the results of getting the list of key pairs. The possible values are as follows:SHA256: For SHA-256 algorithms.SHA384: For SHA-384 algorithms.SHA512: For SHA-512 algorithms.
-
<path_to_source_file>: Path to the file to hash. -
<path_to_hash_file>: Path to the file to save the hash to.
This will create a text file containing the hash of the source file.
-
-
Create a hash-based file signature:
CLI-
View the description of the CLI command for getting a hash-based digital signature:
yc kms asymmetric-signature-crypto sign-hash --help -
Get the ID of the folder containing the digital signature key pair.
-
Get the ID of the required digital signature key pair by specifying the folder ID:
yc kms asymmetric-signature-key list \ --folder-id <folder_ID>Result:
+----------------------+----------------------+---------------------------+---------------------+--------+ | ID | NAME | SIGNATURE ALGORITHM | CREATED AT | STATUS | +----------------------+----------------------+---------------------------+---------------------+--------+ | abj9g2dil5sj******** | sample-signature-key | RSA_2048_SIGN_PSS_SHA_512 | 2023-08-16 09:06:57 | ACTIVE | +----------------------+----------------------+---------------------------+---------------------+--------+ -
Get a hash-based digital signature:
yc kms asymmetric-signature-crypto sign-hash \ --id <key_pair_ID> \ --signature-output-file <signature_file_path> \ --message-hash-file <hash_file_path> \ --inform hexWhere:
--id: ID of the digital signature key pair.--signature-output-file: Path to the file to save the digital signature to.--message-hash-file: Path to the previously created hash file.--inform: Hash file format. Our example uses the commonhexformat that is supported by all platforms. The possible values areraw(default),base64, andhex.
Result:
signature: W7V8A...22g==
-
Verify the digital signature
ECDSA signature
Verify the digital signature using OpenSSL
openssl dgst \
-<hashing_algorithm> \
-verify <path_to_public_key_file> \
-signature <signature_file_path> \
<path_to_signed_file>
Where:
<hashing_algorithm>: Hashing algorithm used to create the signature key pair. The possible values are as follows:sha256: For SHA-256 algorithms.sha384: For SHA-384 algorithms.sha512: For SHA-512 algorithms.
-verify: Path to the signature public key file.-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 valid, OpenSSL will return 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>";
byte[] signatureDer = Base64.getDecoder().decode(signatureStr);
System.out.println(verifyEcdsaSignature(publicKeyPem, signatureDer, "<message>", "<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 signature public key.<signature>: Contents of the digital signature in thebase64encoding.<message>: String containing the source message signed with the digital signature, or 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 valid, and false 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>"
signatureDER, _ := base64.StdEncoding.DecodeString(signatureB64)
message := "<message>"
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 signature public key in thebase64encoding.<signature>: Contents of the digital signature in thebase64encoding.<message>: String containing the source message signed with the digital signature, or 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 valid, and false 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_contents>
-----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 signature public key.<signature>: Contents of the digital signature in thebase64encoding.<message>: String containing the source message signed with the digital signature, or 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 valid, and false if it is not.
RSA signature
Verify 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 <signature_file_path> \
<path_to_signed_file>
Where:
<hashing_algorithm>: Hashing algorithm used to create the signature key pair. The possible values are as follows:sha256: For SHA-256 algorithms.sha384: For SHA-384 algorithms.sha512: For SHA-512 algorithms.
-verify: Path to the signature public key file.-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 valid, OpenSSL will return 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>";
byte[] signatureBytes = Base64.getDecoder().decode(signatureStr);
String message = "<message>";
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 signature public key.<signature>: Contents of the digital signature in thebase64encoding.<message>: String containing the source message signed with the digital signature, or 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 valid, and false 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>"
signatureBytes, _ := base64.StdEncoding.DecodeString(signatureB64)
message := "<message>"
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 signature public key in thebase64encoding.<signature>: Contents of the digital signature in thebase64encoding.<message>: String containing the source message signed with the digital signature, or 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 valid, and false 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 signature public key in thebase64encoding.<signature>: Contents of the digital signature in thebase64encoding.<message>: String containing the source message signed with the digital signature, or 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 valid, and false if it is not.