In MySuiteA's view, a cryptographic algorithm consist of a set of functions that transforms operands between various forms such as plaintext message, ciphertext, digest, signature, predicate, and/or other form of information;
a cryptographic algorithm is associated with a set of properties that are intrinsic of the algorithm (e.g. key and key schedule size, output size, input length limit);
an algorithm may be based on another algorithm, a.k.a. an algorithm is parameterized by another (set of) algorithm(s) - for example, a digital signature may be instantiated with a hash function, an encryption algorithm may be instantiated with a blockcipher, etc.
Based on the above premise, the term algorithm construction refers to algorithms that needs to be instantiated with another (set of) algorithm(s) or other types of parameters; the term algorithm instance refers to algorithms which have all necessary parameterizations (if any) specified and can operate on operands in a definite way.
The term algorithm can refer to either algorithm construction or algorithm instance. The term algorithm family is banned from use in MySuiteA documentations as it is ambiguous.
MySuiteA implements a uniform interface for using and instantiating from cryptographic algorithms at compile, link, and run time.
At compile time, a function-like macro whose name consists of the name
of the algorithm prefixed with a single lower-case letter "c" is defined.
This macro takes a single argument q, which
specifies the property associated with the algorithm to be queried, and
evaluates to the value of that property.
For other queries, this macro evaluates to 0.
At link time, a similar macro exists. In addition to the queries available in the compile time queries, the pointers to the set of functions constituting the algorithm may be queried. The prefix of this macro is "x" instead of "c".
At run time, a function with the exact behavior of the link time macro exists. A function is needed so as to enable run-time instantiation, as a function can be encapsulated in a pointer to the function. Such a function is colloquially called a "crypto object".
For the set of queries available (and their applicability to particular types of algorithm), see "mysuitea-common.h".
The "crypto object" function mentioned above takes a single argument. It has the prototype:
IntPtr (*iCryptoObj_t)(int q);
where IntPtr is a signed integer type with same width as
a pointer type. By default this is defined to be the same as
intptr_t, however on systems where object and function
pointer types have different representation, this may be changed to
something else by the user of the MySuiteA library.
A algorithm construction crypto object is represented as a function that takes a parameterization argument first, then the query. Such function has the prototype:
IntPtr (*tCryptoObj_t)(const CryptoParam_t *P, int q);
where CryptoParam_t contains the following members:
iCryptoObj_t info;
This member is used when param and aux are
both NULL, which indicates this parameter is an
algorithm instance.
tCryptoObj_t factory;
This member is used if param or aux is
non-NULL, which indicates this parameter is an
algorithm construction that takes further instantiation parameter(s).
Previously, this member was named
,
however due to conflict with the C++ keyword, it was renamed.
template
The members info and factory are aliased
together in a union data structure.
const CryptoParam_t *param;
If used, this is passed as the first argument to factory.
This allows an algorithm construction to be recursively instantiated
by other algorithm constructions and instances.
IntPtr aux
If used, this is passed as the first argument to factory.
This member allow certain algorithm constructions to be instantiated with
miscellaneous objects and values and in ways not defined in MySuiteA.
This member is aliased to param in a union
data structure, so that caller of factory doesn't have to
be aware of its argument semantics.
Both the parameter P and the member param may
point to both a single CryptoParam_t object, or the initial
element of an array of which - in the case of an array, the algorithm
construction is said to be parameterized by multiple elements.
Previously (before v0.2) there was a PKParamsFunc query for
obtaining instantiation parameters for use in PKC algorithms. The way
it's implemented caused inflexibility with re-combining PKC Algorithms
with other instantiation parameters.
The approach taken in v0.2 is to have a PKC_Algo_Inst_t
data structure, representing an PKC algorithm instance, defined as:
typedef struct {
int secbits;
tCryptoObj_t algo;
CryptoParam_t *param;
} PKC_Algo_Inst_t;
Initial set of instantiation parameters for the algorithms are declared
in the header files named ${algorithm-name}-paramset.h, and
defined in the corresponding C source code file. Algorithm instances are
generally named as ${Algorithm-Name}_${Parameter}
To use an algorithm instance, after checking it provides the desired
security level, query algo with param
as instantiation parameter, and obtain relevant algorithm
characteristics and interfaces.
Parallel and tree hashing had been an active area of research, with results such as KangarooTwelve and BLAKE3 turning up. These 2 algorithms are implemented in the Suite with a thin layer of hosted library support.
The model chosen for these multi-thread capable algorithms, is to implement them in such way that, they can take a parallel object to dispatch independent workloads to a crew of threads in a hosted environment; equally, in a freestanding environment, the thread crew can just be a stub that executes the workload subroutine within the control flow with no benefit of any concurrency.
The thread crew is provided as a pointer to a structure which contains
at least 2 interfaces - an enqueue that dispatches the
workload, and a wait that waits for all currently dispatched
workloads to complete. The thread crew instances shall have structures
that're layout-compatible with the TCrew_Abstract_t
data type, as defined here:
typedef void (*TCrew_Assignment_t)(void *ctx);
typedef int (*TCrew_Dispatch_t)(
void *crew, TCrew_Assignment_t func, void *ctx);
typedef struct {
TCrew_Dispatch_t enqueue;
TCrew_Dispatch_t wait;
} TCrew_Abstract_t;
MySuiteA strives to implement a uniform set of interface where there's a common API for each type of cryptographic algorithm. This interface is layered, where higher-level schemes may be instantiated from lower-level primitives. As explained in the note in "notes.md" dated [2021-09-03b], blockciphers, permutations, hash, and XOF functions never take parameters to instantiate from, and will always be algorithm instances.
MySuiteA is an octet-oriented implementation, and is written with the assumption that 1 byte is exactly 8 bits (1 octet). It also assumes that 16-bit, 32-bit, 64-bit exact-width integers are available.
In the following sub-sections, property queries available to a particular
type of cryptographic algorithm are listed in code shading,
each followed by a description of its semantic. The following shorthands
are used in function prototype descriptions:
buf | void * |
|---|---|
dat | void const * |
rbuf |
void *restrict |
rdat |
void const *restrict |
_param_ |
const CryptoParam_t *P |
_tc_ |
TCrew_Abstract_t *restrict tc
|
A blockcipher is a fixed-width keyed permutation. MySuiteA currently only support a single block size of 128 bits. Use of 64-bit blocks have negative security implications; blockciphers with larger block sizes currently lack support from higher-level algorithm constructions and therefore have poorer interoperability and is of lesser use.
Note the phrase "blockcipher instance" is used to distinguish it from a blockcipher family, which may specify many variants of blockcipher instances.
blockByteskeyByteskeyschedBytesEncFuncvoid (*)(dat in, buf out, rdat w); where
in is the input plaintext block, out is the output ciphertext block, and w is the key schedule generated from the key. DecFuncvoid (*)(dat in, buf out, rdat w); where
in is the input ciphertext block, out is the output plaintext block, and w is the key schedule generated from the key. KschdFuncvoid (*)(rdat key, rbuf w); where
key is cipher key to use, w is the buffer to hold the key schedule. [note:in-out-ptr-alias]
Note that neither in nor out has the
restrict pointer qualifier. This is an intentional
design decision for allowing in-place cipher computation.
It is implemented as if the encryption and decryption functions first
copied input to output buffer before encryption/decryption.
blockBytesPermuteFuncvoid (*)(dat in, buf out); where
in is the input block, out is the output block. See note tagged [note:in-out-ptr-alias] in the Blockcipher section.
outBytesblockByteskeyBytes (MAC-specific)contextBytesInitFunc (hash-specific)void (*)(rbuf x); where
x is the pointer
to the working context in memory.
KInitFunc (MAC-specific)void *(*)(rbuf x, rdat k, size_t klen);;
or if the construction is parameterized, the function of prototype
void *(*)(_param_, rbuf x, rdat k, size_t klen); where
_param_ is the instantiation parameter, x is the pointer,
to the working context in memory.
k is MAC key to use, klen is length of the key input. NULL. Otherwise, everything succeeds, and
x is returned.
UpdateFuncvoid (*)(rbuf x, rdat data, size_t len); where
x is the pointer to the working context, data the data to feed into the working context,len the length of the data. data is NULL
while len is non-zero, the remaining bytes in the block
(whose size is defined by blockBytes) are padded and
1 invocation of the state transformation function is executed.
This functionality is provided for the some schemes that make use
of "alternative initialization vector", either for accelerated
processing from cached state(s) (e.g. an optimization option
for SLH-DSA), or for de-correlation between secret and public data
as a side-channel mitigation (e.g. recommended by
[SBBDS17]).
FinalFuncvoid (*)(rbuf x, rdat out, size_t t); where
x is the pointer to the working context, out
is the pointer to the memory location to which,
the hash digest will be written,
len
is the requested length of the digest.
[note:outlen]: It is a convention of MySuiteA that, if the requested length is greater than (or less than) the digest/tag length of the hash/MAC function, then the output will be zero-extended (or truncated).
Additionally, for parallel and tree hashing algorithms, there are:
chunkBytesUpdate4FuncTCrew_Abstract_t to potentially
provide concurrent computation. It has the prototype
void (*)(rbuf x, rdat data, size_t len, _tc_); where
x is the pointer to the working context,data the data to feed into the working context,len the length of the data, andtc
the thread crew that will receive the dispatched workload.Final2Funcvoid (*)(rbuf x, _tc_); where
x is the pointer to the working context,tc
the thread crew that had been used with the working context.Read4Funcvoid (*)(rbuf x, rbuf data, size_t len, int flags);
where
x is the pointer to the working context,data is the pointer to the output buffer,len is the length of digest data to retrieve,flags specifies miscellaneous information.
Because newer parallel and tree hashing algorithms are commonly
XOFs, by default, this function behaves identically to XOF's
ReadFunc when flags is 0. There is the flag
HASHING_READ4_REWIND that resets the reading offset
of the hashing context to the start, and it's currently the only flag
defined for Read4Func.
outTruncBytesblockBytescontextBytesInitFunc (hash-specific)void (*)(rbuf x); where
x is the pointer
to the working context in memory.
WriteFuncvoid (*)(rbuf x, rdat data, size_t len); where
x is the pointer to the working context, data the data to feed into the working context,len the length of the data. XofFinalFuncvoid (*)(rbuf x); where
x is the pointer to the working context, ReadFuncvoid (*)(rbuf x, rbuf data, size_t len); where
x is the pointer to the working context, data the buffer into which data will be read, len the requested length of the data.
Because WriteFunc and UpdateFunc have
identical prototype, and that they serve very similar purpose,
these 2 symbolic constants are aliased together by having
identical numerical value.
Some algorithms (e.g. KMAC) can operate in both XOF and regular
fixed-length mode, and may support domain separation between different
usage. These algorithms usually support all three of
FinalFunc,
XofFinalFunc, and
ReadFunc.
And, their FinalFunc behave similar to
XofFinalFunc and ReadFunc called in
adjacent sequence.
keyBytescontextBytesivBytesKInitFuncvoid *(*)(rbuf x, rdat k, size_t klen);;
or if it's a parameterized algorithm construction,
the function of prototype
void *(*)(_param_, rbuf x, rdat k, size_t klen); where
_param_ is the instantiation parameter, x is the pointer
to the working context in memory,
k is cipher key to use, klen is length of the key input. NULL. Otherwise, everything succeeds, and
x is returned.
AEncFuncvoid *(*)(
rbuf x,
size_t ivlen, dat iv,
size_t alen, dat aad,
size_t len, dat in, buf out,
size_t tlen, buf T);
where
x is the cipher working context, ivlen is the byte length of iv, iv points to the initialization vector, alen is the byte length of aad, aad
is the additional data to be authenticated but not encrypted,
len
is the byte length of plaintext and ciphertext,
in, out
are plaintext input and ciphertext output respectively,
tlen
is the requested length of the tag,
T
is the buffer in which the tag will be stored.
out will be returned.
Otherwise, if there's unsupported/invalid parameter(s) or
any other kind of error, NULL will be returned.
ADecFuncvoid *(*)(
rbuf x,
size_t ivlen, dat iv,
size_t alen, dat aad,
size_t len, dat in, buf out,
size_t tlen, dat T);
where
x is the cipher working context, ivlen is the byte length of iv, iv points to the initialization vector, alen is the byte length of aad, aad
is the additional non-encrypted data to be verified,
len
is the byte length of plaintext and ciphertext,
in, out
are ciphertext input and plaintext output respectively,
tlen
is the length of the MAC tag to be compared,
which may be truncated or zero-extended
at the request of the invoker of the encryption function,
T
is the buffer that stores the MAC tag.
out will be returned.
Otherwise, if there's decryption failure, unsupported/invalid
parameter(s) or any other kind of error, NULL
will be returned.
See notes tagged [note:in-out-ptr-alias] in the Blockcipher section and [note:outlen] in the Hash Function & Message Authentication Code section.
contextBytesseedBytesInstInitFuncvoid *(*)(rbuf x, rdat seedstr, size_t len); or
if the construction is parameterized, the function of prototype:
void *(*)(_param_, rbuf x, rdat seedstr, size_t len);
where
_param_ is the instantiation parameter, x is the pointer to the working context, seedstr is the pointer to the PRNG seed, len is the length of the seed in bytes, NULL. Otherwise, everything
succeeds, and x is returned.
ReseedFuncvoid (*)(rbuf x, rdat seedstr, size_t len); where
x is the pointer to the PRNG working context, seedstr
is the pointer to the reseeding material,
len
is the length of the reseeding material in bytes.
GenFuncvoid (*)(rbuf x, rbuf out, size_t len); where
x is the pointer to the PRNG working context, seedstr
is the pointer to the memory location to store the random bytes,
len
is the length of the requested random bytes.
Due to functional similarity as well as them having
identical prototypes,
InstInitFunc,
ReseedFunc, and
GenFunc
are aliased to
KInitFunc,
WriteFunc, and
ReadFunc respectively, by them having
identical corresponding numerical values.
The API for key-pair encoding and decoding is common to both PKE/KEM and signature schemes, therefore it's described here in the first place.
The encoder functions have the prototype:
IntPtr (*PKKeyEncoder_t)(
rdat any,
rbuf enc,
size_t enclen,
_param_);
where as the decoder functions have the prototype:
IntPtr (*PKKeyDecoder_t)(
rbuf any,
rdat enc,
size_t enclen,
_param_);
The any parameter points to the working context of the
PKC algorithm, which may be the public-key or the private-key
working context depending on the usage. The enc parameter
points to the buffer that holds the encoded key. The enclen
specifies the length of this buffer. The _param_ parameter
specifies the instantiation parameters used for the algorithm, which will
be taken into account when estimating the size of the working context.
These functions have 2 passes. In the 1st pass, these functions returns the estimated size of encoded key (encoder) or the working context (decoder). If some error occurs, then -1 is returned. The actual encoding/decoding is carried out in the 2nd pass. The return value in the 2nd pass is the same as that from the 1st pass.
Such setup is necessary, as some key formats (e.g. ASN.1 DER encoding of RSA private keys) have variable encoded lengths depending on the values of components of the key material, even when the parameters are otherwise constant.
In pass 1, the enc and enclen should be
specified as NULL and 0 respectively for the encoder
(although enclen is ignored for now, it may have other
reserved meaning in future versions of MySuiteA); for the decoder,
any should be specified as NULL.
In pass 2, all of any, enc,
enclen, and _param_ should be specified with
valid values.
PKKengenFuncPKPrivkeyEncoderPKKeyEncoder_t where any
is the private-key working context
PKPrivkeyDecoderPKKeyDecoder_t where any
is the private-key working context
PKPubkeyExporterPKKeyEncoder_t where any
is the private-key working context
PKPubkeyEncoderPKKeyEncoder_t where any
is the public-key working context
PKPubkeyDecoderPKKeyDecoder_t where any
is the public-key working context
The interface for encoding and decoding is the same for both KEM/PKE and digital signatures. They're associated with the PKC algorithm through the public/private function pair.
The encoder functions have the prototype:
void *(*PKCiphergramEncoder_t)(
rbuf x,
rbuf c, size_t *len);
The encoder function saves the ciphergram (ciphertext or signature)
into c. It returns c on success and
NULL otherwise. If c is NULL
then *len is set to its length.
Such set up is necessary as some formats of some signatures (e.g. ECDSA when encoding (r,s) using ASN.1 DER encoding, or the Falcon PQC digital signature scheme) have lengths that're variable.
The decoder functions have the prototype:
void *(*PKCiphergramDecoder_t)(
rbuf x,
rdat c, size_t len);
The decoder function loads the ciphergram into the working context of
the PKC algorithm. It returns x on success and
NULL on failure.
PKEncFunc (KEM-specific)PKDecFunc (KEM-specific)PKSignFunc (signature scheme -specific)PKVerifyFunc (signature scheme -specific)PKCtEncoderPKCiphergramEncoder_t.
Ct means cipher transcript.PKCtDecoderPKCiphergramDecoder_t.
Ct means cipher transcript.isParamDetermByKey
If the value of this query is 1, then the values of the queries
bytesCtxPriv and bytesCtxPub can only
be used for private contexts for generating keys, as the parameters
and the size of the working contexts are variable depending on the
key to be loaded.
On the other hand, if the value of this query is 0, then
bytesCtxPriv and bytesCtxPub can be
used to set parameters for the working context, as they're not
determined by the key. See "mysuitea-common.h" for more.
bytesCtxPrivbytesCtxPubPKKeygenFunc
The key generation function of prototype:
IntPtr (*PKKeygenFunc_t)(
rbuf x, _param_,
GenFunc_t prng_gen, rbuf prng);
where:
x points to the private-key working context,_param_ specifies the parameter
for generating the new key,prng_gen is the PRNG
random bits generating function,prng is the working context for the PRNG.This function generates a key-pair and save its internal representation in the working context, which can be retrieved using key encoder/exporter functions and loaded using decoder functions.
If x is NULL, then then function returns
the estimated space needed for allocating a working context based on
the given parameters; otherwise, x is returned.
PKEncFunc
The KEM secret encapsulation function of prototype:
void *(*PKEncFunc_t)(
rbuf x,
rbuf ss, size_t *restrict sslen,
GenFunc_t prng_gen, rbuf prng);
where:
x points to the public-key working context,ss points to the buffer that will
receives the shared secret,sslen points to a variable of type
size_t. If ss is NULL,
this variable will be set to the length of the shared secret.
If ss is non-NULL, the variable
indicates the size of the buffer pointed to by ss,
in which case, at most *sslen bytes will be written
to ss, and if there's excess space than needed to
store the shared secret, the result will be right-padded with
nul bytes.
prng_gen is the PRNG
random bits generating function,prng is the working context for the PRNG.
This function generates a shared secret and a ciphertext based on
the public key loaded into the public-key working context.
The shared secret is stored into ss while the ciphertext
is saved in a internal representation in the working context and
will be exported using a ciphergram encoding function.
PKDecFunc
The KEM secret decapsulation function of prototype:
void *(*PKDecFunc_t)(
rbuf x,
rbuf ss, size_t *restrict sslen);
where:
x points to the private-key working context,ss points to the buffer that will
receives the shared secret,sslen points to a variable of type
size_t. If ss is NULL,
this variable will be set to the length of the shared secret.
If ss is non-NULL, the variable
indicates the size of the buffer pointed to by ss,
in which case, at most *sslen bytes will be written
to ss, and if there's excess space than needed to
store the shared secret, the result will be right-padded with
nul bytes.
This function decapsulates the shared secret from the ciphertext using the private key. The ciphertext and the private key are loaded into the private-key working context using the private key decoder function and the ciphertext decoder function. For some cryptosystems, the ciphertext may be of variable length (e.g. RSA PKE), as such, repeating the call returns the same information unless a different ciphertext is loaded using the ciphergram decoder function.
isParamDetermByKeybytesCtxPrivbytesCtxPubPKKeygenFuncSame as that for KEMs'.
dssNonceNeededdssExternRngNeededForNoncefalse, otherwise true.PKSignFunc
The digital signature signing function of prototype:
void *(*PKSignFunc_t)(
rbuf x,
rdat msg, size_t msglen,
GenFunc_t prng_gen, rbuf prng);
where:
x points to the private-key working context,msg is the buffer
holding the message to be signed,msglen is the length of the message in bytes,prng_gen is the PRNG
random bits generating function,prng is the working context for the PRNG.
The function signs the message msg and saves the
internal representation of the signature in x to be
exported using the ciphergram encoding function. It returns
x on success and NULL on failure.
PKVerifyFunc
The digital signature signing function of prototype:
const void *(*PKVerifyFunc_t)(
rbuf x,
rdat msg, size_t msglen);
where:
x points to the public-key working context,msg is the buffer
holding the message to be verified,msglen is the length of the message in bytes,
The function verifies the message msg with the
public key and the signature loaded into the public-key working
context using the public-key and ciphergram decoder function.
It returns msg if signature is valid, and
NULL otherwise.
Many existing digital signature schemes actually signs a hash digest of the message. Those that does this including RSA (PKCS#1 v1.5 and PSS), ECDSA, and some variants of EdDSA specified in RFC-8032. To realize their full potential, incremental signing are devised.
dssPreHashingTypeThe type of pre-hashing supported by the algorithm instance. It can be one of the following enumerations:
dssPreHashing_Unsupported:
This algorithm does not support pre-hashing at all.dssPreHashing_Interface:
Pre-hashing offered in an interface, and the algorithm behaves
the same as if the message is buffered and signed all-at-once.dssPreHashing_Variant:
Pre-hashing offered in an interface, but algorithm will behave
differently from that of buffering and signing all-at-once.dssPreHashing_ParamSet:
Pre-hashing is supported in a separate algorithm instance.PKIncSignInitFuncPKIncVerifyInitFuncThe initialization functions for incremental signing and incremental verifying. They have the prototype:
void *(*PKIncSignInitFunc_t)(rbuf x, UpdateFunc_t *placeback);
This type has an alias: PKIncVerifyInitFunc_t.
When called, it initializes a hashing context and prepare it with algorithm-specific prefix data. Before returning the pointer to this working context, it places a pointer to the update function of this hashing function into placeback.
PKIncSignFinalFuncThe function that completes hashing of and produce a signature for the hashed message, of the prototype:
void *(*PKIncSignFinalFunc_t)(
rbuf x,
GenFunc_t prng_gen, rbuf prng);
where:
x points to the private-key working context,prng_gen is the PRNG
random bits generating function,prng is the working context for the PRNG.PKIncVerifyFinalFuncThe function that completes hashing and verifies the signature of the hashed message, of the prototype:
void *(*PKIncVerifyFinalFunc_t)(rbuf x);
where:
x points to the public-key working context,Context control functions are a class of catch-all functions that performs operations that're algorithm-specific. Generally, these operations involve additional parameters that can't be passed using other uniform APIs.
XctrlFuncPubXctrlFuncPrivXctrlFuncThe context control function of prototype:
void *(*XctrlFunc_t)(
rbuf x,
int cmd,
const bufvec_t *restrict bufvec,
int veclen,
int flags);
where:
x points to the
working context of the algorithm,cmd specifies the sub-function to perform,bufvec is the array of data buffer elements
passed to the sub-functionveclen specifies the length of the array,flags specifies additional information
for the sub-function, with 0 as a reserved default,
The query XctrlFunc generally apply to symmetric-key
algorithms,
whereas PubXctrlFunc and PrivXctrlFunc
apply to the public and private working contexts of the
asymmetric-key algorithm. And the type definition for
bufvec_t is:
typedef struct {
union {
size_t len;
IntPtr info;
};
union {
void const *dat;
void *buf;
};
} bufvec_t;