Ossasepia

December 6, 2018

SMG Comms Chapter 11: Arbitrary Size of Public Exponent

Filed under: Coding, EuCrypt, SMG_Comms — Diana Coman @ 10:59 a.m.

~ This is a work in progress towards an Ada implementation of Eulora's communication protocol. Start with Chapter 1.~

The change from fixed-size to arbitrary size of the public exponent "e" of RSA keys1 turned out to be of course more than just introducing a parameter for e's size - all I can say about it is that I was rather ready for this development by now, given the known mess that is the MPI lib. So the only surprise here was the exact way in which MPI fails rather than the fact that it does fail. Let's take it slowly and in detail since it is - like everything else related to encryption - rather important to have it as clear as one can make it.

On the SMG Comms side, a shorter "e" is really no trouble at all: basically there is no such thing as "shorter" since it can perfectly be the same size as it always was, only starting with whatever number of 0s it needs to make up to the expected length, big deal. And this is in fact already handled and handled well in the wrappers I wrote previously for the RSA and MPI C code, since it was already clear that yes, any set of octets might start at any time with 0s but that doesn't make them fewer octets or anything of the sort. So at first look, there really isn't any need to change anything, since the "change" required is neatly and natively handled as what it is - just a specific case of the more general operation that is implemented. Still, for clarity and further use downstream, I decided to add a constant and a new subtype to Raw_Types simply for the purpose of providing an explicit way of using the exactly-8-octets-long e (neither the new constant nor the new subtype are put to use so far by any of the message pack/unpack or read/write methods):

    -- RSA public exponent (e) size in octets
    -- NB: this should normally match the E_LENGTH_OCTETS in smg_rsa.h
    -- NOT imported here for the same reason given at RSA_KEY_OCTETS above
  E_LENGTH_OCTETS : constant Positive := 8;

  subtype RSA_e is Octets( 1 .. E_LENGTH_OCTETS);

On the RSA side, the same constant "length of e in octets" goes into include/smg_rsa.h since it is not exactly a knob of the whole thing but rather a parameter for RSA key generation:

/**
 * This is the length of the public exponent e, given in octets.
 * TMSR standard e has KEY_LENGTH_OCTETS / 2 octets.
 * Eulora's communication protocol uses however e with 8 octets length.
 * New keypairs generated will have e precisely this length.
 * Change this to your preferred size of e for generating new keys with that size of e.
 * NB: this impacts key generation ONLY! (i.e. NOT encrypt/decrypt).
 */
static const int E_LENGTH_OCTETS = 8;

As the comments above stress, the "length of e" should normally be a concern in the code only when generating a new key pair; at all other times (encrypt/decrypt), the e that is provided will be used, whatever length it might be. Looking at the key generation code, the change to make is minimal since the code is sane - simply replace a local variable that specified the length of the required prime with the new global constant that now specifies the user's choice of length for e (in rsa/rsa.c, function gen_keypair):

	/* choose random prime e, public exponent, with 3 < e < phi */
	/* because e is prime, gcd(e, phi) is always 1 so no need to check it */
	do {
		gen_random_prime( E_LENGTH_OCTETS, sk->e);
	} while ( (mpi_cmp_ui(sk->e, 3) < 0) || (mpi_cmp(sk->e, phi) > 0));

Following the changes above, a re-read of all my rsa and smg_comms code confirmed that no, there is nothing else to change - it is after all just a matter of exposing a constant to the user, not any change of the underlying algorithm, so that's surely all, right? Well, no, of course it's not, because at the lower level, all those 0-led smaller "e" go into the MPI lib of gnarly entrails. And as it turns out, a quick test whipped out to see the whole thing in action got...stuck, going on for ever somewhere in the MPI code. Where? Well, the stack trace goes 8 levels deep into the MPI code and it looks (at times, as it rather depends on where one stops the neverending run...) like this:

#0  0x000000000040b492 in mpihelp_addmul_1 ()
#1  0x0000000000407cd4 in mpih_sqr_n_basecase ()
#2  0x0000000000407e68 in mpih_sqr_n ()
#3  0x0000000000408098 in mpih_sqr_n ()
#4  0x0000000000407d6b in mpih_sqr_n ()
#5  0x0000000000408098 in mpih_sqr_n ()
#6  0x0000000000407d6b in mpih_sqr_n ()
#7  0x0000000000405693 in mpi_powm ()

Claiming that one fully knows what goes on in 8 piled levels of MPI calls is rather disingenous at best so I won't even go there. However, a closer look at all that code, starting with mpi_powm and following the code seems to suggest that the issue at hand is that MPI simply can't handle correctly 0-led numbers. To which one should add of course "in some cases" so that one can't just say fine, wtf is it doing permitting any 0-led numbers then?? No, that would be too easy so the reality is that it permits and it probably even *requires* in places 0-led numbers but *in other places* it gets stuck2 on them. Aren't you happy to have followed this mess so far to such amazing conclusion? At any rate, going through the MPI code yields some more fun of course, such as this fragment in mpi-pow.c:

    /* Normalize MOD (i.e. make its most significant bit set) as required by
     * mpn_divrem.  This will make the intermediate values in the calculation
     * slightly larger, but the correct result is obtained after a final
     * reduction using the original MOD value.  */
    mp = mp_marker = mpi_alloc_limb_space(msize, msec);
    count_leading_zeros( mod_shift_cnt, mod->d[msize-1] );
    if( mod_shift_cnt )
  mpihelp_lshift( mp, mod->d, msize, mod_shift_cnt );
    else
  MPN_COPY( mp, mod->d, msize );

The obvious clue there is that at least one culprit of "can't handle 0-led numbers" is that divrem function that is indeed called as part of the exponentiation. But the added joke that's for insiders only is that the normalization there is done ad-hoc although there exists a function precisely for...normalizing aka trimming the leading 0s from an mpi! Eh, so what if it exists - by the time something gets as big and tangled as MPI, chances are nobody remembers everything there is but that's not a problem at all, right? But wait, just ~10 lines further down, there is another normalization and at *that* place, the author somehow remembered that there is even a macro defined for this purpose! And oh, another ~10 lines further, there is yet another way in which normalization is done on the spot (shifting the bits directly!). So what is it already that makes one write code like this, some misplaced purple-prose inclination, let's not repeat the same expression or what exactly? Frankly the only logical answer is that it's done on purpose - anything and everything to increase the number of lines of code. Increase productivity3 !!

Moving further, it turns out that this very same function actually *does* trim the leading 0s off the exponent at some point! Which of course begs now the question of just how and why is then a problem to give it a 0-led exponent? Essentially it trims it but too late/not fully/not for everything and not everywhere that it should do it, that's the best I can say about it. And overall, the fact of the matter is simply that MPI just doesn't correctly handle 0-led MPI values4, end of story. To quote from MPI code and comments themselves, the author's explanation:

/****************
 * Sometimes we have MSL (most significant limbs) which are 0;
 * this is for some reasons not good, so this function removes them.
 */

So it is "for some reasons not good", mmkay? It reminds me of the other display of great expertise in "reasons". Without wasting even more time on the MPI code of wonders5, the solution for SMG Comms is essentially a work around: the C wrappers get another job, namely to ensure that the values passed on to MPI are normalized. Note that the symmetrical opposite of this, namely adding missing leading 0s is already implemmented where needed (in the Ada code that actually deals perfectly fine with 0-led values since they are not oh-so-special, really). Thankfully, this is a very simple thing to do: instead of using directly the mpi_set_buffer method to set the value of an mpi number, define an mpi_set_normalized method that calls mpi_set_buffer + mpi_normalize:

void mpi_set_normalized(MPI m, const char *buffer,
                        unsigned int noctets, int sign) {
  mpi_set_buffer( m, buffer, noctets, sign );
  mpi_normalize( m );
}

Using the above code, all the mpi_set_buffer calls in c_wrappers are replaced by mpi_set_normalized calls and so there are no more 0-led mpi values passed on to the C code when calling rsa from Ada (since this is the purpose of those c_wrappers: to provide a sane interface for Ada to work with the insanity of C for RSA needs). Obviously, if you insist on calling the C rsa encrypt/decrypt methods directly, it's up to you to make sure you don't pass them 0-led values. While I could change the encrypt/decrypt methods themselves to normalize all the keys' components before doing anything, I think that's a very ugly and ultimately incorrect thing to do: the encrypt/decrypt should use precisely what they are given, not go about tampering with the inputs, regardless of "reasons". Yes, it is ugly and incorrect that MPI forces this normalization nonsense but that's not a justification for messing the encrypt/decrypt functions to cover up for it.

Note also that I specifically chose NOT to include the normalization in the existing method mpi_set_buffer because on one hand it's not the job of mpi_set_buffer to trim its inputs and on the other hand there is a need for mpi_set_buffer precisely as it is: there is code in there relying on being able to set the buffer of an mpi to anything, including 0-led vectors (even if at times, that doesn't remain 0-led for long). So no, modifying mpi_set_buffer is not a good option, even without considering the fact that MPI is better thrown away6 than changed.

The rest of the .vpatch for this chapter of SMG Comms contains simply the 2 additional tests (and changes needed for them in the test environment) that I wrote: one for the RSA c code, to flag the issue and one for the Ada code to ensure that there is at least one test with an exponent of 8 octets. I've used first the rsa code with the length of e set to 8 to generate a pair of RSA keys that are used then for the new test. So there is now a new file, "8_keys.txt" containing this new set of keys and the Ada test is simply another call with different parameters to read its input from this file as opposed to another.

Given that the arbitrary size of e touches essentially EuCrypt code, I also packed those minimal changes to smg_rsa.h and to key generation, together with the new test using a shorter e, into a .vpatch for EuCrypt. I've also added in there stern warnings at the encrypt/decrypt regarding the 0-led issue since it is the responsibility of the caller to either make sure they don't provide 0-led values or otherwise deal with a potentially-blocking call. Both .vpatch files and their corresponding signatures are on my Reference Code Shelf as well as linked here for your convenience:


  1. In practice this is more like 8-octets size for Eulora and otherwise 256 octets for TMSR rather than all sorts of sizes. Nevertheless, the big change is from one single size to ~anything more than one size since that means that the size can't be relied on to be anything specific at any given time. 

  2. Or as good as stuck: I did not have the patience to wait on it for more than 3 minutes, given that the operation in question should have barely taken a couple of seconds at maximum. 

  3. And probably surpass the 5-years plan too, since it's hardly distinguishable already. 

  4. Or, apparently, only values with "too many" 0 at the front aka at least one limb. 

  5. Yes, I did actually dig even deeper into the MPI code following this because I don't really have a choice not to as long as MPI remains part of SMG Comms and EuCrypt. The conclusion however is still the same, so I'm jumping here to the conclusion and you are free to tell me in detail in the comments section the results of your investigation on this. 

  6. Hopefully there will soon be a replacement for it and so it WILL be thrown away, I can't wait for that! 

October 10, 2018

EuCrypt Chapter 14: CRC32 Implementation with Lookup Table

Filed under: Coding, EuCrypt — Diana Coman @ 1:51 p.m.

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

The communication protocol for Eulora uses CRC32 as checksum for its packages and so I found myself looking in disbelief at the fact that GNAT imposes the use of Streams for something as simple as calculating the checksum of anything at all, no matter how small no matter what use, no matter what you might need or want or indeed find useful, no matter what. No matter! As usual, the forum quickly pointed me in the right direction - thank you ave1! - namely looking under the hood of course, in this case GNAT's own hood, the Systems.CRC32 package. Still, this package makes a whole dance of eating one single character at a time since it is written precisely to support the stream monstrosity on top rather than to support the user with what they might indeed need. Happily though, CRC32 is a very simple thing and absolutely easy to lift and package into 52 lines of code in the .adb file + 130 in the .ads file so 182 all in total1, comments and two types of input (string or raw array of octets) included. And since a CRC32 implementation is anyway likely to be useful outside of Eulora's communication protocol too, I'm adding it as a .vpatch on the EuCrypt tree where it seems to fit best at the moment. It's a lib on its own as "CRC32" when compiled via its own crc32.gpr or otherwise part of the aggregate EuCrypt project if you'd rather compile all of EuCrypt via its eucrypt.gpr.

My CRC32 lib uses the 0x04C11DB7 generator polynomial and a lookup table with pre-calculated values for faster execution. As Stanislav points out, implementing the actual division and living with the slow-down incurred is not a huge problem but at least for now I did not bother with it. The CRC32 lib provides as output 32 bits of checksum for either a string or an array of octets. At the moment at least I really don't see the need for anything more complicated than this - even the string-input method is more for the convenience of other/later uses than anything else. For my own current work on Eulora's protocol I expect to use CRC32 on arrays of octets directly.

The .vpatch adding CRC32 to EuCrypt and my signature for it are as usual on my Reference Code Shelf with additional links here for your convenience:


  1. Specifically: 52 lines is the count of crc32.adb that does the work. The .ads file brings in another 130 lines that are mostly the lookup table with pre-calculated values. The .gpr file has another 61 lines and the restrict.adc another 80 lines. 

June 15, 2018

EuCrypt Manifest File

Filed under: EuCrypt — Diana Coman @ 2:09 p.m.

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

This little patch simply adds a manifest file to EuCrypt so that the intended order of patches is made explicit, easily accessible and easily maintained throughout the life of the project even as new patches are added by others (hopefully). I've created this file following the manifest format specification proposed by Michael Trinque. For each patch, I used the block count1 as reported by mimisbrunnr on the day when I published the patch.

Since I publish this as a .vpatch on top of existing EuCrypt itself, there is of course a line in the manifest file for this vpatch too. To keep it nicely flowing from the previous last leaf of EuCrypt, there is an additonal change to the README file of the project as well (since it's this file I have used so far, before the manifest solution was adopted, as an implicit way of forcing some meaningful order on the vpatches).

As usual, the .vpatch and its signatures can be found on my Reference Code Shelf and are also linked directly from here for your convenience:


  1. Starting with last round number on that day so as to have some space for the case when there were several patches on same day. 

May 3, 2018

EuCrypt Chapter 13: SMG RNG

Filed under: EuCrypt — Diana Coman @ 3:01 p.m.

Motto: It's just the int interpretation of a float's binary representation.

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

This unexpected chapter equips EuCrypt with more convenient ways of using the excellent Fuckgoats (FG) for generating random numbers in various formats. The backstory is a heartwarming (if short) tale of idiocy stabbing itself in its own useless foot, so let's indulge for a moment.

As you might be aware, cryptography algorithms such as RSA and games such as Eulora rely to a significant extent on randomly generated numbers. As you might not fully appreciate though, the usual "random number generator" available on your computer is at best pseudo-random but the pseudo - here starts the idiocy - is not mentioned much, preferably not at all. So one gets /dev/random, /dev/urandom but never, ever, ever /dev/pseudorandom. One might say "what's in a name"1 and attempt to sort-of still work with it - let's just feed /dev/random with truly random bits from the FG and keep it around the house, as it might still be useful, why not? But of course the whole thing is made so that there isn't in fact a clear and straightforward way to even feed it better inputs - in other words, through its own working, /dev/random managed to push itself into being discarded even sooner rather than later. Isn't that... nice?

Once the silly idea of still using /dev/random in some way was thus abandoned as it should be, the only remaining thing was to provide some reliable ways of directly converting the raw random bits that FGs produce into some actual numbers of the sort that a user (a human, a game or anything else) needs in practice. Obviously, there can never be a full list of what sort of formats and ranges of numbers one might want, so this is by no means a comprehensive set or the only set of formats, ranges and even ways of converting FG bits into random numbers. It's simply a starter set of methods that are useful to S.MG at this time and may be perhaps useful to you at some time. As always, before using them, first read and understand their working and their limitations so that you can actually decide whether they meet your needs or not. And if they don't, either change them into what you need them to be or - even better yet - add to them your own methods that do exactly what you need them to do.

A random number generator based on FG would logically be a library on its own so that it can be easily used by any code - even without importing the whole EuCrypt if it doesn't need the rest. However, at the moment, the new code is simply an addition to truerandom.c that is part of smg_rsa, as it effectively builds on the other methods in there (those for accessing FGs specifically). I refrained from extracting the RNG into a separate module at this time because of a lack of tools: current vdiff does not know about simply moving code without changing it. As soon as the new vtools are ready and fully functional, it should be an easy task to simply move the relevant code to a different module with a short and easy to read vpatch (as opposed to the large delete & add vpatch that would result now from such a simple move). Until then at least, smg_rng will effectively be part of smg_rsa.

There are 4 new methods for obtaining random numbers using bits from a source of entropy2, providing the following types and ranges of numbers:

  • an unsigned integer between 0 and 2^32 - 1 (i.e. on precisely 32 bits): this method reads 32 bits from the specified entropy source and then copies them directly to the memory address of an uint32_t (unsigned integer on 32 bits) variable that is the result. Although working at bit-level, the method doesn't really care about endianness, since the bits are random anyway: sure, same bits will be interpreted as a different value by your Big Endian iron compared to your Little Endian iron but that doesn't make the series obtained with successive calls to this method from *the same machine* any less random.
  • an unsigned integer between 0 and 2^64 - 1 (i.e. on precisely 64 bits): this method is just like the above, but for larger numbers (64 bits instead of 32); specifically, this method reads 64 bits from the specified entropy source and then copies them directly to the memory address of an uint64_t (unsigned integer on 64 bits) variable that is the result. Although working at bit-level, the method doesn't really care about endianness, since the bits are random anyway: sure, same bits will be interpreted as a different value by your Big Endian iron compared to your Little Endian iron but that doesn't make the series obtained with successive calls to this method from *the same machine* any less random.
  • a "dirty" float between 0 and 1: this method obtains first a 32-bit random integer value with the relevant method above and then divides this (as a float) by the maximum possible value on 32 bits (2^32 - 1). The resulting float is deemed "dirty" from a randomness perspective because of the way in which floating points are stored: basically there will be some unpredictable rounding of the least significant bits so use this *only* if this degradation is insignificant to you.
  • a float between 1 and 2, assuming the IEEE 754/1985 internal representation: unlike the "dirty" float method, this one directly writes the random bits obtained from FG to the mantissa part of a float number in memory (it also sets the sign bit to 0 and the exponent to 127 effectively ensuring that the result is read as a positive float with value 1.mantissa). For clarity reasons, the code of this method actually writes first 32 random bits at the address of the desired float and then simply goes in and sets the sign to 0 and the exponent to 127, leaving the rest (i.e. the mantissa) with the random bits. Because of this direct bit-diddling, this method has to take into account the endianness: on Big Endian, the sign and exponent are the first two octets at the address of the float, while on Little Endian the exponent and sign are the last 2 octets. To handle this, the method first calls a little bit of new code that tests the endianness and then it sets accordingly the relevant offsets for the 2 octets that need diddling.

The signatures of the above methods as well as the new snippet of code for testing endianness at run-time are added to the relevant header file, eucrypt/smg_rsa/include/smg_rsa.h, together with the usual comments to help the reader understand the code.

The run-time test of endianness:

/* A way to determine endianness at runtime.
 * Required for diddling a float's mantissa for instance.
 */
static const int onect = 1;
#define is_bigendian() ( (*(char*)&onect) == 0 )

The signatures of rng methods:

/* Returns (in parameter *n) a *potentially biased* random float between 0 and 1
 * Uses bits from ENTROPY_SOURCE but it rounds when converting to float
 * NB: This function rounds impredictably.
       Use it ONLY if LSB normalization is insignificant to you!
 * This function uses rng_uint64 below.
 *
 * @param n - a float value (LSB rounded) between 0 and 1, obtained using
 *            a 64-bit random integer (64 bits from ENTROPY_SOURCE)
 * @return  - a positive value on success and a negative value in case of error
 *            main possible cause for error: failure to open ENTROPY_SOURCE.
 *            NB: a non-responsive/malconfigured source can result in blocking
 */
int rng_dirty_float(float *n);

/* Returns (in parameter *n)  a randomly generated float between 1 and 2 using:
 *    - the IEEE 754/1985 format for single float representation
 *    - ENTROPY_SOURCE to obtain bits that are *directly* used as mantissa
 * NB: this value is between 1 and 2 due to the normalised format that includes
 *     an implicit 1 ( i.e. value is (-1)^sign * 2^(e-127) * (1.mantissa) )
 *
 * From IEEE 754/1985, a description of the single float format:
 *   msb means most significant bit
 *   lsb means least significant bit
 *  1    8               23            ... widths
 * +-+-------+-----------------------+
 * |s|   e   |            f          |
 * +-+-------+-----------------------+
 *    msb lsb msb                 lsb  ... order

 * A 32-bit single format number X is divided as shown in the figure above. The
 * value v of X is inferred from its constituent fields thus:
 * 1. If e = 255 and f != 0 , then v is NaN regardless of s
 * 2. If e = 255 and f = 0 , then v = (-1)^s INFINITY
 * 3. If 0 < e < 255 , then v = (-1)^s * 2^(e-127) * ( 1.f )
 * 4. If e = 0 and f != 0 , then v = (-1)^s * 2^(-126) * ( 0.f ) (denormalized
 *    numbers)
 * 5. If e = 0 and f = 0 , then v = ( -1 )^s * 0 (zero)
 *
 * @param n - the address of an IEEE 754/1985 float: its mantissa will be set to
 *              random bits obtained from ENTROPY_SOURCE; its sign will be set
 *              to 0; its exponent will be set to 127 (the bias value so
 *              that the actual exponent is 0).
 * @return  - a positive value on success and a negative value in case of error
 *              main possible cause for error: failure to open ENTROPY_SOURCE.
 *              NB: a non-responsive/malconfigured source can result in blocking
 */
int rng_float_754_1985(float *n);

/* Returns (in parameter *n) a random unsigned integer value on 32 bits.
 * Uses random bits from ENTROPY_SOURCE that are directly interpreted as int
 *
 * @param n - it will contain the random integer obtained by interpreting 32
 *            bits from ENTROPY_SOURCE as an unsigned int value on 32 bits.
 * @return  - a positive value on success and a negative value in case of error
 */
int rng_uint32( uint32_t *n );

/* Returns (in parameter *n) a random unsigned integer value on 64 bits.
 * Uses random bits from ENTROPY_SOURCE that are directly interpreted as int
 *
 * @param n - it will contain the random integer obtained by interpreting 64
 *            bits from ENTROPY_SOURCE as an unsigned int value on 64 bits.
 * @return  - a positive value on success and a negative value in case of error
 */
int rng_uint64( uint64_t *n );

The actual implementation of the above methods is in eucrypt/smg_rsa/truerandom.c:

int rng_dirty_float(float *n) {
  int status;   /* for aborting in case of error */
  uint32_t r;   /* a random value on 32 bits */
  uint32_t maxval = 0xffffffff; /* maximum value on 32 bits */

  /* obtain a random number on 32 bits using ENTROPY_SOURCE */
  status = rng_uint32( &r );
  if ( status < 0 )
    return status;

  /* calculate and assign the floating-point random value as (r*1.0)/max val */
  /* multiplication by 1.0 IS NEEDED to do float division rather than int div*/
  *n = ( r * 1.0 ) / maxval;

  return 1;
}

int rng_float_754_1985(float *n) {
  /* Single float ieee 754/1985 has 23 bits that can be set for the mantissa
   * (and one implicit bit=1).
   * Full single float ieee 754/1985 representation takes 4 octets in total.
   */
  int noctets = 4; /* number of octets to read from ENTROPY_SOURCE */
  int nread;       /* number of octets *read* from ENTROPY_SOURCE  */
  unsigned char bits[ noctets ]; /* the random bits from ENTROPY_SOURCE */
  int oSignExp, oExpM;/* offsets for sign+exponent octet, exponent+mantissa*/

  /* obtain random bits */
  nread = get_random_octets( noctets, bits );

  if (nread != noctets )
    return -1;  /* something wrong at reading from ENTROPY_SOURCE, abort */

  /* set offsets for bit diddling depending on endianness of iron */
  if (is_bigendian()) {
    oSignExp = 0;
    oExpM = 1;
  }
  else {
    oSignExp = 3;
    oExpM = 2;
  }

  /* set sign=0; exponent=127; explicit mantissa = random bits (23 bits) */
  *(bits+oExpM) = *(bits+2) | 0x80;  /* one bit of exponent set */
  *(bits+oSignExp) = 0x3f;           /* sign=0; exponent bits for 127 */

  /* now copy the bits to the result var (i.e. as a float's representation */
  memcpy( n, bits, noctets );
  return 1;
}

int rng_uint32( uint32_t *n ) {
  int noctets = 4;  /*  32 bits aka 4 octets to read from ENTROPY_SOURCE     */
  int nread;        /*  the number of octets read from ENTROPY_SOURCE        */
  unsigned char bits[ noctets ]; /* for storing the bits from ENTROPY_SOURCE */

  /* read random 32 bits from ENTROPY_SOURCE */
  nread = get_random_octets( noctets, bits );
  if ( nread != noctets )
    return -1;

  /* copy the random bits to n, to be interpreted as uint32 */
  /* endianness is irrelevant here - the bits are random anyway */
  memcpy( n, bits, noctets );

  return 1;
}

int rng_uint64( uint64_t *n ) {
  int noctets = 8;  /*  64 bits aka 8 octets to read from ENTROPY_SOURCE     */
  int nread;        /*  the number of octets read from ENTROPY_SOURCE        */
  unsigned char bits[ noctets ]; /* for storing the bits from ENTROPY_SOURCE */

  /* read random 64 bits from ENTROPY_SOURCE */
  nread = get_random_octets( noctets, bits );
  if ( nread != noctets )
    return -1;

  /* copy the random bits to n, to be interpreted as uint64 */
  /* endianness is irrelevant here - the bits are random anyway */
  memcpy( n, bits, noctets );

  return 1;
}

As usual, for every new piece of code, there are also new tests. In this case, the tests are very basic, simply calling each method and reporting both the resulting number and the status code (i.e. whether the method reported any errors). Note that assessing the outputs of a random number generator is way beyond the scope of those basic tests - you should devise and use your own tools for evaluating (to a degree that is satisfying for yourself) the resulting sequence of numbers obtained through repeated calls to any or all of these methods. The new tests in eucrypt/smg_rsa/tests/tests.c:

void test_dirty_float_rng( int nruns ) {
  int i, status;
  float dirty;

  printf("Running test for smg rng dirty float with %d runsn", nruns);
  for (i=0; i0 ? "OK" : "FAIL");
  }
}

void test_ieee_float_rng( int nruns ) {
  int i, status;
  float ieee;

  printf("Running test for smg rng ieee 745/1985 float with %d runsn", nruns);
  for (i=0; i0 ? "OK" : "FAIL");
  }
}

void test_uint32_rng( int nruns ) {
  int i, status;
  uint32_t n;

  printf("Running test for smg rng unsigned int32 with %d runsn", nruns);
  for (i=0; i0 ? "OK" : "FAIL");
  }
}

void test_uint64_rng( int nruns ) {
  int i, status;
  uint64_t n;

  printf("Running test for smg rng unsigned int64 with %d runsn", nruns);
  for (i=0; i0 ? "OK" : "FAIL");
  }
}

The .vpatch for the above smg rng implementation can be found on my Reference Code Shelf as well as below:


  1. A full backstory, at the very least! But let's not digress even more from the code... 

  2. The source of entropy is assumed to be a FG but - unlike /dev/urandom - you can change this quite easily. 

March 15, 2018

EuCrypt: Additional Check and a Note on Cosmetic Changes

Filed under: EuCrypt — Diana Coman @ 6:46 p.m.

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

A very useful comment by Arjen (ave1) at my previous post on EuCrypt prompted me to have another look at all calls for random bits from the entropy source, throughout EuCrypt. As Arjen mentioned, it turns out that there is one lonely call for which the potential error flag (the number of octets read) is not directly checked by the caller. Since this is a sensitive issue (the call is in primegen.c precisely when attempting to find randomly a large prime number), I am adding this missing check there. As a result, EuCrypt's behaviour is now entirely uniform in this respect: any time EuCrypt requests a number of random bits from the entropy source, it does so in a loop, discarding any output that is less than/different from what it requested and insisting with the requests until satisfied.

The .vpatch contains the small change to the code, basically a loop around the request for random bits from the entropy source. To ensure that the whole V tree of EuCrypt remains neatly with a single leaf, namely this patch, I added also a short comment to the README file of EuCrypt stating this design principle of trying for as long as needed: "NB: EuCrypt aims to *keep trying* to accomplish a task. In particular, when entropy is needed, it will keep asking for as many random bits as it needs from the configured entropy source and it will not proceed unless those are provided." The change to the code is clear enough from the .vpatch itself:

+  int nread;
 	do {
-		get_random_octets_from( noctets, p, entropy_source );
+    do {
+      nread = get_random_octets_from( noctets, p, entropy_source );
+    } while ( nread != noctets );

Note that one can legitimately raise the concern (as Arjen did) that an inaccessible/in error state entropy source can therefore result in an infinite loop. This is true and it is intended behaviour: the entropy source is part of the environment in which EuCrypt runs and looking after it is outside of the scope of EuCrypt itself. Moreover, a working (and reliable) entropy source is simply a crucial pre-requisite for the tasks of EuCrypt and therefore I do not want it to proceed with anything at all when this pre-requisite is not met. An alternative solution would be to simply abort whatever EuCrypt was trying to do, as soon as the entropy source fails to provide the requested bits. However, this effectively means that EuCrypt will need to be restarted even if the environment is set up so that access to the entropy source is re-established. Basically this would add the restarting of EuCrypt and potential recovery of previous work as an additional task for the environment manager (whatever that might be) and I don't see at the moment the case for this. I'd much rather have EuCrypt keep trying, simply and stubbornly until the conditions are met and it can proceed with what it had to do. Should the caller prefer otherwise, they can of course have their own mechanisms in place and make their own decisions as to recovery options if the call to EuCrypt takes longer than they are willing to wait for. What EuCrypt promises is simple: it will NOT proceed with fewer random bits than it needs and it will KEEP trying to get them for as long as it takes or until it gets killed by the caller/outside forces.

It might be worth mentioning at this point that the design decision to re-open the entropy source for requests that are not made by the same caller / block of code provides also a bit of support for this approach of keep-trying-until-it-works-no-matter-what: as long as the string identifier for the entropy source is the same, the physical source can change seamlessly between uses by EuCrypt.

On a different, lighter note, having looked at a potential cosmetic patch to the whole library to replace all tabs with spaces and to ensure *everywhere* strict adherence to the coding style1 introduced midway through development, I can say that such a patch will weigh in at more than 2k lines for non-mpi components only, going above 9k lines when mpi is included as well. As such, I'm really in two minds whether it would be more useful than annoying. Perhaps have your say on this matter if you feel strongly about the tabs-to-spaces and 80 columns rule. Before you do, note however that all code written after those rules were adopted is already following them, of course. This being said, a review of the code is good at any time and the need for cosmetic changes is as good a time as any if not even better than many.

The eucrypt_check_nread.vpatch and my signature for it are as usual on my Reference Code Shelf and linked here as well, for your convenience:


  1. maximum 80 columns per line and various alignments 

March 8, 2018

EuCrypt: Compilation Sheet

Filed under: EuCrypt — Diana Coman @ 2:02 p.m.

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

First of all, you'll need V, the republican versioning system, there is no way around this and I won't provide any way around it either. Grab yourself a copy of V and learn to use it or implement your own copy, as you prefer. If you need some help with that, go first through the gentle introduction to V by Ben Vulpes and if you are still stuck after that come and ask intelligent questions in #trilema on irc.

Once you have V in working order, head to my Reference Code Shelf and download the .vpatches for EuCrypt and their signatures. Alternatively, you should be able to get the same from btcbase's mirror of EuCrypt, possibly1 with some other signatures as well if that helps. Press to the .vpatch you want, but make sure you do include the fixes at least for the components you are interested in. Just saying. Then you're ready for building the lib itself.

EuCrypt can be built in 2 main ways:

  • as a single, aggregate library, including therefore all the components: mpi, smg_keccak, smg_bit_keccak, smg_rsa, smg_serpent; you'll get one static library with everything, ready to be used; current size of the resulting file is 215K when built with AdaCore's GNAT 2016 and gcc 4.9.4.
  • component by component, picking and choosing only what you need; current sizes when built with AdaCore's GNAT 2016 and gcc 4.9.4: mpi 109K; smg_bit_keccak 17K; smg_keccak 42K; smg_rsa 19K; smg_serpent 20K - 31K (depending on level of optimisation chosen). NOTE: smg_rsa uses smg_keccak and mpi!

EuCrypt is written in Ada (Serpent and Keccak components) and C (mpi and rsa components). Therefore you'll need a tool chain that supports multi-language libraries. My personal recommendation is to use AdaCore's GNAT - it is currently the only tool I know to actually work out of the box for everything that EuCrypt needs2 and it includes directly the rather powerful GPRBuild tool3 for automatic builds of multi-language projects of all sorts. Given this lack of alternatives that I could recommend, I'll mirror here the precise version of AdaCore's GNAT that I am currently using and that I recommend you use too for building EuCrypt:

To compile, simply go to the eucrypt folder and run gprbuild. This will build EuCrypt as an aggregate library. To build any separate component, go to its own folder and run gprbuild there.

EuCrypt has been built successfully so far on the following systems:

  • CentOS 6.8, AdaCore's GNAT GPL 2016 (gcc 4.9.4)
  • Ubuntu 14.04, AdaCore's GNAT GPL 2016 (gcc 4.9.4)

I'll gladly add to the above list any other systems/configurations that I become aware of - just tell me in the comments below what you compiled it on or even better - write it up on your blog and drop me a link in the comments below (a trackback is good too - just make sure it works!).

Note that the building of EuCrypt as a multi-language, C and Ada project should be quite pain-free with gprbuild. However, there is quite a lot of pain at writing code time when you need to interface between the two languages and especially when you need to pass strings and/or pointers. You can see such interfacing in action in Chapter 12 of EuCrypt (the wrapper for using Keccak OAEP + RSA directly from C code) but I fully recommend in any case that you read as well the very clear account of sending array of octets between C and Ada, by ave1. At the moment EuCrypt uses a more basic method to accomplish the same task, namely copying octet by octet an array of characters from Ada to C or from C to Ada, as required. Feel free to change that (as anything else), of course and let me know how it goes - there is no better way of understanding some code than trying to make a meaningful change to it!


  1. hopefully, at some point not that distant into the future 

  2. You CAN supposedly use any other version of GNAT, most notably whatever comes with your gcc and/or specific OS distribution but it seems to lag behind for one thing and to be rather prone to trouble due for instance (among other troubles) to various version mismatch between all the different moving parts; so if you DO use a different GNAT and get it to work correctly, please document your work, write it up on your blog and drop me a link - I'll be happy to read it and add it as known working alternative! 

  3. This is NEEDED if you want to build eucrypt as a standalone aggregate library! While you CAN build the components separately with gnatmake for instance, you won't be able to build aggregate library with it as gnatmake simply doesn't support this at the moment as far as I'm aware. 

March 3, 2018

EuCrypt: Correcting Bounds Error in Keccak (Word-Level)

Filed under: EuCrypt — Diana Coman @ 3:55 p.m.

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

Thanks to prompt use of Keccak as hashing method in the new vtools by phf , an error has been discovered and neatly reported1 about 8 hours ago. Thanks to the very helpful way in which the error was reported and the very helpful way in which Ada reports the very line on which some check failed, it took all of 10 minutes to identify the problem: I had flipped 2 variables, attempting to copy from "ToPos" to "FromPos" and somehow I failed to see this glaring error even at re-readings... Other than the obvious fact that I should take a break, get more sleep and all that, there really is nothing else I can say on how this happened. So I'll get straight on to the fix for the error itself, meaning changing one line in eucrypt/smg_keccak/smg_keccak.adb:

-      BWord(Bitword'First .. Bitword'First + SBB - 1) := Block(ToPos..FromPos);
+      BWord(Bitword'First .. Bitword'First + SBB - 1) := Block(FromPos..ToPos);

Note that the bit-level Keccak will not have this sort of problem as it doesn't have to dance around the "is it multiple of 8 or not?" issue that required the above code in the first place in the word-level version. So there is nothing to change in the bit-level Keccak.

As usual, an error found means at least one test added to first show the error and then showcase the result of the fix. In this case the test has been basically already provided by the helpful phf. I have simply adapted it to make it use the full valid range of the bitrate for Keccak (based on the Keccak_Rate type defined in smg_keccak.ads) and to actually fail if there is a problem anywhere, since the failure itself will be pointing precisely at where the trouble is. The new test is added to eucrypt/smg_keccak/tests/smg_keccak-test.adb:

+  procedure test_all_bitrates is
+    Input : constant String := "hello, world";
+    Bin   : Bitstream( 0 .. Input'Length * 8 - 1 ) := ( others => 0 );
+    Bout  : Bitstream( 0 .. 100 ) := ( others => 0 );
+  begin
+    ToBitstream( Input, Bin );
+    Put_Line("Testing all bitrates:");
+      for Bitrate in Keccak_Rate'Range loop
+        Sponge(Bin, Bout, Bitrate);
+        Put_Line("PASSED: keccak with bitrate " & Integer'Image(Bitrate));
+      end loop;
+  end test_all_bitrates;
+

Running all the tests shows now a very satisfying PASSED for ALL valid bitrates of Keccak. Note that bitrate has to be between 1 and width of the Keccak state, which is currently 1600. Anything outside this range will cause CORRECTLY an abortion of execution - this will happen however as soon as the Sponge procedure is called since the value will simply not match the restricted Keccak_Rate type.

The .vpatch for the above and its signature will be on my Reference Code Shelf as well as linked here directly for your convenience:


  1. Reported with code to reproduce the problem and a paste of the output and everything one needs to find the trouble in about 10 minutes flat. 

March 1, 2018

EuCrypt Chapter 12: Wrapper C-Ada for RSA + OAEP

Filed under: EuCrypt — Diana Coman @ 9:45 p.m.

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

Find yourself a comfortable position, as this might take a while to go through. At 1034 lines, the .vpatch for this chapter deals with the gnarly mess that is the passing of char* to Ada and back, while also gathering everything from previous chapters together into the actual EuCrypt library as a standalone, static lib. As usual and as always in my books, adding something does not mean that I'm taking away previous, useful options, quite on the contrary. Specifically, this chapter provides a way to compile everything in a single lib *in addition* to the previous options of compiling each and any of the EuCrypt components as standalone libraries if preferred.

The main thorny issue with compiling EuCrypt as a whole is that one needs to put together code in both Ada and C. I have to say that this past week increased my disgust for C at an incredible speed - I suspect this outcome is simply unavoidable when getting back to C after a spell of Ada and moreover when this getting back to C involves a significant part of in-your-face comparison of the 2 languages as one has to write a "wrapper for C" with 2 parameters for each 1 single parameter that Ada requires, while also being anyway at the mercy of the caller for all that verbosity to actually make any sense as intended. Still, as long as there isn't any replacement for the despicable mpi that effectively forces C into EuCrypt, such is the life to live - stinky, verbose and treacherous, so watch your step and read 10 times before running code even once, not to mention even thinking of writing any of it.

The brighter side of all this come of course from the Ada-part: GNAT's Project Manager (GPR) is an excellent tool for building and managing precisely this sort of mixed-languages projects. Moreover, GPR1 is the only tool that I found to actually work2 for aggregate libraries such as EuCrypt. And since this is the only thing that I know to actually work as intended for the task at hand, the first part of this .vpatch nukes all previous Makefiles and provides instead sweet and short .gpr files for each and every component as well as for eucrypt as a whole:

  • EuCrypt as a whole (top level, including ALL the different components), eucrypt/eucrypt.gpr:

     -- S.MG, 2018
    
    aggregate library project EuCrypt is
      for Project_Files use (
                              "mpi/mpi.gpr",
                              "smg_bit_keccak/smg_bit_keccak.gpr",
                              "smg_keccak/smg_keccak.gpr",
                              "smg_rsa/smg_rsa.gpr",
                              "smg_serpent/smg_serpent.gpr");
    
      for Library_Name use "EuCrypt";
      for Library_Kind use "static";
    
      for Library_Dir use "lib";
    end EuCrypt;
    
  • MPI component, eucrypt/mpi/mpi.gpr:

    -- S.MG, 2018
    
    project MPI is
      for Languages use ("C");
      for Library_Name use "MPI";
      for Library_Kind use "static";
    
      for Source_Dirs use (".", "include");
      for Object_Dir use "obj";
      for Library_Dir use "bin";
    
    end MPI;
    
  • MPI tests, eucrypt/mpi/tests/test_mpi.gpr

    -- S.MG, 2018
    
    with "../mpi.gpr";
    
    project test_MPI is
      for Languages use ("C");
    
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("test_mpi.c");
    end test_MPI;
    
  • Bit-level keccak component, eucrypt/smg_bit_keccak/smg_bit_keccak.gpr:

     -- S.MG, 2018
    project SMG_Bit_Keccak is
      for Languages use ("Ada");
      for Library_Name use "SMG_Bit_Keccak";
      for Library_Kind use "static";
    
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Library_Dir use "lib";
    
    end SMG_Bit_Keccak;
    
  • Bit-level Keccak tests, eucrypt/smg_bit_keccak/tests/smg_bit_keccak_test.gpr:

     -- Tests for SMG_Bit_Keccak (part of EuCrypt)
     -- S.MG, 2018
    
    with "../smg_bit_keccak.gpr";
    
    project SMG_Bit_Keccak_Test is
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("smg_bit_keccak-test.adb");
    end SMG_Bit_Keccak_Test;
    
  • Keccak (word-level) component, eucrypt/smg_keccak/smg_keccak.gpr:

     -- S.MG, 2018
    project SMG_Keccak is
      for Languages use ("Ada");
      for Library_Name use "SMG_Keccak";
      for Library_Kind use "static";
    
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Library_Dir use "lib";
    
    end SMG_Keccak;
    
  • Keccak (word-level) tests, eucrypt/smg_keccak/tests/smg_keccak-test.gpr:

     -- Tests for SMG_Keccak (part of EuCrypt)
     -- S.MG, 2018
    
    with "../smg_keccak.gpr";
    
    project SMG_Keccak_Test is
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("smg_keccak-test.adb");
    end SMG_Keccak_Test;
    
  • RSA (including true random number generator and OAEP padding using Keccak as hash function) component, eucrypt/smg_rsa/smg_rsa.gpr:

     -- S.MG, 2018
    
    with "../mpi/mpi.gpr";
    with "../smg_keccak/smg_keccak.gpr";
    
    project SMG_RSA is
      for Languages use ("C");
      for Library_Name use "SMG_RSA";
      for Library_Kind use "static";
    
      for Source_Dirs use (".", "include");
      for Object_Dir use "obj";
      for Library_Dir use "bin";
    
    end SMG_RSA;
    
  • RSA tests, eucrypt/smg_rsa/tests/smg_rsa_tests.gpr:

     -- Tests for SMG_RSA (part of EuCrypt)
     -- S.MG, 2018
    
    with "../smg_rsa.gpr";
    
    project SMG_RSA_Tests is
      for Languages use("C");
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("tests.c");
    end SMG_RSA_Tests;
    
  • Serpent component, eucrypt/smg_serpent/smg_serpent.gpr:

    -- S.MG, 2018
    
    project SMG_Serpent is
      for Languages use ("Ada");
      for Library_Name use "SMG_Serpent";
      for Library_Kind use "static";
    
      for Source_Dirs use ("src");
      for Object_Dir use "obj";
      for Library_Dir use "lib";
    
    end SMG_Serpent;
    
  • Serpent tests, eucrypt/smg_serpent/tests/smg_serpent_tests.gpr:

     -- Tests for SMG_Serpent (part of EuCrypt)
     -- S.MG, 2018
    
    with "../smg_serpent.gpr";
    
    project SMG_Serpent_Tests is
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("testall.adb");
    end SMG_Serpent_Tests;
    

To compile anything, simply go to where the .gpr file of interest is and then run "gprbuild." As there is only one .gpr file per folder, you don't even need to specify *what* to build, as it's clever enough to figure out you mean that .gpr right there. Any components that are required by the one you are trying to build will be built automatically as part of the process. To clean up the result of any compilation, all you need to do is "gprclean -r". The r flag means recursive as otherwise gpr will not follow through to clean any dependencies it built.

With the "easy" part of building EuCrypt and its components out of the way, let's see the gnarly part of making a working wrapper for rsa and oaep, effectively mixing Ada code (the oaep part in smg_keccak) and C code (the rsa part in smg_rsa). Since I'm misfortunate enough to have Eulora's server code + underlying graphics engine in C, it follows that the rsa and oaep will need to be used from C anyway, so the approach here is to simply add to the smg_rsa component 2 new methods that do the OAEP padding + RSA encryption (and then in reverse for decrypting). The signatures for those 2 new methods can be found in eucrypt/smg_rsa/include/smg_rsa.h:


/*********
 * @param output - an MPI with KEY_LENGTH_OCTETS octets allocated space;
                   it will hold the result: (rsa(oaep(input), pk))
   @param input  - the plain-text message to be encrypted; maximum length is
                   245 octets (1960 bits)
   @param pk     - public key with which to encrypt
   NB: this method does NOT allocate memory for output!
   preconditions:
     - output IS different from input!
     - output has at least KEY_LENGTH_OCTETS octets allocated space
     - input is AT MOST max_len_msg octets long (ct defined in smg_oaep.ads)
 */
void rsa_oaep_encrypt( MPI output, MPI input, RSA_public_key *pk);

/*
 * Opposite operation to rsa_oaep_encrypt.
 * Attempts oaep_decrypt(rsa_decrypt(input))
 * @param output - an MPI to hold the result; allocated >= max_len_msg octets
 * @param input  - an MPI previously obtained with rsa_oaep_encrypt
 * @param sk     - the secret key with which to decrypt
 * @param success - this will be set to -1 if there is an error
 *
 * preconditions:
 *   - output IS different from input!
 *   - output has at least KEY_LENGTH_OCTETS octets allocated space
 *   - input is precisely KEY_LENGTH_OCTETS
 */
void rsa_oaep_decrypt( MPI output, MPI input, RSA_secret_key *sk, int *success);

The comments in the code snippet above should be clear enough: rsa_oaep_encrypt takes an MPI (the "message") and a public key as input, calls first oaep on the message and then rsa on the result, returning (via "output") the final result; rsa_oaep_decrypt does the reverse operations using the corresponding secret key and performing therefore rsa first, followed by oaep decrypt. Note that rsa_oaep_decrypt has an additional return value through the "success" flag - this is needed in order to signal to the caller potential clear failures at decryption (for instance if the input given is corrupted or otherwise a non-valid input). Obviously, the method works with a relatively limited definition of "success" since it can't really tell whether you got the original plain-text, nor is it meant to care about that: when the success flag is set to a negative value3 the caller can be absolutely sure that something went wrong; on the other hand, when the success flag is set to a positive value, the caller can only be assured that *something* was obtained through the decryption process from the original MPI. No assurance is given (nor can be given) regarding the value, integrity or meaning of that something.

Since the OAEP part is implemented in Ada, the same .h file as above also contains a few more declarations:

/*
 * This is the maximum length of a plain-text message (in octets) that can be
 * oeap+rsa encrypted in a single block. Its value is defined in smg_oaep.ads
 */
extern int max_len_msg;

/*
 * ada-exported oaep encrypt
 */
extern void oaep_encrypt_c( char* msg, int msglen,
                            char* entropy, int entlen,
                            char* encr, int encrlen,
                            int* success);

/*
 * ada-exported oaep decrypt
 */
extern void oaep_decrypt_c( char* encr, int encrlen,
                            char* decr, int* decrlen,
                            int* success);

Those oaep_encrypt_c and oaep_decrypt_c are Ada wrappers made specifically for use from C. The reason why such wrappers are needed (as opposed to using directly the Ada OAEP procedure for instance) is precisely the need to translate back and forth between C's char * unkempt "strings" and Ada's very tidy, trim and proper fixed-length Strings. The length of a char * from C can be anything and C doesn't bother apparently to pass it explicitly to Ada when calling an Ada function. At the same time, Ada is very careful to avoid writing out of bounds so its "Strings" are fixed-size arrays of char, with very well-known and unchangeable length4. To help with such interfacing problems, Ada conveniently offers the packages Interfaces.C and Interfaces.C.Strings. Using the later, one could work with chars_ptr that are in principle specifically made to handle the char * of C. However, since my purpose is not at all to import char * into C but rather to somehow bridge the chasm and recover the information from char * into a sane, fixed-length array of char, it follows that I'm using Interfaces.C.char_array. And to use those, C will have to pass explicitly the length of each char * parameter that it ever passes to Ada in any form. On its side, Ada will have to (unfortunately...) ...basically trust that value as correct and use therefore a fixed-length String of precisely that length, reading/writing at the address stored in the char* parameter at most length octets and not more.

On C side, the implementation of rsa_oaep_encrypt and rsa_oaep_decrypt are quite straightforward, making use of the previously discussed oaep_encrypt_c and oaep_decrypt_c external methods conveniently provided by Ada. From eucrypt/smg_rsa/rsa.c:

void rsa_oaep_encrypt( MPI output, MPI input, RSA_public_key *pk) {
  /* precondition: output is different from input */
  assert( output != input );

  /* precondition: output has enough memory allocated */
	unsigned int nlimbs_n = mpi_nlimb_hint_from_nbytes( KEY_LENGTH_OCTETS);
	assert( mpi_get_alloced( output ) >= nlimbs_n);

  /* precondition: input is at most max_len_msg octets long */
  unsigned int nlimbs_msg = mpi_nlimb_hint_from_nbytes( max_len_msg );
  assert( mpi_get_nlimbs( input ) <= nlimbs_msg);

  /* Step 1: oaep padding */
  /* get message char array and length */
  int msglen = 0;
  int sign;
  unsigned char * msg = mpi_get_buffer( input, &msglen, &sign);
  /* allocate memory for result */
  int encrlen = KEY_LENGTH_OCTETS;
  unsigned char * encr = xmalloc( encrlen );
  int entlen = KEY_LENGTH_OCTETS;
  unsigned char * entropy = xmalloc( entlen );
  int success = -10;
  /* call oaep until result is strictly < N of the rsa key to use */
  MPI oaep = mpi_alloc( nlimbs_n ); /* result of oaep encrypt/pad */

  int nread;
  do {
    /* get random bits */
		do {
      nread = get_random_octets( entlen, entropy );
		} while (nread != entlen);

    oaep_encrypt_c( msg, msglen, entropy, entlen, encr, encrlen, &success);
    if (success > 0) {
      /* set the obtained oaep to output mpi and compare to N of the rsa key */
      /* NB: 0-led encr WILL GET TRUNCATED!! */
      mpi_set_buffer( oaep, encr, encrlen, 0);
    }
    printf(".");
  }
  while ( success <=0 || mpi_cmp( oaep, pk->n ) >= 0 );

  printf("n");
  /* Step2 : call rsa for final result */
  public_rsa( output, oaep, pk );

  /* clear up */
  xfree( msg );
  xfree( encr );
  xfree( entropy );
  mpi_free( oaep );
}

void rsa_oaep_decrypt( MPI output, MPI input, RSA_secret_key *sk, int *success)
{
  *success = -1;
	unsigned int nlimbs_n = mpi_nlimb_hint_from_nbytes( KEY_LENGTH_OCTETS);
  unsigned int nlimbs_msg = mpi_nlimb_hint_from_nbytes( max_len_msg );

  /* preconditions */
  assert( output != input );
	assert( mpi_get_alloced( output ) >= nlimbs_msg);
  assert( mpi_get_nlimbs( input )  == nlimbs_n);

  /* rsa */
  MPI rsa_decr = mpi_alloc( nlimbs_n );
  secret_rsa( rsa_decr, input, sk );

  /* oaep */
  unsigned encr_len, decr_len;
  int sign, flag;
  char *oaep_encr = mpi_get_buffer( rsa_decr, &encr_len, &sign );
  char *oaep_decr = xmalloc( encr_len );
  decr_len = encr_len;
  oaep_decrypt_c( oaep_encr, encr_len, oaep_decr, &decr_len, &flag );

  /* check status */
  if ( flag > 0 ) {
    *success = 1;
    mpi_set_buffer( output, oaep_decr, decr_len, 0 );
  }
  else
    *success = -1;

  /* cleanup */
  mpi_free( rsa_decr );
  xfree( oaep_encr );
  xfree( oaep_decr );
}

The actual steps of both encryption and decryption above should be fairly obvious and further explained by the comments in the code. I'll just point to you one important decision taken in there: the oaep encryption is called in a loop until the result obtained is strictly smaller than the modulus (n) of the RSA key that will be used for encryption. This condition is a requirement of RSA and since oaep returns a block of the same length of TMSR RSA modulus, it follows that in some cases the oaep block will actually be a number bigger than the modulus, there is no way around this. Happily though, the TMSR RSA modulus has the highest bit set, meaning that an oaep block with highest bit 0 will surely be smaller than n. And since the highest bit of the resulting oaep block is quite random (TMSR OAEP uses *by design* a significant number of *true* random bits), it follows that it's really enough to simply check the result provided by oaep encryption, discard it if it's bigger than the modulus n and try again with another set of random bits - in most cases there should be at most 2 tries in order to get something that can be then safely fed to RSA encrypt.

To support the calling of Ada stuff from C as shown above, a lot of ugly code has to be added in Ada - the wrappers for the otherwise-perfectly-fine Ada oaep methods. Those are all at least contained in a single package, namely smg_oaep in eucrypt/smg_keccak/smg_oaep.ads and eucrypt/smg_keccak/smg_oaep.adb. First, methods to copy octet by octet from /to those char *:

  -- copy from Ada String to C char array and back, octet by octet

  -- This copies first Len characters from A to the first Len positions in S
  -- NB: this does NOT allocate /check memory!
  -- Caller has to ensure that:
  --    S has space for at least Len characters
  --    A has at least Len characters
  procedure Char_Array_To_String( A   : in Interfaces.C.char_array;
                                  Len : in Natural;
                                  S   : out String);

  -- This copies first Len characters from S to the first Len positions in A
  -- NB: there are NO checks or memory allocations here!
  -- Caller has to make sure that:
  --   S'Length >= Len
  --   A has allocated space for at least Len characters
  procedure String_To_Char_Array( S   : in String;
                                  Len : in Natural;
                                  A   : out Interfaces.C.char_array);

Then, the wrappers themselves and the pragma export for them:

 -- wrapper of oaep_encrypt for direct use from C
  -- NB: caller HAS TO provide the length of the Message (parameter LenMsg)
  -- NB: caller HAS TO provide the length of the Entropy (parameter LenEnt)
  -- NB: caller HAS TO provide the allocated space for result (LenEncr)
  -- NB: LenEncr HAS TO be at least OAEP_LENGTH_OCTETS!
  -- NB: LenEnt HAS TO be at least OAEP_LENGTH_OCTETS or this will FAIL!
  procedure OAEP_Encrypt_C( Msg       : in Interfaces.C.char_array;
                            MsgLen    : in Interfaces.C.size_t;
                            Entropy   : in Interfaces.C.char_array;
                            EntLen    : in Interfaces.C.size_t;
                            Encr      : out Interfaces.C.char_array;
                            EncrLen   : in Interfaces.C.size_t;
                            Success   : out Interfaces.C.Int);
  pragma Export( C, OAEP_Encrypt_C, "oaep_encrypt_c" );

 -- wrapper for use from C
  procedure oaep_decrypt_c( Encr    : in Interfaces.C.Char_Array;
                            EncrLen : in Interfaces.C.Int;
                            Decr    : out Interfaces.C.Char_Array;
                            DecrLen : in out Interfaces.C.Int;
                            Success : out Interfaces.C.Int);
  pragma Export( C, oaep_decrypt_c, "oaep_decrypt_c");

The implementations for all the above:

  -- This copies first Len characters from A to the first Len positions in S
  -- NB: this does NOT allocate /check memory!
  -- Caller has to ensure that:
  --    S has space for at least Len characters
  --    A has at least Len characters
  procedure Char_Array_To_String( A   : in Interfaces.C.char_array;
                                  Len : in Natural;
                                  S   : out String) is
  begin
    for Index in 0 .. Len - 1 loop
      S( S'First + Index ) := Character( A( Interfaces.C.size_t( Index )));
    end loop;
  end Char_Array_To_String;

  -- This copies first Len characters from S to the first Len positions in A
  -- NB: there are NO checks or memory allocations here!
  -- Caller has to make sure that:
  --   S'Length >= Len
  --   A has allocated space for at least Len characters
  procedure String_To_Char_Array( S   : in String;
                                  Len : in Natural;
                                  A   : out Interfaces.C.char_array) is
    C : Character;
  begin
    for Index in 0 .. Len - 1 loop
      C := S( S'First + Index );
      A( Interfaces.C.size_t( Index )) := Interfaces.C.Char( C );
    end loop;
  end String_To_Char_Array;

  procedure OAEP_Encrypt_C( Msg       : in Interfaces.C.char_array;
                            MsgLen    : in Interfaces.C.size_t;
                            Entropy   : in Interfaces.C.char_array;
                            EntLen    : in Interfaces.C.size_t;
                            Encr      : out Interfaces.C.char_array;
                            EncrLen   : in Interfaces.C.size_t;
                            Success   : out Interfaces.C.Int) is
    AdaMsgLen  : Natural := Natural( MsgLen );
    AdaEntLen  : Natural := Natural( EntLen );
    AdaEncrLen : Natural := Natural( EncrLen );
    AdaMsg     : String( 1 .. AdaMsgLen );
    AdaEntBlock: OAEP_Block;
    AdaResult  : OAEP_Block := ( others => '0' );
  begin
    Success := 0;
    -- check there is enough entropy and enoug output space, fail otherwise
    if AdaEntLen /= AdaEntBlock'Length or AdaEncrLen < AdaResult'Length then
      return;
    end if;
    -- translate to Ada
      --Interfaces.C.To_Ada( Msg, AdaMsg, AdaMsgLen );
    Char_Array_To_String( Msg, AdaMsgLen, AdaMsg );
      --Interfaces.C.To_Ada( Entropy, AdaEntropy, AdaEntLen );
    Char_Array_To_String( Entropy, AdaEntLen, AdaEntBlock );

    -- call the actual oaep encrypt
    OAEP_Encrypt( AdaMsg, AdaEntBlock, AdaResult );

    -- translate back to C, set success flag and return
       --Interfaces.C.To_C( AdaResult, CEncr, CEncrLen, False );
    -- EncrLen has already been tested to be at least AdaResult'Length
    String_To_Char_Array( AdaResult, AdaEncrLen, Encr );
    Success := 1;

  end OAEP_Encrypt_C;

  procedure oaep_decrypt_c( Encr    : in Interfaces.C.Char_Array;
                            EncrLen : in Interfaces.C.Int;
                            Decr    : out Interfaces.C.Char_Array;
                            DecrLen : in out Interfaces.C.Int;
                            Success : out Interfaces.C.Int) is
    AdaDecr    : OAEP_HALF := ( others => '0' );
    AdaEncr    : OAEP_Block:= ( others => '0' );
    AdaEncrLen : Natural := Natural( EncrLen );
    AdaDecrLen : Natural := 0;
    AdaFlag    : Boolean;
  begin
    -- check and set success flag/exit if needed
    Success := 0;
    if EncrLen /= OAEP_Block'Length then
      return;
    end if;

    -- translate to Ada: copy octet by octet as C.To_Ada is problematic
      -- Interfaces.C.To_Ada( Encr, AdaEncr, AdaEncrLen, False );
    Char_Array_To_String( Encr, AdaEncrLen, AdaEncr );

    -- actual decrypt
    OAEP_Decrypt( AdaEncr, AdaDecrLen, AdaDecr, AdaFlag );

    -- translate back to C
    AdaDecrLen := AdaDecrLen / 8;  -- from bits to octets
    if AdaFlag and
       Natural( DecrLen ) >= AdaDecrLen and
       AdaDecr'Length >= AdaDecrLen then
      Success := 1;
      DecrLen := Interfaces.C.Int( AdaDecrLen );
        -- Interfaces.C.To_C( AdaDecr, Decr, AdaDecrLen );
      String_To_Char_Array( AdaDecr, AdaDecrLen, Decr );
    end if;
  end oaep_decrypt_c;

In case you wonder why am I using this octet by octet copy thing instead of the procedures in Interfaces.C (To_Ada and To_C): I tried to use them and in some cases they still fail miserably, quite possibly because I don't yet fully understand them and therefore I'm not using them properly. So if you have experience with them for the sort of task you see here, please chime in. In any case, for as long as I can't trust them, I can't use them here, so octet by octet copying it is at least for now.

As seen before with the .gpr files, the compilation itself is quite straightforward. The call of Ada methods from C is also just a matter of "pragma export "foo" " on Ada side and "extern foo" on C side. The handy .gpr file takes otherwise care of the dependency introduced between the two EuCrypt components (since smg_rsa uses now smg_keccak) and we are all set. Time therefore to run the tests! More specifically, the *new* tests, in eucrypt/smg_rsa/tests/tests.c:

void test_oaep_encr_decr( int nruns ) {
  /* a set of RSA keys previously generated with eucrypt */
	RSA_public_key pk;
	pk.n = mpi_alloc(0);
	pk.e = mpi_alloc(0);

  RSA_secret_key sk;
  sk.n = mpi_alloc(0);
  sk.e = mpi_alloc(0);
  sk.d = mpi_alloc(0);
  sk.p = mpi_alloc(0);
  sk.q = mpi_alloc(0);
  sk.u = mpi_alloc(0);

  mpi_fromstr(sk.n, "0x
CD2C025323BEA46FFF2FA8D7A9D39817EA713421F4AE03FA8120641193892A70BFECF5
83101635A432110D3DDE6339E3CC7ECC0AD91C026FCACE832DD3888A6FCA7BCE56C390
5A5AC8C7BC921DA675E4B62489B254EB34659D547D71165BC998983A81937BD251AEE1
2D985EC387D5376F5DCC5EF7EC530FBD6FD2AA7285EE1AF3335EA73163F0954F30402E
D7B374EE84A97B1849B0674B0DA0A2050BD79B71ABB1559F3A9CFDB8557DED7BC90CF2
09E8A847E9C226140845B7D03842162E7DA5DD16326CB1F71A248D841FE9076A09911F
2F4F5E3EA44EA8DE40332BF00406990BCCF61C322A03C456EF3A98B341E0BDBC1088CE
683E78510E76B72C2BCC1EE9AEDD80FFF18ABFC5923B2F36B581C25114AB2DF9F6C2B1
9481703FD19E313DCD7ACE15FA11B27D25BCE5388C180A7E21167FB87750599E1ED7C7
50F4A844E1DC2270C62D19671CF8F4C25B81E366B09FC850AE642136D204A9160AEECE
575B57378AA439E9DD46DC990288CD54BAA35EEE1C02456CD39458A6F1CBF012DCEDF4
27CCF3F3F53645658FC49C9C9D7F2856DB571D92B967AB5845514E0054DDB49099F5DD
04A6F6F5C5CE642276834B932881AEB648D1F25E9223971F56E249EF40CF7D80F22621
CDD0260E9E7D23746960ADB52CF2987584FB1DE95A69A39E5CB12B76E0F5C1A0529C0C
065D2E35720810F7C7983180B9A9EA0E00C11B79DC3D");

  mpi_fromstr(sk.e, "0x
DD4856B4EE3D099A8604AE392D8EFEC094CDF01546A28BE87CB484F999E8E75CDFCD01
D04D455A6A9254C60BD28C0B03611FC3E751CC27EF768C0B401C4FD2B27C092834A6F2
49A145C4EDC47A3B3D363EC352462C945334D160AF9AA72202862912493AC6190AA3A6
149D4D8B9996BA7927D3D0D2AD00D30FD630CF464E6CAF9CF49355B9A70E05DB7AE915
F9F602772F8D11E5FCDFC7709210F248052615967090CC1F43D410C83724AA5912B2F0
52E6B39449A89A97C79C92DC8CB8DEEFCF248C1E1D2FC5BFE85165ECA31839CAA9CEB3
3A92EBDC0EB3BAC0F810938BB173C7DA21DCBB2220D44CBA0FD40A2C868FC93AC5243E
C137C27B0A76D65634EBB3");

  mpi_fromstr(sk.d, "0x
7C8A6FA1199D99DCA45E9BDF567CA49D02B237340D7E999150BC4883AE29DEC5158521
B338F35DC883792356BDDBB3C8B3030A6DD4C6522599A3254E751F9BA1CB1061C5633C
81BBFACF6FCD64502614102DFED3F3FA284066C342D5E00953B415915331E30812E5FB
CD6680ADCCDEE40B8376A3A225F2E160EA59C7566804526D73BB660A648A3EF9802313
B2F841E8458B2AAACE7AACF31083E8F3F630298138393BC88BBD7D4AA4334949651D25
365B10DBF4A4A08E20A6CC74BFDD37C1C38E2ADC2A283DF06590DF06B46F67F6ACA67F
AC464C795261659A2F9558802D0BBAA05FD1E1AF2CDC70654723DF7EFAEA148B8CDBEB
C89EA2320AB9BBB1BC4311475DF3D91446F02EF192368DFEBAC598CCFD4407DEC58FDC
1A94CCDD6E5FBA9C52164ACEA8AEE633E557BCCEACB7A1AF656C379482D784A120A725
32F9B2B35173D505F21D5AD4CB9511BC836DC923730B70291B70290A216CA3B21CFF79
E895C35F4F7AF80E1BD9ED2773BD26919A76E4298D169160593E0335BE2A2A2D2E8516
948F657E1B1260E18808A9D463C108535FB60B3B28F711C81E5DE24F40214134A53CE5
9A952C8970A1D771EBEFFA2F4359DCF157995B3F1950DE3C6EC41B7FF837148F55F323
372AF3F20CE8B8038E750C23D8F5041FA951327859B0E47483F0A47103EF808C72C251
006FA526245291C8C84C12D2EF63FB2301EA3EEDA42B");

  mpi_fromstr(sk.p, "0x
E236732452039C14EC1D3B8095BDDCFB7625CE27B1EA5394CF4ED09D3CEECAA4FC0BF6
2F7CE975E0C8929CE84B0259D773EA038396479BF15DA065BA70E549B248D77B4B23ED
A267308510DBEE2FD44E35D880EE7CFB81E0646AA8630165BD8988C3A8776D9E704C20
AA25CA0A3C32F27F592D5FD363B04DD57D8C61FFDCDFCCC59E2913DE0EE47769180340
E1EA5A803AA2301A010FF553A380F002601F0853FCACDB82D76FE2FACBCD6E5F294439
0799EA5AE9D7880D4E1D4AE146DC1D4E8495B9DD30E57E883923C5FC26682B7142D35C
D8A0FC561FE725A6CF419B15341F40FE0C31132CBD81DD8E50697BD1EBFFA16B522E16
F5B49A03B707218C7DA60B");

  mpi_fromstr(sk.q, "0x
E830482A3C4F5C3A7E59C10FF8BA760DB1C6D55880B796FFDA4A82E0B60E974E81D04B
2A4AD417823EBFB4E8EFB13782943562B19B6C4A680E3BA0C8E37B5023470F4F1AC1F8
A0B10672EF75CD58BCD45E6B14503B8A6A70AFE79F6201AF56E7364A1C742BE1453FD2
24FDC9D66522EAF4466A084BCB9E46D455A2946E94CBF028770F38D0B741C2CC59308F
71D8C2B4B9C928E0AE8D68DEB48A3E9EFD84A10301EBD55F8221CA32FC567B306B2A8E
116350AFB995859FDF4378C5CFD06901494E8CFA5D8FAC564D6531FA8A2E4761F5EFBA
F78750B6F4662BE9EA4C2FAD67AF73EEB36B41FC15CB678810C19A51DF23555695C4C1
546F3FACA39CAA7BB8DBD7");

  mpi_fromstr(sk.u, "0x
846232322775C1CD7D5569DC59E2F3E61A885AE2E9C4A4F8CB3ACBE8C3A5441E5FE348
A2A8AC9C2998FBF282222BF508AA1ECF66A76AEDD2D9C97028BFD3F6CA0542E38A5312
603C70B95650CE73F80FDD729988FBDB5595A5BF8A007EA34E54994A697906CE56354C
E00DF10EB711DEC274A62494E3D350D88736CF67A477FB600AC9F1D6580727585092BF
5EBC092CC4D6CF75769051033A1197103BE269942F372168A53771746FBA18ED6972D5
0B935A9B1D6B5B3DD50CD89A27FE93C10924E9103FACF7B4C5724A046C3D3B50CC1C78
5F5C8E00DBE1D6561F120F5294C170914BC10F978ED4356EED67A9F3A60D70AFE540FC
5373CBAE3D0A7FD1C87273");

  /* copy the public key components */
  pk.n = mpi_copy( sk.n );
  pk.e = mpi_copy( sk.e );

  /* some plain text message */
	MPI msg = mpi_alloc(0);
	mpi_fromstr(msg, "0x
5B6A8A0ACF4F4DB3F82EAC2D20255E4DF3E4B7C799603210766F26EF87C8980E737579
EC08E6505A51D19654C26D806BAF1B62F9C032E0B13D02AF99F7313BFCFD68DA46836E
CA529D7360948550F982C6476C054A97FD01635AB44BFBDBE2A90BE06F7984AC8534C3
28097EF92F6E78CAE0CB97");

  /* actual testing */
	printf("TEST verify oaep_encr_decr on message: n");
	mpi_print( stdout, msg, 1);
	printf("n");

  int nlimbs_n = mpi_nlimb_hint_from_nbytes( KEY_LENGTH_OCTETS);
	MPI encr = mpi_alloc( nlimbs_n );
	MPI decr = mpi_alloc( nlimbs_n );
  int success;

  adainit();
  rsa_oaep_encrypt( encr, msg, &pk );
  rsa_oaep_decrypt( decr, encr, &sk, &success );

  if (success <= 0 ||
      mpi_cmp(encr, msg) == 0 ||
      mpi_cmp(msg, decr) != 0)
    printf("FAILED: success flag is %dn", success);
  else
    printf("PASSEDn");

  /* attempt to decrypt corrupted block */
  mpi_clear( decr );
  rsa_oaep_decrypt( decr, pk.n, &sk, &success);
  if (success > 0)
    printf("FAILED: attempt to decrypt non-/corrupted oaep blockn");
  else
    printf("PASSED: attempt to decrypt non-/corrupted oaep blockn");
  adafinal();

  /* clean up */
  mpi_free( sk.n );
  mpi_free( sk.e );
  mpi_free( sk.d );
  mpi_free( sk.p );
  mpi_free( sk.q );
  mpi_free( sk.u );

	mpi_free( pk.n );
	mpi_free( pk.e );

	mpi_free( msg );
	mpi_free( encr );
	mpi_free( decr );
}

void test_mpi_buffer() {
  unsigned int noctets = 10;
  int nlimbs = mpi_nlimb_hint_from_nbytes( noctets );
  MPI m = mpi_alloc( nlimbs );
  unsigned char *setbuffer = xmalloc( noctets );
  unsigned char *getbuffer;
  unsigned int i, sign, mpilen, nerrors;

  for (i=0; i< noctets; i++)
    setbuffer[i] = i;

  mpi_set_buffer( m, setbuffer, noctets, 0);

  getbuffer = mpi_get_buffer( m, &mpilen, &sign );

  if (mpilen == noctets -1 ) {
    nerrors = 0;
    for (i=0;i0)
      printf("FAIL: got %d different values!n", nerrors);
    else printf("PASSED: mpi_get/set_buffern");
  }

  mpi_free(m);
  xfree(setbuffer);
  xfree(getbuffer);
}

The test_oaep_encr_decr method uses a pair of TMSR RSA keys (previously generated by smg_rsa) to attempt oaep+rsa on a message, using rsa_oaep_encrypt and rsa_oaep_decrypt. There is also an attempt at decrypting a "corrupted" oaep block and this correctly fails with the success flag set accordingly.

The second test method in there is ...bonus. It follows a further discovery of the unexpected in the mpi "code": the mpi_set_buffer and mpi_get_buffer methods for an mpi are not exactly symmetrical. In other words, calling mpi_get_buffer for the same mpi on which you previously (immediately before!) called mpi_set_buffer with a given buffer b will NOT always return EXACTLY same b! That's because mpi_set_buffer will helpfully trim leading-0 octets from the buffer you pass, so if you pass number 009, it will store 9 only and therefore it will return...9. To reflect this, the test gives a warning as result - basically I'm flagging this for the future, not changing anything at the moment. This MPI implementation has already eaten an incredible amount of time with very little to show for it in return and I foresee that it will still eat even more time with similarly poor returns on it. Moreover, it has already gotten to the stage where I think it would have been probably better *not* to have an mpi implementation at all than to have this one. I can only add that I would certainly throw it away and implement a useful Ada library, if not for the fact that there really, really are many more pressing things to do right now. Sigh.

Getting back to happier facts, this chapter quite completes EuCrypt as the library contains now everything that it is currently meant to contain. Since the original Introduction, there has been a change in that the smg_comm component was taken out of EuCrypt: the reason for this removal is that smg_comm is very specific to Eulora and as such it is naturally a user of EuCrypt rather than part of it; by contrast, EuCrypt offers generic crypto routines (despite being made because Eulora needs it). The .vpatch and its signature can be found on my Reference Code Shelf and are also linked directly here for your convenience:

Any further chapters -if and when they might be- will deal with cosmetic changes or fixing of errors if any are found. At the moment there aren't any further components /parts planned as part of EuCrypt itself. Give it a spin!


  1. I'm currently using Adacore's 2016 version with gcc 4.9.4 as previously mentioned in the logs

  2. Note that here as everywhere I really recommend the Adacore version as opposed to whatever your favourite linux distribution can find: the reason for this recommendation is pure and painful experience - while I think that one *can* get a working setup with non-Adacore GNAT and/or GPR and random-flavour gcc, it's certainly not a straightforward task and there are so many things that can (and do!) go wrong (mismatching versions of various tools is the one I stumbled on repeatedly) that it's not worth it, simply. So do yourself a favour and get the only ada-tron that actually... works - Adacore's. 

  3. Grrr, why can't this be a Boolean as it should be!!! 

  4. Yes, I know you can use unbounded strings instead but I won't be using those unless I really, really have no choice. I suggest you do the same rather than writing "in Ada" while importing as much C-uncertainty as possible. 

February 22, 2018

EuCrypt Chapter 11: Serpent

Filed under: EuCrypt — Diana Coman @ 1:22 p.m.

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

This chapter adds to EuCrypt's generous endowment in the bit and byte diddling1 department: a byte-ready Serpent joins the bit-level Keccak and byte-level Keccak from previous chapters!

As I previously reviewed the functioning and testing of this implementation of Serpent, I won't repeat that part here. I'll go instead straight to the code, which is found in eucrypt/smg_serpent. There is first the smg_serpent.gpr file, for compiling (using gprbuild)2 the smg_serpent as a static library by itself:

-- S.MG, 2018

project SMG_Serpent is
  for Languages use ("Ada");
  for Library_Name use "SMG_Serpent";
  for Library_Kind use "static";

  for Source_Dirs use ("src");
  for Object_Dir use "obj";
  for Library_Dir use "lib";

end SMG_Serpent;

As you might guess from the .gpr file above, the actual sources of Serpent itself are found in eucrypt/smg_serpent/src. The smg_serpent.ads is minimal, simply defining a few types that Serpent uses (Bytes as array of Unsigned_8 aka array of octets aka array of groups of 8 bits; Words as array of Unsigned_32; Block as an array of 16 octets; Key as an array of 32 octets and Key_Schedule as an array of 140 32-bit words indexed for Serpent's purposes from -8 to 13), the three main procedures for Serpent use (making the key schedule out of a given Serpent key; encrypting a given plaintext with a given key schedule; decrypting a given encrypted text with a given key schedule) and one additional procedure that provides one single self-test of Serpent's functioning:

-------------------------------------------------------------------------------
-- S.MG, 2018; with added automated tests
--
-- Serpent Blockcipher
--
-- Copyright (c) 1998 Markus G. Kuhn . All rights reserved.
--
-- $Id: serpent.ads,v 1.2 1998-06-10 14:22:16+00 mgk25 Exp $
--
-------------------------------------------------------------------------------
--
-- This is the Ada95 reference implementation of the Serpent cipher
-- submitted by Ross Anderson, Eli Biham and Lars Knudson in June 1998 to
-- the NIST Advanced Encryption Standard (AES) contest. Please note that
-- this is a revised algorithm that is not identical to the old version
-- presented at the 1998 Fast Software Encryption Workshop.
-- 
--
-- Compiled with GNAT 3.10p under Linux, this implementation encrypts and
-- decrypts with 20.8 Mbit/s on a 300 MHz Pentium II.
--
-------------------------------------------------------------------------------

with Interfaces; use Interfaces;

package SMG_Serpent is

  pragma Pure(SMG_Serpent);

  type Bytes is array (Integer range <>) of Unsigned_8;
  type Words is array (Integer range <>) of Unsigned_32;
  subtype Block is Bytes (0 .. 15);
  subtype Key   is Bytes (0 .. 31);
  subtype Key_Schedule is Words (-8 .. 131);

  procedure Prepare_Key (K : in Key; W : out Key_Schedule);

  procedure Encrypt (W : in Key_Schedule; Plaintext  :  in Block;
					   Ciphertext : out Block);

  procedure Decrypt (W : in Key_Schedule; Ciphertext :  in Block;
					   Plaintext  : out Block);

  procedure Selftest;

  Implementation_Error : exception;  -- raised if Selftest failed

end SMG_Serpent;

The actual implementation of the above is quite straightforward but relatively verbose due to loops being unrolled for faster execution. There is also handling of big endian / little endian iron through octet flipping, as required:

 -------------------------------------------------------------------------------
 --
 -- Serpent Blockcipher
 --
 -- Copyright (c) 1998 Markus G. Kuhn . All rights reserved.
 --
 -- Modified by S.MG, 2018
 --
 -------------------------------------------------------------------------------
 --
 -- This implementation is optimized for best execution time by use of
 -- function inlining and loop unrolling. It is not intended to be used in
 -- applications (such as smartcards) where machine code size matters. Best
 -- compiled with highest optimization level activated and all run-time
 -- checks supressed.
 --
 -------------------------------------------------------------------------------

with System, Ada.Unchecked_Conversion;
use System;

package body SMG_Serpent is

  pragma Optimize( Time );

  -- Auxiliary functions for byte array to word array conversion with
  -- Bigendian/Littleendian handling.
  --
  -- The convention followed here is that the input byte array
  --
  --   00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
  --
  -- is converted into the register values
  --
  --   X0 = 03020100,  X1 = 07060504,  X2 = 0b0a0908,  X3 = 0f0e0d0c

  subtype Bytes_4 is Bytes (0 .. 3);
  function Cast is new Ada.Unchecked_Conversion (Bytes_4, Unsigned_32);
  function Cast is new Ada.Unchecked_Conversion (Unsigned_32, Bytes_4);

  function Bytes_To_Word (X : Bytes_4) return Unsigned_32 is
  begin
    if Default_Bit_Order = Low_Order_First then
      -- we have a Littleendian processor
      return Cast(X);
    else
      -- word sex change
      return Cast((X(3), X(2), X(1), X(0)));
    end if;
  end Bytes_To_Word;

  function Word_To_Bytes (X : Unsigned_32) return Bytes_4 is
  begin
    if Default_Bit_Order = Low_Order_First then
      -- we have a Littleendian processor
      return Cast(X);
    else
      -- word sex change
      return (Cast(X)(3), Cast(X)(2), Cast(X)(1), Cast(X)(0));
    end if;
  end Word_To_Bytes;

  pragma Inline(Bytes_To_Word, Word_To_Bytes);
  -- inline functions for the Encryption and Decryption procedures

  -- Sbox function
  procedure S (R : Integer; X0, X1, X2, X3 : in out Unsigned_32) is
      T01, T02, T03, T04, T05, T06, T07, T08, T09,
      T10, T11, T12, T13, T14, T15, T16, T17, T18 : Unsigned_32;
      W, X, Y, Z : Unsigned_32;
  begin
    if R = 0 then
      -- S0:   3  8 15  1 10  6  5 11 14 13  4  2  7  0  9 12
      -- depth = 5,7,4,2, Total gates=18
      T01 := X1  xor X2;
      T02 := X0  or X3;
      T03 := X0  xor X1;
      Z   := T02 xor T01;
      T05 := X2  or z;
      T06 := X0  xor X3;
      T07 := X1  or X2;
      T08 := X3  and T05;
      T09 := T03 and T07;
      Y   := T09 xor T08;
      T11 := T09 and y;
      T12 := X2  xor X3;
      T13 := T07 xor T11;
      T14 := X1  and T06;
      T15 := T06 xor T13;
      W   :=     not T15;
      T17 := W   xor T14;
      X   := T12 xor T17;
    elsif R = 1 then
      -- S1:  15 12  2  7  9  0  5 10  1 11 14  8  6 13  3  4
      -- depth = 10,7,3,5, Total gates=18
      T01 := X0  or X3;
      T02 := X2  xor X3;
      T03 :=     not X1;
      T04 := X0  xor X2;
      T05 := X0  or T03;
      T06 := X3  and T04;
      T07 := T01 and T02;
      T08 := X1  or T06;
      Y   := T02 xor T05;
      T10 := T07 xor T08;
      T11 := T01 xor T10;
      T12 := Y   xor T11;
      T13 := X1  and X3;
      Z   :=     not T10;
      X   := T13 xor T12;
      T16 := T10 or x;
      T17 := T05 and T16;
      W   := X2  xor T17;
    elsif R = 2 then
      -- S2:   8  6  7  9  3 12 10 15 13  1 14  4  0 11  5  2
      -- depth = 3,8,11,7, Total gates=16
      T01 := X0  or X2;
      T02 := X0  xor X1;
      T03 := X3  xor T01;
      W   := T02 xor T03;
      T05 := X2  xor w;
      T06 := X1  xor T05;
      T07 := X1  or T05;
      T08 := T01 and T06;
      T09 := T03 xor T07;
      T10 := T02 or T09;
      X   := T10 xor T08;
      T12 := X0  or X3;
      T13 := T09 xor x;
      T14 := X1  xor T13;
      Z   :=     not T09;
      Y   := T12 xor T14;
    elsif R = 3 then
      -- S3:   0 15 11  8 12  9  6  3 13  1  2  4 10  7  5 14
      -- depth = 8,3,5,5, Total gates=18
      T01 := X0  xor X2;
      T02 := X0  or X3;
      T03 := X0  and X3;
      T04 := T01 and T02;
      T05 := X1  or T03;
      T06 := X0  and X1;
      T07 := X3  xor T04;
      T08 := X2  or T06;
      T09 := X1  xor T07;
      T10 := X3  and T05;
      T11 := T02 xor T10;
      Z   := T08 xor T09;
      T13 := X3  or z;
      T14 := X0  or T07;
      T15 := X1  and T13;
      Y   := T08 xor T11;
      W   := T14 xor T15;
      X   := T05 xor T04;
    elsif R = 4 then
      -- S4:   1 15  8  3 12  0 11  6  2  5  4 10  9 14  7 13
      -- depth = 6,7,5,3, Total gates=19
      T01 := X0  or X1;
      T02 := X1  or X2;
      T03 := X0  xor T02;
      T04 := X1  xor X3;
      T05 := X3  or T03;
      T06 := X3  and T01;
      Z   := T03 xor T06;
      T08 := Z   and T04;
      T09 := T04 and T05;
      T10 := X2  xor T06;
      T11 := X1  and X2;
      T12 := T04 xor T08;
      T13 := T11 or T03;
      T14 := T10 xor T09;
      T15 := X0  and T05;
      T16 := T11 or T12;
      Y   := T13 xor T08;
      X   := T15 xor T16;
      W   :=     not T14;
    elsif R = 5 then
      -- S5:  15  5  2 11  4 10  9 12  0  3 14  8 13  6  7  1
      -- depth = 4,6,8,6, Total gates=17
      T01 := X1  xor X3;
      T02 := X1  or X3;
      T03 := X0  and T01;
      T04 := X2  xor T02;
      T05 := T03 xor T04;
      W   :=     not T05;
      T07 := X0  xor T01;
      T08 := X3  or w;
      T09 := X1  or T05;
      T10 := X3  xor T08;
      T11 := X1  or T07;
      T12 := T03 or w;
      T13 := T07 or T10;
      T14 := T01 xor T11;
      Y   := T09 xor T13;
      X   := T07 xor T08;
      Z   := T12 xor T14;
    elsif R = 6 then
      -- S6:   7  2 12  5  8  4  6 11 14  9  1 15 13  3 10  0
      -- depth = 8,3,6,3, Total gates=19
      T01 := X0  and X3;
      T02 := X1  xor X2;
      T03 := X0  xor X3;
      T04 := T01 xor T02;
      T05 := X1  or X2;
      X   :=     not T04;
      T07 := T03 and T05;
      T08 := X1  and x;
      T09 := X0  or X2;
      T10 := T07 xor T08;
      T11 := X1  or X3;
      T12 := X2  xor T11;
      T13 := T09 xor T10;
      Y   :=     not T13;
      T15 := X   and T03;
      Z   := T12 xor T07;
      T17 := X0  xor X1;
      T18 := Y   xor T15;
      W   := T17 xor T18;
    elsif R = 7 then
      -- S7:   1 13 15  0 14  8  2 11  7  4 12 10  9  3  5  6
      -- depth = 10,7,10,4, Total gates=19
      T01 := X0  and X2;
      T02 :=     not X3;
      T03 := X0  and T02;
      T04 := X1  or T01;
      T05 := X0  and X1;
      T06 := X2  xor T04;
      Z   := T03 xor T06;
      T08 := X2  or z;
      T09 := X3  or T05;
      T10 := X0  xor T08;
      T11 := T04 and z;
      X   := T09 xor T10;
      T13 := X1  xor x;
      T14 := T01 xor x;
      T15 := X2  xor T05;
      T16 := T11 or T13;
      T17 := T02 or T14;
      W   := T15 xor T17;
      Y   := X0  xor T16;
    end if;
    X0 := W;
    X1 := X;
    X2 := Y;
    X3 := Z;
  end S;

  -- Inverse Sbox function

  procedure SI (R : Integer; X0, X1, X2, X3 : in out Unsigned_32) is
      T01, T02, T03, T04, T05, T06, T07, T08, T09,
      T10, T11, T12, T13, T14, T15, T16, T17, T18 : Unsigned_32;
      W, X, Y, Z : Unsigned_32;
  begin
    if R = 0 then
      -- InvS0:  13  3 11  0 10  6  5 12  1 14  4  7 15  9  8  2
      -- depth = 8,4,3,6, Total gates=19
      T01 := X2  xor X3;
      T02 := X0  or X1;
      T03 := X1  or X2;
      T04 := X2  and T01;
      T05 := T02 xor T01;
      T06 := X0  or T04;
      Y   :=     not T05;
      T08 := X1  xor X3;
      T09 := T03 and T08;
      T10 := X3  or y;
      X   := T09 xor T06;
      T12 := X0  or T05;
      T13 := X   xor T12;
      T14 := T03 xor T10;
      T15 := X0  xor X2;
      Z   := T14 xor T13;
      T17 := T05 and T13;
      T18 := T14 or T17;
      W   := T15 xor T18;
    elsif R = 1 then
      -- InvS1:   5  8  2 14 15  6 12  3 11  4  7  9  1 13 10  0
      -- depth = 7,4,5,3, Total gates=18
      T01 := X0  xor X1;
      T02 := X1  or X3;
      T03 := X0  and X2;
      T04 := X2  xor T02;
      T05 := X0  or T04;
      T06 := T01 and T05;
      T07 := X3  or T03;
      T08 := X1  xor T06;
      T09 := T07 xor T06;
      T10 := T04 or T03;
      T11 := X3  and T08;
      Y   :=     not T09;
      X   := T10 xor T11;
      T14 := X0  or y;
      T15 := T06 xor x;
      Z   := T01 xor T04;
      T17 := X2  xor T15;
      W   := T14 xor T17;
    elsif R = 2 then
      -- InvS2:  12  9 15  4 11 14  1  2  0  3  6 13  5  8 10  7
      -- depth = 3,6,8,3, Total gates=18
      T01 := X0  xor X3;
      T02 := X2  xor X3;
      T03 := X0  and X2;
      T04 := X1  or T02;
      W   := T01 xor T04;
      T06 := X0  or X2;
      T07 := X3  or w;
      T08 :=     not X3;
      T09 := X1  and T06;
      T10 := T08 or T03;
      T11 := X1  and T07;
      T12 := T06 and T02;
      Z   := T09 xor T10;
      X   := T12 xor T11;
      T15 := X2  and z;
      T16 := W   xor x;
      T17 := T10 xor T15;
      Y   := T16 xor T17;
    elsif R = 3 then
      -- InvS3:   0  9 10  7 11 14  6 13  3  5 12  2  4  8 15  1
      -- depth = 3,6,4,4, Total gates=17
      T01 := X2  or X3;
      T02 := X0  or X3;
      T03 := X2  xor T02;
      T04 := X1  xor T02;
      T05 := X0  xor X3;
      T06 := T04 and T03;
      T07 := X1  and T01;
      Y   := T05 xor T06;
      T09 := X0  xor T03;
      W   := T07 xor T03;
      T11 := W   or T05;
      T12 := T09 and T11;
      T13 := X0  and y;
      T14 := T01 xor T05;
      X   := X1  xor T12;
      T16 := X1  or T13;
      Z   := T14 xor T16;
    elsif R = 4 then
      -- InvS4:   5  0  8  3 10  9  7 14  2 12 11  6  4 15 13  1
      -- depth = 6,4,7,3, Total gates=17
      T01 := X1  or X3;
      T02 := X2  or X3;
      T03 := X0  and T01;
      T04 := X1  xor T02;
      T05 := X2  xor X3;
      T06 :=     not T03;
      T07 := X0  and T04;
      X   := T05 xor T07;
      T09 := X   or T06;
      T10 := X0  xor T07;
      T11 := T01 xor T09;
      T12 := X3  xor T04;
      T13 := X2  or T10;
      Z   := T03 xor T12;
      T15 := X0  xor T04;
      Y   := T11 xor T13;
      W   := T15 xor T09;
    elsif R = 5 then
      -- InvS5:   8 15  2  9  4  1 13 14 11  6  5  3  7 12 10  0
      -- depth = 4,6,9,7, Total gates=17
      T01 := X0  and X3;
      T02 := X2  xor T01;
      T03 := X0  xor X3;
      T04 := X1  and T02;
      T05 := X0  and X2;
      W   := T03 xor T04;
      T07 := X0  and w;
      T08 := T01 xor w;
      T09 := X1  or T05;
      T10 :=     not X1;
      X   := T08 xor T09;
      T12 := T10 or T07;
      T13 := W   or x;
      Z   := T02 xor T12;
      T15 := T02 xor T13;
      T16 := X1  xor X3;
      Y   := T16 xor T15;
    elsif R = 6 then
      -- InvS6:  15 10  1 13  5  3  6  0  4  9 14  7  2 12  8 11
      -- depth = 5,3,8,6, Total gates=19
      T01 := X0  xor X2;
      T02 :=     not X2;
      T03 := X1  and T01;
      T04 := X1  or T02;
      T05 := X3  or T03;
      T06 := X1  xor X3;
      T07 := X0  and T04;
      T08 := X0  or T02;
      T09 := T07 xor T05;
      X   := T06 xor T08;
      W   :=     not T09;
      T12 := X1  and w;
      T13 := T01 and T05;
      T14 := T01 xor T12;
      T15 := T07 xor T13;
      T16 := X3  or T02;
      T17 := X0  xor x;
      Z   := T17 xor T15;
      Y   := T16 xor T14;
    elsif R = 7 then
      -- InvS7:   3  0  6 13  9 14 15  8  5 12 11  7 10  1  4  2
      -- depth := 9,7,3,3, Total gates:=18
      T01 := X0  and X1;
      T02 := X0  or X1;
      T03 := X2  or T01;
      T04 := X3  and T02;
      Z   := T03 xor T04;
      T06 := X1  xor T04;
      T07 := X3  xor z;
      T08 :=     not T07;
      T09 := T06 or T08;
      T10 := X1  xor X3;
      T11 := X0  or X3;
      X   := X0  xor T09;
      T13 := X2  xor T06;
      T14 := X2  and T11;
      T15 := X3  or x;
      T16 := T01 or T10;
      W   := T13 xor T15;
      Y   := T14 xor T16;
    end if;
    X0 := W;
    X1 := X;
    X2 := Y;
    X3 := Z;
  end SI;

  -- Linear Transform

  procedure Tr (X0, X1, X2, X3 : in out Unsigned_32) is
  begin
    X0 := Rotate_Left(X0, 13);
    X2 := Rotate_Left(X2, 3);
    X1 := X1 xor X0 xor X2;
    X3 := X3 xor X2 xor Shift_Left(X0, 3);
    X1 := Rotate_Left(X1, 1);
    X3 := Rotate_Left(X3, 7);
    X0 := X0 xor X1 xor X3;
    X2 := X2 xor X3 xor Shift_Left(X1, 7);
    X0 := Rotate_Left(X0, 5);
    X2 := Rotate_Left(X2, 22);
  end Tr;

  -- Inverse Linear Transform

  procedure TrI (X0, X1, X2, X3 : in out Unsigned_32) is
  begin
    X2 := Rotate_Right(X2, 22);
    X0 := Rotate_Right(X0, 5);
    X2 := X2 xor X3 xor Shift_Left(X1, 7);
    X0 := X0 xor X1 xor X3;
    X3 := Rotate_Right(X3, 7);
    X1 := Rotate_Right(X1, 1);
    X3 := X3 xor X2 xor Shift_Left(X0, 3);
    X1 := X1 xor X0 xor X2;
    X2 := Rotate_Right(X2, 3);
    X0 := Rotate_Right(X0, 13);
  end TrI;

  procedure Keying (W : Key_Schedule;
                    R : Integer;
       X0, X1, X2, X3 : in out Unsigned_32) is
  begin
    X0 := X0 xor W(4*R);
    X1 := X1 xor W(4*R+1);
    X2 := X2 xor W(4*R+2);
    X3 := X3 xor W(4*R+3);
  end Keying;

  pragma Inline(S, SI, Tr, TrI, Keying);

  procedure Prepare_Key (K : in Key; W : out Key_Schedule) is
  begin
    for I in 0..7 loop
      W(-8+I) := Bytes_To_Word(K(4*I .. 4*I+3));
    end loop;
    for I in 0..131 loop
      W(I) := Rotate_Left(W(I-8) xor W(I-5) xor W(I-3) xor W(I-1) xor
              16#9e3779b9# xor Unsigned_32(I), 11);
    end loop;
    S(3, W(  0), W(  1), W(  2), W(  3));
    S(2, W(  4), W(  5), W(  6), W(  7));
    S(1, W(  8), W(  9), W( 10), W( 11));
    S(0, W( 12), W( 13), W( 14), W( 15));
    S(7, W( 16), W( 17), W( 18), W( 19));
    S(6, W( 20), W( 21), W( 22), W( 23));
    S(5, W( 24), W( 25), W( 26), W( 27));
    S(4, W( 28), W( 29), W( 30), W( 31));
    S(3, W( 32), W( 33), W( 34), W( 35));
    S(2, W( 36), W( 37), W( 38), W( 39));
    S(1, W( 40), W( 41), W( 42), W( 43));
    S(0, W( 44), W( 45), W( 46), W( 47));
    S(7, W( 48), W( 49), W( 50), W( 51));
    S(6, W( 52), W( 53), W( 54), W( 55));
    S(5, W( 56), W( 57), W( 58), W( 59));
    S(4, W( 60), W( 61), W( 62), W( 63));
    S(3, W( 64), W( 65), W( 66), W( 67));
    S(2, W( 68), W( 69), W( 70), W( 71));
    S(1, W( 72), W( 73), W( 74), W( 75));
    S(0, W( 76), W( 77), W( 78), W( 79));
    S(7, W( 80), W( 81), W( 82), W( 83));
    S(6, W( 84), W( 85), W( 86), W( 87));
    S(5, W( 88), W( 89), W( 90), W( 91));
    S(4, W( 92), W( 93), W( 94), W( 95));
    S(3, W( 96), W( 97), W( 98), W( 99));
    S(2, W(100), W(101), W(102), W(103));
    S(1, W(104), W(105), W(106), W(107));
    S(0, W(108), W(109), W(110), W(111));
    S(7, W(112), W(113), W(114), W(115));
    S(6, W(116), W(117), W(118), W(119));
    S(5, W(120), W(121), W(122), W(123));
    S(4, W(124), W(125), W(126), W(127));
    S(3, W(128), W(129), W(130), W(131));
  end Prepare_Key;

  procedure Encrypt (W : in Key_Schedule; Plaintext  :  in Block;
            Ciphertext : out Block) is
    X0, X1, X2, X3 : Unsigned_32;
  begin
    X0 := Bytes_To_Word(Plaintext( 0 ..  3));
    X1 := Bytes_To_Word(Plaintext( 4 ..  7));
    X2 := Bytes_To_Word(Plaintext( 8 .. 11));
    X3 := Bytes_To_Word(Plaintext(12 .. 15));

    Keying(W,  0, X0, X1, X2, X3); S(0, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W,  1, X0, X1, X2, X3); S(1, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W,  2, X0, X1, X2, X3); S(2, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W,  3, X0, X1, X2, X3); S(3, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W,  4, X0, X1, X2, X3); S(4, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W,  5, X0, X1, X2, X3); S(5, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W,  6, X0, X1, X2, X3); S(6, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W,  7, X0, X1, X2, X3); S(7, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W,  8, X0, X1, X2, X3); S(0, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W,  9, X0, X1, X2, X3); S(1, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 10, X0, X1, X2, X3); S(2, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 11, X0, X1, X2, X3); S(3, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 12, X0, X1, X2, X3); S(4, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 13, X0, X1, X2, X3); S(5, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 14, X0, X1, X2, X3); S(6, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 15, X0, X1, X2, X3); S(7, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 16, X0, X1, X2, X3); S(0, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 17, X0, X1, X2, X3); S(1, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 18, X0, X1, X2, X3); S(2, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 19, X0, X1, X2, X3); S(3, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 20, X0, X1, X2, X3); S(4, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 21, X0, X1, X2, X3); S(5, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 22, X0, X1, X2, X3); S(6, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 23, X0, X1, X2, X3); S(7, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 24, X0, X1, X2, X3); S(0, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 25, X0, X1, X2, X3); S(1, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 26, X0, X1, X2, X3); S(2, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 27, X0, X1, X2, X3); S(3, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 28, X0, X1, X2, X3); S(4, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 29, X0, X1, X2, X3); S(5, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 30, X0, X1, X2, X3); S(6, X0, X1, X2, X3); Tr(X0, X1, X2, X3);
    Keying(W, 31, X0, X1, X2, X3);
    S(7, X0, X1, X2, X3);
    Keying(W, 32, X0, X1, X2, X3);

    Ciphertext( 0 ..  3) := Word_To_Bytes(X0);
    Ciphertext( 4 ..  7) := Word_To_Bytes(X1);
    Ciphertext( 8 .. 11) := Word_To_Bytes(X2);
    Ciphertext(12 .. 15) := Word_To_Bytes(X3);
  end Encrypt;

  procedure Decrypt (W : in Key_Schedule; Ciphertext :  in Block;
            Plaintext  : out Block) is
    X0, X1, X2, X3 : Unsigned_32;
  begin
    X0 := Bytes_To_Word(Ciphertext( 0 ..  3));
    X1 := Bytes_To_Word(Ciphertext( 4 ..  7));
    X2 := Bytes_To_Word(Ciphertext( 8 .. 11));
    X3 := Bytes_To_Word(Ciphertext(12 .. 15));

    Keying(W, 32, X0, X1, X2, X3);
    SI(7, X0, X1, X2, X3);
    Keying(W, 31, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(6, X0, X1, X2, X3); Keying(W,30, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(5, X0, X1, X2, X3); Keying(W,29, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(4, X0, X1, X2, X3); Keying(W,28, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(3, X0, X1, X2, X3); Keying(W,27, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(2, X0, X1, X2, X3); Keying(W,26, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(1, X0, X1, X2, X3); Keying(W,25, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(0, X0, X1, X2, X3); Keying(W,24, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(7, X0, X1, X2, X3); Keying(W,23, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(6, X0, X1, X2, X3); Keying(W,22, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(5, X0, X1, X2, X3); Keying(W,21, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(4, X0, X1, X2, X3); Keying(W,20, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(3, X0, X1, X2, X3); Keying(W,19, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(2, X0, X1, X2, X3); Keying(W,18, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(1, X0, X1, X2, X3); Keying(W,17, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(0, X0, X1, X2, X3); Keying(W,16, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(7, X0, X1, X2, X3); Keying(W,15, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(6, X0, X1, X2, X3); Keying(W,14, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(5, X0, X1, X2, X3); Keying(W,13, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(4, X0, X1, X2, X3); Keying(W,12, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(3, X0, X1, X2, X3); Keying(W,11, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(2, X0, X1, X2, X3); Keying(W,10, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(1, X0, X1, X2, X3); Keying(W, 9, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(0, X0, X1, X2, X3); Keying(W, 8, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(7, X0, X1, X2, X3); Keying(W, 7, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(6, X0, X1, X2, X3); Keying(W, 6, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(5, X0, X1, X2, X3); Keying(W, 5, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(4, X0, X1, X2, X3); Keying(W, 4, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(3, X0, X1, X2, X3); Keying(W, 3, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(2, X0, X1, X2, X3); Keying(W, 2, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(1, X0, X1, X2, X3); Keying(W, 1, X0, X1, X2, X3);
    TrI(X0, X1, X2, X3); SI(0, X0, X1, X2, X3); Keying(W, 0, X0, X1, X2, X3);

    Plaintext( 0 ..  3) := Word_To_Bytes(X0);
    Plaintext( 4 ..  7) := Word_To_Bytes(X1);
    Plaintext( 8 .. 11) := Word_To_Bytes(X2);
    Plaintext(12 .. 15) := Word_To_Bytes(X3);
  end Decrypt;

  procedure Selftest is
    K     : Key    := (others => 0);
    P     : Block  := (others => 0);
    P2, C : Block;
    W     : Key_Schedule;
  begin
    for I in 1 .. 128 loop
      Prepare_Key(K, W);
      Encrypt(W, P, C);
      Decrypt(W, C, P2);
      if (P2 /= P) then
        raise Implementation_Error;
      end if;
      P           := K( 0 .. 15);
      K( 0 .. 15) := K(16 .. 31);
      K(16 .. 31) := C;
    end loop;
    if C /= (16#A2#, 16#46#, 16#AB#, 16#69#, 16#0A#, 16#E6#, 16#8D#, 16#FB#,
             16#02#, 16#04#, 16#CB#, 16#E2#, 16#8E#, 16#D8#, 16#EB#, 16#7A#)
      then
      raise Implementation_Error;
    end if;
  end Selftest;

end SMG_Serpent;

The Selftest procedure above runs one single test for Serpent encrypt/decrypt, failing with an "Implementation_Error" if the result is not as expected. I added a bigger set of tests on all the test vectors and cases publicly available as part of the NESSIE project. The eucrypt/smg_serpent/tests/smg_serpent_tests.gpr can be used with gprbuild to compile the code for the automated tests:

 -- Tests for SMG_Serpent (part of EuCrypt)
 -- S.MG, 2018

project SMG_Serpent_Tests is
  for Source_Dirs use (".", "../src");
  for Object_Dir use "obj";
  for Exec_Dir use ".";

  for Main use ("testall.adb");
end SMG_Serpent_Tests;

EuCrypt/smg_serpent/tests/test_serpent.ads defines a package Test_Serpent that includes a procedure for running one single hard-coded test (similar to Selftest in Serpent's own code) and another procedure for running all tests on all test vectors and cases from a given file:

-- S.MG, 2018

with Ada.Strings.Fixed;
use Ada.Strings.Fixed;

package Test_Serpent is
	procedure test_from_file(filename: String);
	procedure test_one;
end Test_Serpent;

The implementation of those two testing procedures is in eucrypt/smg_serpent/tests/test_serpent.adb, where a lot of space is taken mainly by reading and interpreting the plain-text NESSIE format of the test vectors:

 -- S.MG, 2018
 -- Testing of Serpent implementation using Nessie-format test vectors

with SMG_Serpent; use SMG_Serpent;

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line; -- set exit status on fail
with Interfaces; use Interfaces; -- unsigned_8

package body Test_Serpent is
  Test_Fail : exception;  -- raised if a test fails

  procedure test_from_file (filename: String) is
    file     : FILE_TYPE;
    keylen   : constant := 256;
    blocklen : constant := 128;
    octets   : constant := 16;
    K        : Key;
    P, P2    : Block;	--plain text
    C, C2    : Block;	--cipher (encrypted) text
    times100 : Block;	--value after 100 iterations
    times1k  : Block;	--value after 1000 iterations
    W        : Key_Schedule;
    Test_No  : Positive := 1;
  begin
    begin
      open(file, In_File, filename);
      exception
      when others =>
        Put_Line(Standard_Error, "Can not open the file '" & filename &
                                 "'. Does it exist?");
        Set_Exit_Status(Failure);
        return;
    end;

    loop
      declare
        Line1      : String := Get_Line(file);
        Line2      : String := Line1;
        key1, key2 : String( 1..octets*2 );
        len        : Natural := 0;
      begin
        --check if this is test data of any known kind
        if index( Line1, "key=", 1 ) > 0 then
          Line2 := Get_Line( file );
          key1  := Tail( Line1, octets*2  );
          key2  := Tail( Line2, octets*2 );
          for jj in 1..octets loop
            K(jj-1) := Unsigned_8'Value("16#" &
                                        key1( ( jj - 1 ) * 2 + 1 .. jj * 2 ) &
                                        "#");
            K( jj + octets - 1 ) :=
                       Unsigned_8'Value("16#" &
                                        key2( ( jj - 1 ) * 2 + 1 .. jj * 2 ) &
                                        "#");
          end loop;

        elsif index( Line1, "plain=", 1 ) > 0 then
          key1 := Tail( Line1, octets * 2 );
          for jj in 1..octets loop
            P(jj-1) := Unsigned_8'Value("16#" &
                                        key1( ( jj - 1 ) * 2 + 1 .. jj * 2 ) &
                                        "#");
          end loop;
        elsif index( Line1, "cipher=", 1 ) > 0 then
          key1 := Tail( Line1, octets * 2 );
          for jj in 1..octets loop
            C(jj-1) := Unsigned_8'Value("16#" &
                                        key1( ( jj - 1 ) * 2 + 1 .. jj * 2) &
                                        "#");
          end loop;
        elsif index( Line1, "100 times=", 1 ) > 0 then
          key1 := Tail( Line1, octets * 2 );
          for jj in 1..octets loop
            times100(jj-1) :=
                       Unsigned_8'Value("16#" &
                                        key1( ( jj - 1 ) * 2 + 1 .. jj * 2 ) &
                                        "#");
          end loop;
        elsif index( Line1, "1000 times=", 1 ) > 0 then
          key1 := Tail( Line1, octets * 2 );
          for jj in 1..octets loop
            times1k(jj-1) :=
                       Unsigned_8'value("16#" &
                                        key1( ( jj - 1 ) * 2 + 1 .. jj * 2 ) &
                                        "#");
          end loop;
          --at this stage we should have ALL needed, so run test
          Put("-----Test " & Positive'Image(Test_No) & ": encryption...");
          Prepare_Key(K, W);
          Encrypt(W, P, C2);
          if C2 /= C then
            raise Test_Fail;
          else
            Put_Line("Passed-----");
          end if;
          Put("-----Test " & Positive'Image(Test_No) & ": decryption...");
          Decrypt(W, C2, P2);
          if P /= P2 then
            raise Test_Fail;
          else
            Put_Line("Passed-----");
          end if;

          Put("-----Test " & Positive'Image(Test_No) & ": 100 iterations...");
          for jj in 1 .. 100 loop
            Encrypt(W, P, C2);
            Decrypt(W, C2, P2);
            if (P2 /= P) then
              raise Test_Fail;
            end if;
            P := C2;
          end loop;
          Put_Line("Passed-----");

          Put("-----Test " & Positive'Image(Test_No) & ": 1000 iterations...");

          for jj in 1 .. 900 loop
            Encrypt(W, P, C2);
            Decrypt(W, C2, P2);
            if (P2 /= P) then
              raise Test_Fail;
            end if;
            P := C2;
          end loop;
          Put_Line("Passed-----");
          Test_No := Test_No + 1;
        end if;
        exit when End_Of_File(file);
      end;
    end loop;
    Close(file);
  end test_from_file;

  procedure test_one is
    K: Key;
    P, P2: Block;
    C: Block;
    W: Key_Schedule;
  begin
    K := (16#80#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#,
          16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#,
          16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#,
          16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#);

    P := (16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#,
          16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#, 16#00#);

    SMG_Serpent.Prepare_Key(K, W);
    Encrypt(W, P, C);
    if C /= (16#A2#, 16#23#, 16#AA#, 16#12#, 16#88#, 16#46#, 16#3C#, 16#0E#,
             16#2B#, 16#E3#, 16#8E#, 16#BD#, 16#82#, 16#56#, 16#16#, 16#C0#)
      then
      raise Test_Fail;
    end if;

    for I in 1 .. 100 loop
      Encrypt(W, P, C);
      Decrypt(W, C, P2);
      if (P2 /= P) then
        raise Test_Fail;
      end if;
      P := C;
    end loop;

    if C /= (16#73#, 16#9E#, 16#01#, 16#48#, 16#97#, 16#1F#, 16#D9#, 16#75#,
             16#B5#, 16#85#, 16#EA#, 16#FD#, 16#BD#, 16#65#, 16#9E#, 16#2C#)
      then
      raise Test_Fail;
    end if;

    for I in 1 .. 900 loop
      Encrypt(W, P, C);
      Decrypt(W, C, P2);
      if (P2 /= P) then
        raise Test_Fail;
      end if;
      P := C;
    end loop;

    if C /= (16#BE#, 16#FD#, 16#00#, 16#E0#, 16#D6#, 16#E2#, 16#7E#, 16#56#,
             16#95#, 16#1D#, 16#C6#, 16#61#, 16#44#, 16#40#, 16#D2#, 16#86#)
      then
      raise Test_Fail;
    else
      Put_Line("PASSED: test single case.");
    end if;

  end test_one;

end Test_Serpent;

To put everything together, the calls to the two testing procedures are in eucrypt/smg_serpent/tests/test_serpent.ads:

-- S.MG, 2018

with Ada.Strings.Fixed;
use Ada.Strings.Fixed;

package Test_Serpent is
	procedure test_from_file(filename: String);
	procedure test_one;
end Test_Serpent;

The test vectors from the NESSIE project are also provided in eucrypt/smg_serpent/tests/nessie_vectors.txt but I won't paste them here directly seeing how the file has 10309 lines.

For the actual .vpatch this time I even had a choice of vdiff tools to use, since phf conveniently just published the first part of his work on vtools. I can therefore happily report that his patches press fine and his resulting vdiff worked on this chapter's files all right as far as I can see. A comparison of the .vpatch obtained with the old vdiff vs the .vpatch obtained with phf's vdiff reveals (as phf has already noted) that there a differences only with respect to the order in which the files are considered. For the curious reader, here is the diff of the two vpatches, obtained with "diff eucrypt_ch11_serpent.vpatch test.vpatch" where test.vpatch is the result of running phf's vdiff while eucrypt_ch11_serpent.vpatch is the result of running the old vdiff: diff_order.txt. In the interest of consistency, I'll publish this .vpatch as obtained with the same .vdiff as all other EuCrypt vpatches, but as EuCrypt will soon end, I'll gladly move on to using phf's vdiff for future projects.

As usual, the .vpatch for this chapter, together with my signature for it can be found on my Reference Code Shelf as well as by following the direct links copied here for your convenience:

In the next chapter I'll finally bring everything together through a .vpatch that should (among other things) provide a way to compile the whole EuCrypt as one single aggregate library.


  1. You might call this "hashing" or "encryption/decryption" or even voodoo. After all this work on EuCrypt, I am increasingly drawn towards calling it bit byte diddling - fits better both the actual happenings and the level of proof as to what exactly is achieved. 

  2. I strongly recommend using Adacore's gprbuild as opposed to the gnu/gcc gprbuild.  

February 20, 2018

EuCrypt: Correcting an Error in OAEP Check

Filed under: EuCrypt — Diana Coman @ 3:56 p.m.

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

As I was (re)reading today through my own OAEP with Keccak code from the previous chapter, I found an error1. And while I take all the blame for it being there in the first place and for it remaining there for all of 5 days until today, I have to take also all the credit for finding it and fixing it. There is power in all the numbers, including one, apparently. Without further talk, let's see the error itself, in eucrypt/smg_keccak/smg_oaep.adb:

MaxLen       : constant Natural := OAEP_LENGTH_OCTETS - 11;

That MaxLen is the maximum length of plain text that can fit in a single OAEP block (hence, the maximum number of characters that can be encrypted with a single call to OAEP_Encrypt and at the same time the maximum number of characters that OAEP_Decrypt can ever return as decrypted message). It is used later on in the OAEP_Decrypt procedure to do a basic validity check on the "decrypted" message: if the length as read from the decrypted OAEP block is higher than this MaxLen then there is clearly something wrong (corrupt/invalid OAEP block most likely) and the procedure simply sets the success flag to "False" without any further operations (avoiding thus among other things attempts to read outside the bounds of the arrays involved for instance). The error in the above line is the use of OAEP_LENGTH_OCTETS which stands for the length in octets of the whole, encrypted OAEP block. The correct calculation has to be made either with OAEP_LENGTH_OCTETS/2 or more directly with the actual constant defined precisely for this purpose and otherwise used correctly at OAEP_Encrypt: OAEP_HALF_OCTETS. So the correct line is:

MaxLen       : constant Natural := OAEP_HALF_OCTETS - 11;

Before actually making that change in code however, the very first thing to do in such a case is a... test that exposes this type of issue. As current tests really are absolutely minimal, there can't be a better time than right now to add to them anyway so here are 2 additional tests that use OAEP_Decrypt on invalid input and, respectively, both encrypt and decrypt on an initial message longer than the maximum length (eucrypt/smg_keccak/tests/smg_keccak-test.adb):

    -- test decrypt on invalid (non-OAEP) string
    Flag := True;
    C := Encr( Encr'First );
    Encr( Encr'First ) := Character'Val( Character'Pos( C ) / 2 );
    Decr := ( others => ' ' );
    OAEP_Decrypt( Encr, Len, Decr, Flag );

    if Flag = True then
      Put_Line("FAILED: oaep test with invalid package");
    else
      Put_Line("PASSED: oaep test with invalid package");
    end if;

    -- test encrypt on message longer than maximum payload (1096 bits)
    Flag := False;
    Len := 0;
    LongMsg( 1..Msg'Length ) := Msg;
    Encr := ( others => '.' );
    OAEP_Encrypt( LongMsg, Entropy, Encr);
    OAEP_Decrypt( Encr, Len, Decr, Flag);

    if Flag = False or
       Len /= MaxLen * 8 or
       Decr( Decr'First .. Decr'First + Len / 8 - 1 ) /=
             LongMsg( LongMsg'First..LongMsg'First + MaxLen - 1 )
       then
      Put_Line("FAILED: oaep test with too long message");
      Put_Line("Msg is: "  & LongMsg);
      Put_Line("Decr is: " & Decr);
      Put_Line("Flag is: " & Boolean'Image( Flag ) );
      Put_Line("Len is: "  & Natural'Image( Len ) );
    else
      Put_Line("PASSED: oaep test with too long message");
    end if;

The first test basically messes one character from a valid OAEP-encrypted string and then tries to pass the result on to the OAEP_Decrypt procedure. The test fails if the success flag is set to true (since that means OAEP_Decrypt reports success on an invalid packet). The second test provides an initial plain-text message longer than the maximum length and checks that OAEP uses indeed only the first MaxLen bits ignoring the rest, as stated in the procedure's own description of behaviour (see the relevant comments in the code).

Running the above tests with the original code results, as expected, in trouble. However, having used the very reliable Ada language means that the code fails very clearly and obviously: as the length is greater than the available space, the boundary checks fail and the execution is aborted. Once the error is corrected, the code recompiled and the tests run again, the execution proceeds correctly and all the tests (new and old) pass as expected.

Digging a bit deeper into this reveals that there is scope for further improving the code itself to limit the opportunity for such mistake in the future: the MaxLen value is in fact a constant shared by all the OAEP procedures. Consequently, MaxLen should be MAX_LEN_MSG, defined right under OAEP_LENGTH_OCTETS and the others, together with its supporting "TMSR" string, like this:

  TMSR               : constant String := "TMSR-RSA";
  MAX_LEN_MSG        : constant := OAEP_HALF_OCTETS - TMSR'Length - 3;

And since I'm doing some refactoring essentially, I further take this chance to also remove a few prints (Put_Line in Ada's terms) from the oaep tests: the reason for removing them is that they are not of much help when the tests pass anyway (and if the tests fail you'll need to dig deeper than looking at those prints anyway) and moreover, they can mess up your console since they effectively print as characters the gobbledygook resulting from OAEP encrypt. When all is finished, it's time to run all tests again and make sure they all pass.

The .vpatch for all the above changes and refactoring (including updating comments as needed to reflect the removal of MaxLen and the new MAX_LEN_MSG), together with my signature for it will live like all the other EuCrypt .vpatches on my Reference Code Shelf. For your convenience, I link here as well the full .vpatch and my signature for it:

As a fitting ending to this unexpected but necessary post in the EuCrypt series, I'll just link here for comparison the story of the last time I had to correct an error as part of this EuCrypt series: the MPI error that survived for years and was finally uncovered hidden under the carpet. I'll let my readers compare the respective lives of the two errors and the three involved corrections. Meanwhile, I'll just get back to work on the next EuCrypt chapter that will be published as usual, on Thursday.


  1. No, not a "bug", not a "silly mistake" nor anything else but exactly what it is: an error. My error, too.  

Older Posts »

Theme and content by Diana Coman