EVP_PKEY_SIGN(3SSL)
EVP_PKEY_SIGN π sign using a public key algorithm
okay bestie, this one is from OpenSSL (the 3SSL manual, iykyk) and it's the whole glow-up crew for signing stuff with a public key algorithm. Buckle up, it's long, there's a part 2, and we are NOT skipping a single flag. π
β¨ NAME β¨
EVP_PKEY_sign_init, EVP_PKEY_sign_init_ex, EVP_PKEY_sign_init_ex2, EVP_PKEY_sign, EVP_PKEY_sign_message_init, EVP_PKEY_sign_message_update, EVP_PKEY_sign_message_final β sign using a public key algorithm π
β¨ SYNOPSIS β¨
#include <openssl/evp.h>
int EVP_PKEY_sign_init(EVP_PKEY_CTX *ctx);
int EVP_PKEY_sign_init_ex(EVP_PKEY_CTX *ctx, const OSSL_PARAM params[]);
int EVP_PKEY_sign_init_ex2(EVP_PKEY_CTX *ctx, EVP_SIGNATURE *algo,
const OSSL_PARAM params[]);
int EVP_PKEY_sign_message_init(EVP_PKEY_CTX *ctx, EVP_SIGNATURE *algo,
const OSSL_PARAM params[]);
int EVP_PKEY_sign_message_update(EVP_PKEY_CTX *ctx,
unsigned char *in, size_t inlen);
int EVP_PKEY_sign_message_final(EVP_PKEY_CTX *ctx, unsigned char *sig,
size_t *siglen, size_t sigsize);
int EVP_PKEY_sign(EVP_PKEY_CTX *ctx,
unsigned char *sig, size_t *siglen,
const unsigned char *tbs, size_t tbslen);
β¨ DESCRIPTION β¨
EVP_PKEY_sign_init() sets up a public key algorithm context ctx for signing, using the algorithm that was handed over when the context got made β you know, via EVP_PKEY_CTX_new(3) or one of its little siblings. That algorithm is what fetches an EVP_SIGNATURE method implicitly, so if you want the tea on implicit fetches, go read "Implicit fetch" in provider(7). π¦
EVP_PKEY_sign_init_ex() is literally the same as EVP_PKEY_sign_init() except it also sets the params you passed on the context before it returns. cute, efficient, obsessed. β¨
EVP_PKEY_sign_init_ex2() sets up a public key algorithm context ctx for signing a pre-computed message digest, using the algorithm given by algo and the key given through EVP_PKEY_CTX_new(3) or EVP_PKEY_CTX_new_from_pkey(3). π«Ά Important bestie note: a context ctx with no pre-loaded key cannot be used with this function, periodt. This one does almost the same thing as EVP_PKEY_sign_init_ex(), but it's uniquely meant for a pre-computed message digest, and it lets you pre-determine the exact conditions for that digest if you fetched a composite signature algorithm (think RSA-SHA256). And after you call this, setting params that mess with the digest implementation or the padding is normally not supported. π
EVP_PKEY_sign_message_init() sets up a public key algorithm context ctx for signing a message of unlimited size, using the algorithm given by algo and the key given through EVP_PKEY_CTX_new(3) or EVP_PKEY_CTX_new_from_pkey(3). You can pass the message one-shot style with EVP_PKEY_sign(), or you can do the combo of EVP_PKEY_sign_message_update() plus EVP_PKEY_sign_message_final(). π This is the one that unlocks algorithms which can chew through input of arbitrary length β like ED25519, RSA-SHA256 and similar icons. π
EVP_PKEY_sign_message_update() adds inlen bytes from in to the data that's gonna be processed for signing. How those input bytes get processed β and whether there's even a limit on the total input size β is up to the signature algorithm spec and implementation. See "NOTES" below for the deeper explanation, bestie. π
EVP_PKEY_sign_message_final() signs the processed data and drops it in sig, and writes the number of signature bytes into *siglen, assuming that number of bytes doesn't exceed the size you gave in sigsize. Also, sig is allowed to be NULL β and in that case, only *siglen gets updated with the number of signature bytes. no drama. π
EVP_PKEY_sign() is the one-shot function and it works with all the init functions above. π
- If you initialized with
EVP_PKEY_sign_init(),EVP_PKEY_sign_init_ex()orEVP_PKEY_sign_init_ex2(), then the data given bytbsandtbslengets signed after appropriate padding. - If you initialized with
EVP_PKEY_sign_message_init(), then the data given bytbsandtbslengets digested by the implied message digest algorithm, and that result gets signed after appropriate padding.
If sig is NULL, then the maximum size of the output buffer gets written to the siglen parameter. And if sig is not NULL, then before the call your siglen parameter should already contain the length of the sig buffer, and if the call succeeds, the signature gets written to sig and the amount of data written goes into siglen. π
β¨ NOTES β¨
General
Okay so, some signature implementations just accumulate the input data and do no further processing before signing it (they literally expect the input to be a digest), while others compress the data β usually by making a digest internally and then signing that result. And some of them support both modes at the same time. π The caller is expected to know how the chosen algorithm is supposed to behave and under what conditions. yes, you have to read the room.
For example: an RSA implementation can be expected to only want a message digest as input, while ED25519 can be expected to process the input with a hash β i.e. produce the message digest internally. Meanwhile RSA-SHA256 can be expected to handle either mode of operation, depending on whether the operation was initialized with EVP_PKEY_sign_init_ex2() or with EVP_PKEY_sign_message_init(). lowkey neat. π¦
Same energy here: an RSA implementation usually expects extra details to be set, like the message digest algorithm the input is supposed to be digested with, plus the padding mode (peep EVP_PKEY_CTX_set_signature_md(3), EVP_PKEY_CTX_set_rsa_padding(3) and similar others). But an RSA-SHA256 implementation usually has all that pre-set and immutable. π
β οΈ The functions described here can't be used to combine separate algorithms. In particular, neitherEVP_PKEY_CTX_set_signature_md(3)nor theOSSL_PARAMparameter"digest"(OSSL_SIGNATURE_PARAM_DIGEST) can be used to combine a signature algorithm with a hash algorithm to process the input. In other words: it is not possible to take actxpre-loaded with an RSA pkey, or analgothat fetched"RSA", and then try to specify SHA256 separately hoping to get the functionality ofRSA-SHA256. π βοΈ If combining algorithms like that is what you want, please useEVP_DigestSignInit(3)and associated functions instead. π
Performing multiple signatures
When you initialize with EVP_PKEY_sign_init_ex() or EVP_PKEY_sign_init_ex2(), you can call EVP_PKEY_sign() more than once on the same context, so several one-shot operations get performed using the same parameters. slay. π₯
But when you initialize with EVP_PKEY_sign_message_init(), it is not possible to call EVP_PKEY_sign() multiple times. one and done, bestie. π
β¨ RETURN VALUES β¨
All these functions return 1 for success and 0 or a negative value for failure. πΈ
Specifically β and this is the important little detail β EVP_PKEY_sign_init() and its other variants may return -2 to signal that the operation is not supported by the public key algorithm. π
β¨ EXAMPLES β¨
ok bestie, this is the part where it all clicks, i prom π every single one of these is copy-paste-able and i'm keeping them character-for-character because that's the whole point.
π RSA with PKCS#1 padding for SHA256
sign some data with RSA + PKCS#1 padding, and have a SHA256 digest be the input. here's the whole vibe:
#include <openssl/evp.h>
#include <openssl/rsa.h>
EVP_PKEY_CTX *ctx;
/* md is a SHA-256 digest in this example. */
unsigned char *md, *sig;
size_t mdlen = 32, siglen;
EVP_PKEY *signing_key;
/*
* NB: assumes signing_key and md are set up before the next
* step. signing_key must be an RSA private key and md must
* point to the SHA-256 digest to be signed.
*/
ctx = EVP_PKEY_CTX_new(signing_key, NULL);
if (ctx == NULL)
/* Error occurred */
if (EVP_PKEY_sign_init(ctx) <= 0)
/* Error */
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
/* Error */
if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0)
/* Error */
/* Determine buffer length */
if (EVP_PKEY_sign(ctx, NULL, &siglen, md, mdlen) <= 0)
/* Error */
sig = OPENSSL_malloc(siglen);
if (sig == NULL)
/* malloc failure */
if (EVP_PKEY_sign(ctx, sig, &siglen, md, mdlen) <= 0)
/* Error */
/* Signature is siglen bytes written to buffer sig */
see the little two-step waltz? π you ask for the length first with NULL, alloc, then actually sign. no guessing at buffer sizes, periodt.
π¦ RSA-SHA256 with a pre-computed digest
sign a digest with RSA-SHA256 using the one-shot functions. important note incoming: RSA-SHA256 is assumed to be an implementation of "sha256WithRSAEncryption", which means the padding is pre-determined to be RSA_PKCS1_PADDING, and the input digest is assumed to have been computed using SHA256. no take-backs, that's just the deal.
#include <openssl/evp.h>
#include <openssl/rsa.h>
EVP_PKEY_CTX *ctx;
/* md is a SHA-256 digest in this example. */
unsigned char *md, *sig;
size_t mdlen = 32, siglen;
EVP_PKEY *signing_key;
/*
* NB: assumes signing_key and md are set up before the next
* step. signing_key must be an RSA private key and md must
* point to the SHA-256 digest to be signed.
*/
ctx = EVP_PKEY_CTX_new(signing_key, NULL);
alg = EVP_SIGNATURE_fetch(NULL, "RSA-SHA256", NULL);
if (ctx == NULL)
/* Error occurred */
if (EVP_PKEY_sign_init_ex2(ctx, alg, NULL) <= 0)
/* Error */
/* Determine buffer length */
if (EVP_PKEY_sign(ctx, NULL, &siglen, md, mdlen) <= 0)
/* Error */
sig = OPENSSL_malloc(siglen);
if (sig == NULL)
/* malloc failure */
if (EVP_PKEY_sign(ctx, sig, &siglen, md, mdlen) <= 0)
/* Error */
/* Signature is siglen bytes written to buffer sig */
ngl this one lowkey ate π EVP_PKEY_sign_init_ex2 carries the algorithm, so you don't have to hand-set padding and md separately. slay.
πΈ RSA-SHA256, one-shot
sign an actual document with RSA-SHA256 in one shot. same disclaimer as before, bestie: RSA-SHA256 is assumed to be an implementation of "sha256WithRSAEncryption", for which the padding is pre-determined to be RSA_PKCS1_PADDING.
#include <openssl/evp.h>
#include <openssl/rsa.h>
EVP_PKEY_CTX *ctx;
/* in is the input in this example. */
unsigned char *in, *sig;
/* inlen is the length of the input in this example. */
size_t inlen, siglen;
EVP_PKEY *signing_key;
EVP_SIGNATURE *alg;
/*
* NB: assumes signing_key, in and inlen are set up before
* the next step. signing_key must be an RSA private key,
* in must point to data to be digested and signed, and
* inlen must be the size of the data in bytes.
*/
ctx = EVP_PKEY_CTX_new(signing_key, NULL);
alg = EVP_SIGNATURE_fetch(NULL, "RSA-SHA256", NULL);
if (ctx == NULL || alg == NULL)
/* Error occurred */
if (EVP_PKEY_sign_message_init(ctx, alg, NULL) <= 0)
/* Error */
/* Determine sig buffer length */
if (EVP_PKEY_sign(ctx, NULL, &siglen, in, inlen) <= 0)
/* Error */
sig = OPENSSL_malloc(siglen);
if (sig == NULL)
/* malloc failure */
if (EVP_PKEY_sign(ctx, sig, &siglen, in, inlen) <= 0)
/* Error */
/* Signature is siglen bytes written to buffer sig */
notice EVP_PKEY_sign_message_init instead of ..._sign_init π that's the glow-up that lets you feed it real data instead of a pre-chewed digest.
π RSA-SHA256, using update and final
literally the same as the last one, but with stream-like functionality π§ yes queen, chunk it up.
#include <openssl/evp.h>
#include <openssl/rsa.h>
EVP_PKEY_CTX *ctx;
/* in is the input in this example. */
unsigned char *in, *sig;
/* inlen is the length of the input in this example. */
size_t inlen, siglen;
EVP_PKEY *signing_key;
EVP_SIGNATURE *alg;
/*
* NB: assumes signing_key, in and inlen are set up before
* the next step. signing_key must be an RSA private key,
* in must point to data to be digested and signed, and
* inlen must be the size of the data in bytes.
*/
ctx = EVP_PKEY_CTX_new(signing_key, NULL);
alg = EVP_SIGNATURE_fetch(NULL, "RSA-SHA256", NULL);
if (ctx == NULL || alg == NULL)
/* Error occurred */
if (EVP_PKEY_sign_message_init(ctx, alg, NULL) <= 0)
/* Error */
while (inlen > 0) {
if (EVP_PKEY_sign_message_update(ctx, in, inlen)) <= 0)
/* Error */
if (inlen > 256) {
inlen -= 256;
in += 256;
} else {
inlen = 0;
}
}
/* Determine sig buffer length */
if (EVP_PKEY_sign_message_final(ctx, NULL, &siglen) <= 0)
/* Error */
sig = OPENSSL_malloc(siglen);
if (sig == NULL)
/* malloc failure */
if (EVP_PKEY_sign_message_final(ctx, sig, &siglen) <= 0)
/* Error */
/* Signature is siglen bytes written to buffer sig */
so the loop hands it 256 bytes at a time, and when you're done you call EVP_PKEY_sign_message_final β once with NULL to suss out siglen, once for real β¨ chef's kiss.
π₯ SEE ALSO π₯
go read these too, they're all in the same friend group:
EVP_PKEY_CTX_new(3)EVP_PKEY_CTX_ctrl(3)EVP_PKEY_encrypt(3)EVP_PKEY_decrypt(3)EVP_PKEY_verify(3)EVP_PKEY_verify_recover(3)EVP_PKEY_derive(3)
π HISTORY π
the lore, chronologically:
EVP_PKEY_sign_init()andEVP_PKEY_sign()β added in OpenSSL 1.0.0. the OGs.EVP_PKEY_sign_init_ex()β added in OpenSSL 3.0.EVP_PKEY_sign_init_ex2(),EVP_PKEY_sign_message_init(),EVP_PKEY_sign_message_update()andEVP_PKEY_sign_message_final()β where added in OpenSSL 3.4. (yes, "where", that's literally what it says bestie, i'm not touching it π)
π COPYRIGHT π
Copyright 2006-2025 The OpenSSL Project Authors. All Rights Reserved.
Licensed under the Apache License 2.0 (the "License"). You may not use this file except in compliance with the License. You can obtain a copy in the file LICENSE in the source distribution or at <https://www.openssl.org/source/license.html>.
ok that's the whole page, you made it π«Ά this is EVP_PKEY_SIGN(3SSL) from OpenSSL 4.0.3, dated 2026-09-29 β go forth and sign things, bestie, i'm rooting for you πβ¨πΊ
π¦ where this page comes from
- ubuntu stonking: libssl-doc 4.0.3-1ubuntu1 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms