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 template;
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).
The members info
and template
are aliased
together in a union
data structure.
const CryptoParam_t *param;
If used, this is passed as the first argument to template
.
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 template
.
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 template
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.
blockBytes
keyBytes
keyschedBytes
EncFunc
void (*)(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. DecFunc
void (*)(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. KschdFunc
void (*)(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.
blockBytes
PermuteFunc
void (*)(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.
outBytes
blockBytes
(hash-specific)keyBytes
(MAC-specific)contextBytes
InitFunc
(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.
UpdateFunc
void (*)(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. FinalFunc
void (*)(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:
Update4Func
TCrew_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.Final2Func
void (*)(rbuf x, _tc_);
where
x
is the pointer to the working context,tc
the thread crew that had been used with the working context.Read4Func
void (*)(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
.
outTruncBytes
blockBytes
(hash-specific)contextBytes
InitFunc
(hash-specific)void (*)(rbuf x);
where
x
is the pointer
to the working context in memory.
WriteFunc
void (*)(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. XofFinalFunc
void (*)(rbuf x);
where
x
is the pointer to the working context, ReadFunc
void (*)(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.
keyBytes
contextBytes
ivBytes
KInitFunc
void *(*)(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.
AEncFunc
void *(*)( 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.
ADecFunc
void *(*)( 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.
contextBytes
seedBytes
InstInitFunc
void *(*)(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.
ReseedFunc
void (*)(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.
GenFunc
void (*)(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.
PKKengenFunc
PKPrivkeyEncoder
PKKeyEncoder_t
where any
is the private-key working context
PKPrivkeyDecoder
PKKeyDecoder_t
where any
is the private-key working context
PKPubkeyExporter
PKKeyEncoder_t
where any
is the private-key working context
PKPubkeyEncoder
PKKeyEncoder_t
where any
is the public-key working context
PKPubkeyDecoder
PKKeyDecoder_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)PKCtEncoder
PKCiphergramEncoder_t
.
Ct
means cipher transcript.PKCtDecoder
PKCiphergramDecoder_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.
bytesCtxPriv
bytesCtxPub
PKKeygenFunc
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.
isParamDetermByKey
bytesCtxPriv
bytesCtxPub
PKKeygenFunc
Same as that for KEMs'.
dssNonceNeeded
dssExternRngNeededForNonce
false
, 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.
dssPreHashingType
The 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
:
PrePre-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.PKIncSignInitFunc
PKIncVerifyInitFunc
The 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.
PKIncSignFinalFunc
The 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.PKIncVerifyFinalFunc
The 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.
XctrlFunc
PubXctrlFunc
PrivXctrlFunc
The 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;