mirror of
https://github.com/openssl/openssl.git
synced 2024-12-21 06:09:35 +08:00
269 lines
7.7 KiB
Markdown
269 lines
7.7 KiB
Markdown
|
XOF Design
|
||
|
==========
|
||
|
|
||
|
XOF Definition
|
||
|
--------------
|
||
|
|
||
|
An extendable output function (XOF) is defined as a variable-length hash
|
||
|
function on a message in which the output can be extended to any desired length.
|
||
|
|
||
|
At a minimum an XOF needs to support the following pseudo-code
|
||
|
|
||
|
```text
|
||
|
xof = xof.new();
|
||
|
xof.absorb(bytes1);
|
||
|
xof.absorb(bytes2);
|
||
|
xof.finalize();
|
||
|
out1 = xof.squeeze(10);
|
||
|
out2 = xof.squeeze(1000);
|
||
|
```
|
||
|
|
||
|
### Rules
|
||
|
|
||
|
- absorb can be called multiple times
|
||
|
- finalize ends the absorb process (by adding padding bytes and doing a final
|
||
|
absorb). absorb must not be called once the finalize is done unless a reset
|
||
|
happens.
|
||
|
- finalize may be done as part of the first squeeze operation
|
||
|
- squeeze can be called multiple times.
|
||
|
|
||
|
OpenSSL XOF Requirements
|
||
|
------------------------
|
||
|
|
||
|
The current OpenSSL implementation of XOF only supports a single call to squeeze.
|
||
|
The assumption exists in both the high level call to EVP_DigestFinalXOF() as
|
||
|
well as in the lower level SHA3_squeeze() operation (Of which there is a generic
|
||
|
c version, as well as assembler code for different platforms).
|
||
|
|
||
|
A decision has to be made as to whether a new API is required, as well as
|
||
|
considering how the change may affect existing applications.
|
||
|
The changes introduced should have a minimal affect on other related functions
|
||
|
that share the same code (e.g SHAKE and SHA3 share functionality).
|
||
|
Older providers that have not been updated to support this change should produce
|
||
|
an error if a newer core is used that supports multiple squeeze operations.
|
||
|
|
||
|
API Discussion of Squeeze
|
||
|
-------------------------
|
||
|
|
||
|
### Squeeze
|
||
|
|
||
|
Currently EVP_DigestFinalXOF() uses a flag to check that it is only invoked once.
|
||
|
It returns an error if called more than once. When initially written it also did
|
||
|
a reset, but that code was removed as it was deemed to be incorrect.
|
||
|
|
||
|
If we remove the flag check, then the core code will potentially call low level
|
||
|
squeeze code in a older provider that does not handle returning correct data for
|
||
|
multiple calls. To counter this the provider needs a mechanism to indicate that
|
||
|
multiple calls are allowed. This could just be a new gettable flag (having a
|
||
|
separate provider function should not be necessary).
|
||
|
|
||
|
#### Proposal 1
|
||
|
|
||
|
Change EVP_DigestFinalXOF(ctx, out, outlen) to handle multiple calls.
|
||
|
Possibly have EVP_DigestSqueeze() just as an alias method?
|
||
|
Changing the code at this level should be a simple matter of removing the
|
||
|
flag check.
|
||
|
|
||
|
##### Pros
|
||
|
|
||
|
- New API is not required
|
||
|
|
||
|
##### Cons
|
||
|
|
||
|
- Final seems like a strange name to call multiple times.
|
||
|
|
||
|
#### Proposal 2 (Proposed Solution)
|
||
|
|
||
|
Keep EVP_DigestFinalXOF() as a one shot function and create a new API to handle
|
||
|
the multi squeeze case e.g.
|
||
|
|
||
|
```text
|
||
|
EVP_DigestSqueeze(ctx, out, outlen).
|
||
|
```
|
||
|
|
||
|
##### Pros
|
||
|
|
||
|
- Seems like a better name.
|
||
|
- The existing function does not change, so it is not affected by logic that
|
||
|
needs to run for the multi squeeze case.
|
||
|
- The behaviour of the existing API is the same.
|
||
|
- At least one other toolkit uses this approach.
|
||
|
|
||
|
##### Cons
|
||
|
|
||
|
- Adds an extra API.
|
||
|
- The interaction between the 2 API's needs to be clearly documented.
|
||
|
- A call to EVP_DigestSqueeze() after EVP_DigestFinalXOF() would fail since
|
||
|
EVP_DigestFinalXOF() indicates no more output can be retrieved.
|
||
|
- A call to EVP_DigestFinalXOF() after the EVP_DigestSqueeze() would fail.
|
||
|
|
||
|
#### Proposal 3
|
||
|
|
||
|
Create a completely new type e.g. EVP_XOF_MD to implement XOF digests
|
||
|
|
||
|
##### Pros
|
||
|
|
||
|
- This would separate the XOF operations so that the interface consisted
|
||
|
mainly of Init, Absorb and Squeeze API's
|
||
|
- DigestXOF could then be deprecated.
|
||
|
|
||
|
##### Cons
|
||
|
|
||
|
- XOF operations are required for Post Quantum signatures which currently use
|
||
|
an EVP_MD object. This would then complicate the Signature API also.
|
||
|
- Duplication of the EVP_MD code (although all legacy/engine code would be
|
||
|
removed).
|
||
|
|
||
|
Choosing a name for the API that allows multiple output calls
|
||
|
-------------------------------------------------------------
|
||
|
|
||
|
Currently OpenSSL only uses XOF's which use a sponge construction (which uses
|
||
|
the terms absorb and squeeze).
|
||
|
There will be other XOF's that do not use the sponge construction such as Blake2.
|
||
|
|
||
|
The proposed API name to use is EVP_DigestSqueeze.
|
||
|
The alternate name suggested was EVP_DigestExtract.
|
||
|
The terms extract and expand are used by HKDF so I think this name would be
|
||
|
confusing.
|
||
|
|
||
|
API Discussion of other XOF API'S
|
||
|
---------------------------------
|
||
|
|
||
|
### Init
|
||
|
|
||
|
The digest can be initialized as normal using:
|
||
|
|
||
|
```text
|
||
|
md = EVP_MD_fetch(libctx, "SHAKE256", propq);
|
||
|
ctx = EVP_MD_CTX_new();
|
||
|
EVP_DigestInit_ex2(ctx, md, NULL);
|
||
|
```
|
||
|
|
||
|
### Absorb
|
||
|
|
||
|
Absorb can be done by multiple calls to:
|
||
|
|
||
|
```text
|
||
|
EVP_DigestUpdate(ctx, in, inlen);
|
||
|
```
|
||
|
|
||
|
#### Proposal:
|
||
|
|
||
|
Do we want to have an Alias function?
|
||
|
|
||
|
```text
|
||
|
EVP_DigestAbsorb(ctx, in, inlen);
|
||
|
```
|
||
|
|
||
|
(The consensus was that this is not required).
|
||
|
|
||
|
### Finalize
|
||
|
|
||
|
The finalize is just done as part of the squeeze operation.
|
||
|
|
||
|
### Reset
|
||
|
|
||
|
A reset can be done by calling:
|
||
|
|
||
|
```text
|
||
|
EVP_DigestInit_ex2(ctx, NULL, NULL);
|
||
|
```
|
||
|
|
||
|
### State Copy
|
||
|
|
||
|
The internal state can be copied by calling:
|
||
|
|
||
|
```text
|
||
|
EVP_MD_CTX_copy_ex(ctx, newctx);
|
||
|
```
|
||
|
|
||
|
Low Level squeeze changes
|
||
|
--------------------------
|
||
|
|
||
|
### Description
|
||
|
|
||
|
The existing one shot squeeze method is:
|
||
|
|
||
|
```text
|
||
|
SHA3_squeeze(uint64_t A[5][5], unsigned char *out, size_t outlen, size_t r)
|
||
|
```
|
||
|
|
||
|
It contains an opaque object for storing the state B<A>, that can be used to
|
||
|
output to B<out>. After every B<r> bits, the state B<A> is updated internally
|
||
|
by calling KeccakF1600().
|
||
|
|
||
|
Unless you are using a multiple of B<r> as the B<outlen>, the function has no
|
||
|
way of knowing where to start from if another call to SHA_squeeze() was
|
||
|
attempted. The method also avoids doing a final call to KeccakF1600() currently
|
||
|
since it was assumed that it was not required for a one shot operation.
|
||
|
|
||
|
### Solution 1
|
||
|
|
||
|
Modify the SHA3_squeeze code to accept a input/output parameter to track the
|
||
|
position within the state B<A>.
|
||
|
See <https://github.com/openssl/openssl/pull/13470>
|
||
|
|
||
|
#### Pros
|
||
|
|
||
|
- Change in C code is minimal. it just needs to pass this additional parameter.
|
||
|
- There are no additional memory copies of buffered results.
|
||
|
|
||
|
#### Cons
|
||
|
|
||
|
- The logic in the c reference has many if clauses.
|
||
|
- This C code also needs to be written in assembler, the logic would also be
|
||
|
different in different assembler routines due to the internal format of the
|
||
|
state A being different.
|
||
|
- The general SHA3 case would be slower unless code was duplicated.
|
||
|
|
||
|
### Solution 2
|
||
|
|
||
|
Leave SHA3_squeeze() as it is and buffer calls to the SHA3_squeeze() function
|
||
|
inside the final. See <https://github.com/openssl/openssl/pull/7921>
|
||
|
|
||
|
#### Pros
|
||
|
|
||
|
- Change is mainly in C code.
|
||
|
|
||
|
#### Cons
|
||
|
|
||
|
- Because of the one shot nature of the SHA3_squeeze() it still needs to call
|
||
|
the KeccakF1600() function directly.
|
||
|
- The Assembler function for KeccakF1600() needs to be exposed. This function
|
||
|
was not intended to be exposed since the internal format of the state B<A>
|
||
|
can be different on different platform architectures.
|
||
|
- When should this internal buffer state be cleared?
|
||
|
|
||
|
### Solution 3
|
||
|
|
||
|
Perform a one-shot squeeze on the original absorbed data and throw away the
|
||
|
first part of the output buffer,
|
||
|
|
||
|
#### Pros
|
||
|
|
||
|
- Very simple.
|
||
|
|
||
|
#### Cons
|
||
|
|
||
|
- Incredibly slow.
|
||
|
- More of a hack than a real solution.
|
||
|
|
||
|
### Solution 4 (Proposed Solution)
|
||
|
|
||
|
An alternative approach to solution 2 is to modify the SHA3_squeeze() slightly
|
||
|
so that it can pass in a boolean that handles the call to KeccakF1600()
|
||
|
correctly for multiple calls.
|
||
|
|
||
|
#### Pros
|
||
|
|
||
|
- C code is fairly simple to implement.
|
||
|
- The state data remains as an opaque blob.
|
||
|
- For larger values of outlen SHA3_squeeze() may use the out buffer directly.
|
||
|
|
||
|
#### Cons
|
||
|
|
||
|
- Requires small assembler change to pass the boolean and handle the call to
|
||
|
KeccakF1600().
|
||
|
- Uses memcpy to store partial results for a single blob of squeezed data of
|
||
|
size 'r' bytes.
|