Электронная подпись в KMS
Электронная подпись — это результат криптографической операции, который позволяет обеспечить дополнительную защиту данных. Главные задачи электронной подписи:
- подтверждение подлинности данных;
- проверка целостности данных;
- защита данных от изменений;
- удостоверение источника данных.
Алгоритм электронной подписи позволяет выполнять две операции: создание подписи и проверка подписи.
Электронная подпись основана на асимметричной криптографии. Асимметричная ключевая пара электронной подписи состоит из двух частей: открытого ключа подписи (Public key) и закрытого ключа подписи (Private key). Закрытый ключ используется для создания подписи, а открытый — для ее проверки.
Электронную подпись можно использовать для проверки подлинности исходного кода, бинарных файлов, образов контейнеров. Например, можно проверить подлинность подписанного электронной подписью образа. Если при проверке подпись окажется недействительной, значит, образ был изменен или поврежден. Также электронную подпись можно использовать для проверки субъекта сертификата, выданного удостоверяющим центром (Certificate Authority
Каждая ключевая пара соответствует одному ключу в квотах KMS.
Работа с электронной подписью
Участники процесса работы с электронной подписью — это владелец закрытого ключа ключевой пары и получатели подписанных данных. Процесс создания электронной подписи и ее проверки происходит по следующей схеме:
- Владелец подписи создает асимметричную ключевую пару, поддерживающую электронную подпись.
- Владелец создает электронную подпись своих данных. При этом рассчитывается хэш-значение пользовательских данных, которое подписывается закрытым ключом по заданному алгоритму. Используемая хэш-функция указывается в названии алгоритма.
- Владелец подписи передает получателю данные, электронную подпись и открытый ключ асимметричной ключевой пары.
- Получатель использует открытый ключ для проверки электронной подписи.
- Если расшифрованный получателем хэш совпадает с хэшем данных, значит, подпись корректна.
Поддерживаемые алгоритмы электронной подписи
Для электронной подписи сервис KMS предоставляет криптографические алгоритмы 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
Проверка электронной подписи
Проверка электронной подписи выполняется стороной, у которой нет доступа к закрытому ключу электронной подписи. Для проверки подписи используется открытый ключ.
Подпись на эллиптических кривых (ECDSA)
Для проверки электронной подписи:
-
Получите открытый ключ электронной подписи.
-
Выполните проверку:
BashJavaGoPythonПроверьте электронную подпись с помощью утилиты OpenSSL
:openssl dgst \ -<алгоритм_хэширования> \ -verify <путь_к_файлу_открытого_ключа> \ -signature <путь_к_файлу_подписи> \ <путь_к_подписанному_файлу>
Где:
<алгоритм_хэширования>
— алгоритм хэширования, использованный при создании ключевой пары подписи. Возможные значения:sha256
— для алгоритмов SHA-256;sha384
— для алгоритмов SHA-384;sha512
— для алгоритмов SHA-512.
-verify
— путь к файлу с открытым ключом подписи.-signature
— путь к файлу с электронной подписью.<путь_к_подписанному_файлу>
— путь к файлу, для которого проверяется электронная подпись.
Если подпись корректна, утилита OpenSSL вернет статус
Verified OK
.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-- -- - <содержимое_открытого_ключа> -- -- - END PUBLIC KEY-- -- - """; String signatureStr = "<подпись>"; byte[] signatureDer = Base64.getDecoder().decode(signatureStr); System.out.println(verifyEcdsaSignature(publicKeyPem, signatureDer, "<сообщение>", "<тип_алгоритма>")); } 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); } }
Где:
<содержимое_открытого_ключа>
— содержимое открытого ключа подписи.<подпись>
— содержимое электронной подписи в кодировкеbase64
.<сообщение>
— строка, содержащая исходное сообщение, подписанное электронной подписью, или хэш подписанного электронной подписью файла.<тип_алгоритма>
— использованная для подписи хэш-функция. Возможные значения:SHA256
,SHA384
иSHA512
.
Код выполняет проверку электронной подписи на эллиптических кривых (ECDSA). Если подпись корректна, код возвращает
true
, если нет —false
.import ( "crypto/ecdsa" "crypto/sha256" "crypto/x509" "encoding/asn1" "encoding/base64" "encoding/pem" "fmt" "hash" "log" "math/big" ) func runEcdsaSignTest() { publicKeyPem := `-----BEGIN PUBLIC KEY----- <содержимое_открытого_ключа> -----END PUBLIC KEY-----` signatureB64 := "<подпись>" signatureDER, _ := base64.StdEncoding.DecodeString(signatureB64) message := "<сообщение>" fmt.Println(verifyEcdsa(publicKeyPem, signatureDER, message, <тип_алгоритма>)) } 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) }
Где:
<содержимое_открытого_ключа>
— содержимое открытого ключа подписи в кодировкеbase64
.<подпись>
— содержимое электронной подписи в кодировкеbase64
.<сообщение>
— строка, содержащая исходное сообщение, подписанное электронной подписью, или хэш подписанного электронной подписью файла.<тип_алгоритма>
— использованная для подписи хэш-функция. Возможные значения:sha256.New()
,sha512.New384()
иsha512.New()
.
Код выполняет проверку электронной подписи на эллиптических кривых (ECDSA). Если подпись корректна, код возвращает
true
, если нет —false
.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----- <содержимое_открытого_ключа> -----END PUBLIC KEY-----""" signature_b64 = "<подпись>" signature_der = base64.b64decode(signature_b64) message = '<сообщение>' print(verify_ecdsa_signature(public_key_b64, signature_der, message, "<тип_алгоритма>"))
Где:
<содержимое_открытого_ключа>
— содержимое открытого ключа подписи.<подпись>
— содержимое электронной подписи в кодировкеbase64
.<сообщение>
— строка, содержащая исходное сообщение, подписанное электронной подписью, или хэш подписанного электронной подписью файла.<тип_алгоритма>
— использованная для подписи хэш-функция. Возможные значения:SHA256
,SHA384
иSHA512
.
Код выполняет проверку электронной подписи на эллиптических кривых (ECDSA). Если подпись корректна, код возвращает
true
, если нет —false
.
Подпись RSA
Для проверки электронной подписи:
-
Получите открытый ключ электронной подписи.
-
Выполните проверку:
BashJavaGoPythonПроверьте электронную подпись с помощью утилиты OpenSSL
:openssl dgst \ -<алгоритм_хэширования> \ -sigopt rsa_padding_mode:pss \ -sigopt rsa_pss_saltlen:-1 \ -verify <путь_к_файлу_открытого_ключа> \ -signature <путь_к_файлу_подписи> \ <путь_к_подписанному_файлу>
Где:
<алгоритм_хэширования>
— алгоритм хэширования, использованный при создании ключевой пары подписи. Возможные значения:sha256
— для алгоритмов SHA-256;sha384
— для алгоритмов SHA-384;sha512
— для алгоритмов SHA-512.
-verify
— путь к файлу с открытым ключом подписи.-signature
— путь к файлу с электронной подписью.<путь_к_подписанному_файлу>
— путь к файлу, для которого проверяется электронная подпись.
Если подпись корректна, утилита OpenSSL вернет статус
Verified OK
.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----- <содержимое_открытого_ключа> -----END PUBLIC KEY-----"""; String signatureStr = "<подпись>"; byte[] signatureBytes = Base64.getDecoder().decode(signatureStr); String message = "<сообщение>"; System.out.println(verifyRsaSignature(publicKeyPem, signatureBytes, message, "<тип_алгоритма>")); } 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); } }
Где:
<содержимое_открытого_ключа>
— содержимое открытого ключа подписи.<подпись>
— содержимое электронной подписи в кодировкеbase64
.<сообщение>
— строка, содержащая исходное сообщение, подписанное электронной подписью, или хэш подписанного электронной подписью файла.<тип_алгоритма>
— использованная для подписи хэш-функция. Возможные значения:SHA256
,SHA384
иSHA512
.
Код выполняет проверку электронной подписи RSA. Если подпись корректна, код возвращает
true
, если нет —false
.import ( "crypto" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/pem" "fmt" "log" ) func runRsaSignTest() { publicKeyB64 := "<содержимое_открытого_ключа>" signatureB64 := "<подпись>" signatureBytes, _ := base64.StdEncoding.DecodeString(signatureB64) message := "<сообщение>" fmt.Println(verifyRsa(publicKeyB64, signatureBytes, message, <тип_алгоритма>)) } 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 } }
Где:
<содержимое_открытого_ключа>
— содержимое открытого ключа подписи в кодировкеbase64
.<подпись>
— содержимое электронной подписи в кодировкеbase64
.<сообщение>
— строка, содержащая исходное сообщение, подписанное электронной подписью, или хэш подписанного электронной подписью файла.<тип_алгоритма>
— использованная для подписи хэш-функция. Возможные значения:crypto.SHA256
,crypto.SHA384
иcrypto.SHA512
.
Код выполняет проверку электронной подписи RSA. Если подпись корректна, код возвращает
true
, если нет —false
.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----- <содержимое_открытого_ключа> -----END PUBLIC KEY-----""" signature_b64 = '<подпись>' signature_bytes = base64.b64decode(signature_b64) message = '<сообщение>' print(verify_rsa_signature(public_key_b64, signature_bytes, message, '<тип_алгоритма>'))
Где:
<содержимое_открытого_ключа>
— содержимое открытого ключа подписи в кодировкеbase64
.<подпись>
— содержимое электронной подписи в кодировкеbase64
.<сообщение>
— строка, содержащая исходное сообщение, подписанное электронной подписью, или хэш подписанного электронной подписью файла.<тип_алгоритма>
— использованная для подписи хэш-функция. Возможные значения:SHA256
,SHA384
иSHA512
.
Код выполняет проверку электронной подписи RSA. Если подпись корректна, код возвращает
true
, если нет —false
.