Digital signature and its verification based on data hash
In Key Management Service, you can create a digital signature that can be used to validate data authenticity and integrity, as well as to protect signed data from editing.
Getting started
In this tutorial, digital signature verification is performed using the OpenSSL
Run this command:
sudo apt-get install openssl
Use the Chocolatey
choco install openssl
Create a digital signature
Depending on the size of a signed message or file, KMS allows creating a message signature based on a private key or a signature based on data hash.
Message signature based on a private key
Note
A signature based on a private key is used for messages of up to 32 KB.
-
If you do not have a digital signature key pair, create one.
-
Get a public signature key and save it:
Management consoleCLI- In the management console
, select the folder with the appropriate digital signature key pair. - In the list of services, select Key Management Service.
- In the left-hand panel, select Asymmetric keys.
- Go to the Signature tab.
- In the row with the appropriate key pair, 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 command line interface yet, install and initialize it.
The folder specified in the CLI profile is used by default. You can specify a different folder using the
--folder-name
or--folder-id
parameter.-
View the description of the CLI command to get a signature public key:
yc kms asymmetric-signature-crypto get-public-key --help
-
Get the ID of the folder where the digital signature key pair is saved.
-
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
base64
by specifying the path to the created message file inbase64
:base64 message.txt > <base64_message_file>
-
-
Create a message signature:
CLI-
View the description of the CLI command to get a digital signature:
yc kms asymmetric-signature-crypto sign --help
-
Get the message's digital signature:
yc kms asymmetric-signature-crypto sign \ --id <key_pair_ID> \ --signature-output-file <signature_file_path> \ --message-file <message_file_path> \ --inform base64 \ --outform base64
Where:
--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. Possible values:raw
(default),base64
, andhex
.--outform
: Signature file format. Possible values:raw
(default),base64
, andhex
.
Result:
key_id: abjcg4mhmdfe******** signature: MAa7C...imw==
-
Change the format of the resulting digital signature to DER
(this format is required forOpenSSL
):echo -n "$(< <signature_file_path>)" | base64 -d > <signature_file>
Where:
<signature_file_path>
: Path to the signature file created at the previous step.<signature_file>
: Path to the new signature file inDER
format.
The
DER
signature file you get can be used to verify the signature usingOpenSSL
. -
File signature based on data hash
Note
A hash-based signature is used for messages or files over 32 KB in size.
-
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 with the appropriate digital signature key pair. - In the list of services, select Key Management Service.
- In the left-hand panel, select Asymmetric keys.
- Go to the Signature tab.
- In the row with the appropriate key pair, 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 command line interface yet, install and initialize it.
The folder specified in the CLI profile is used by default. You can specify a different folder using the
--folder-name
or--folder-id
parameter.-
View the description of the CLI command to get a signature public key:
yc kms asymmetric-signature-crypto get-public-key --help
-
Get the ID of the folder where the digital signature key pair is saved.
-
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 a file's hash:
BashPowerShellRun this command:
echo -n \ $(<hashing_algorithm> <source_file_path> | cut -d " " -f 1) > \ <hash_file_path>
Where:
<hashing_algorithm>
: Hashing algorithm used when creating a digital signature key pair. The hashing algorithm is specified above in theSIGNATURE ALGORITHM
field of the results you get with the list of key pairs. The possible values are:sha256sum
: For SHA-256 algorithms.sha384sum
: For SHA-384 algorithms.sha512sum
: For SHA-512 algorithms.
<source_file_path>
: Path to the file whose hash you want to get.<hash_file_path>
: 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 ` -NoNewline
Where:
<hashing_algorithm>
: Hashing algorithm used when creating a signature key pair. The hashing algorithm is specified above in theSIGNATURE ALGORITHM
field of the results you get with the list of key pairs. The possible values are:SHA256
: For SHA-256 algorithms.SHA384
: For SHA-384 algorithms.SHA512
: For SHA-512 algorithms.
<source_file_path>
: Path to the file whose hash you want to get.<hash_file_path>
: Path to the file to save the hash to.
-
Create a hash-based file signature:
CLI-
View the description of the CLI command to get a hash-based digital signature:
yc kms asymmetric-signature-crypto sign-hash --help
-
Get the ID of the folder where the digital signature key pair is saved.
-
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 hex
Where:
--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. The example uses the commonhex
format that is supported by all platforms. Possible values:raw
(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 <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 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_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 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_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 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 <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 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_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 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 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 and false
if it is not.