~ This is a work in progress towards an Ada implementation of Eulora's communication protocol. Start with Chapter 1.~
This brings together RSA and OAEP as well as the raw representation as octets of all the things involved: true random padding, RSA key components, plain messages, encrypted outputs, OAEP blocks or Keccak hashes. The main goal was to have some clear and easy to use methods in Ada for doing the whole RSA+OAEP dance from start to finish and then back as needed. Due to the current situation re MPI and RSA, a significant part of this goal involves keeping the C swamp at bay: calling C methods only when there really is at the moment no alternative and in general as few times as possible, making sure that anything built on top of this part of smg_comms can effectively ignore that there is any C code involved at a lower level at all. The reason for insulating the C code away like this is of course that I hope to be able to get rid of it entirely at some point and have everything in Ada without having to touch anything higher up than this part right here. At some point, but not at this point here, not just yet.
At first glance, this chapter was meant to be quite easy and straightforward: glue the different parts together and that's it! As usual, the details were more gnarly and as a result there were a few things I had to change/re-implement in order to get this part into some sort of reasonable shape:
- I've added a few new subtypes of Octets to Raw_Types: RSA_len and RSA_half to hold a component of precisely the length or, respectively, half of the length set for the modulus of a RSA key. Those are needed to store the RSA keys in raw format aka arrays of octets. While I could also use the generic Octets type, the clear specification of length reflects the expected lengths of each RSA component and brings clarity so it's well worth it in my books.
- The random padding is now obtained directly from Ada code rather than via C_wrappers and truerandom.c. The new Ada code simply reads from a Fuckgoats that it expects to be available at a pre-set path, *already initialised*1 and ready to read from. This means that random octets do not require anymore a call to C and the corresponding conversions from array of octets to char * and back.
- The comparison between an OAEP block and the RSA key's modulus is simplified: it's only the top octet of both that is compared. This was discussed previously in the logs but at any rate, the reason for this approach is that it greatly reduces the complexity of the code since it's really a one line compare of 2 8-bit values that are already available as such at the very spot where they are needed! So no more calling the C swamp, converting to char* and then to MPIs and then comparing the full MPIs and returning the result through to Ada. The potential additional OAEP encryption performed at times is considered a price well worth paying in order to avoid the whole Ada->C->MPI->ADA dance that needs to be performed otherwise *each and every time*.
- The conversions from Ada's array of octets to C's char* are done in local code, octet by octet and strictly on arrays that are precisely the same size. I am aware that GNAT provides in principle methods for such conversions, namely To_Ada and To_C but they still fail to convince me that they provide a better service really. If you think they do though, feel free to state your case in the comments below.
- The OAEP implementation is the one from EuCrypt but with different access/interface methods: instead of accepting and producing Strings like the EuCrypt version, the OAEP package in SMG COMMS accepts and produces arrays of octets (i.e. Raw_Types.Octets type) since this is effectively what is needed here. Not to mention that I'm not particularly sad to get rid of Strings even when they are fixed size. Obviously, this means that I also replaced the conversions String <-> Bitstream with Octets <-> Bitstream. The rest however stays the same (including the underlying Keccak for hashes).
- Because of moving all the OAEP+RSA part to Ada and imposing only one-way calls from Ada to C, there was no need anymore for some of the wrappers that were provided in EuCrypt's OAEP and rsa_oaep code. Those were simply discarded so that the code you get by pressing this patch includes simply what is needed for SMG Comms and nothing more.
- Whenever the Ada code (specifically the rsa+oaep code) retrieves an array of octets from C, it will basically reverse the "normalization" that the MPI lib performs so that one doesn't end up in the idiotic situation where the retrieved value is actually different than the one given to MPI just because of leading zeroes. This is part of the price paid for using the MPI lib and at any rate it should not be the concern of any of the calling code so it's kept well separated so that any callers of RSA_OAEP.Encrypt or RSA_OAEP.Decrypt don't have to deal with it directly: the encryption uses at most msg_max_len octets and produces a fixed-size array while the decrypt similarly requires the same fixed-size array as input and will produce as output the actual length of the plain message and the plain message itself.
- The "No Implicit Conditionals" restriction for the whole smg comms had to be discarded because of the Keccak implementation - with the restriction applied, compilation of Keccak fails2. At the moment I don't consider this to be a big issue here but feel free to loudly disagree in the comments below - if you have a solid case, I can change my mind.
- I've updated the relevant tests for RSA (including those from the C_Wrappers package previously published) to use RSA keys of the new size that SMG Comms uses (as opposed to the EuCrypt size). I've added also new tests for the new RSA+OAEP Ada code, of course.
The new Ada code in rng.ads for reading random octets directly from an FG that is already initialised:
-- True Random octets generator reading from an FG and using Raw_Types. -- S.MG, 2018 with Ada.Sequential_IO; with Interfaces; with Raw_Types; package RNG is -- for reading from the FG one Octet at a time package Octet_IO is new Ada.Sequential_IO(Element_Type => Interfaces.Unsigned_8); -- path to the FG; NB: the FG has to be initialized prior to using SMG Comms! RNG_PATH: constant String := "/dev/ttyUSB0"; -- exception raised when FG is not accessible / read fails FG_Failure : exception; -- reads O'Length octets from the FG at RNG_PATH procedure Get_Octets( O: out Raw_Types.Octets ); end RNG;
And the corresponding rng.adb, short and sweet:
-- S.MG, 2018 with Ada.Exceptions; use Ada.Exceptions; package body RNG is procedure Get_Octets( O: out Raw_Types.Octets ) is F : Octet_IO.File_Type; begin begin Octet_IO.Open( File => F, Mode => Octet_IO.In_File, Name => RNG_PATH ); for I in O'Range loop Octet_IO.Read( F, O(I) ); end loop; Octet_IO.Close(F); exception when others => Raise_Exception(FG_Failure'Identity, "Failed to access default RNG source!"); end; end Get_Octets; end RNG;
The code for doing oaep+rsa encrypt from Ada, in rsa_oaep.ads:
-- Ada implementation of RSA with OAEP according to TMSR and Eulora spec -- Uses: -- - Eulora's raw types (Ada, raw_types.ads) -- - Keccak hashes (Ada, keccak.ads/adb) -- - OAEP schema (Ada, oaep.ads/adb) -- - RNG (Ada, rng.ads/adb) for true random padding -- - C wrappers lib (C, c_wrappers/) for: -- - MPI (C, mpi/) -- - RSA (C, rsa/rsa.c) -- -- S.MG, 2018 with Raw_Types; with Interfaces.C; use Interfaces.C; package RSA_OAEP is -- exception for mismatched lengths when converting octets <-> char arrays Mismatched_Lengths_Error: exception; -- public RSA key with n,e stored as raw octets type RSA_pkey is record n : Raw_Types.RSA_len; --public modulus e : Raw_Types.RSA_half; --public exponent end record; --RSA_pkey -- private (secret) RSA key with components stored as raw octets type RSA_skey is record n : Raw_Types.RSA_len; --public modulus e : Raw_Types.RSA_half; --public exponent d : Raw_Types.RSA_len; --secret exponent e*d=1 mod phi; phi=(p-1)*(q-1) p : Raw_Types.RSA_half; --prime p q : Raw_Types.RSA_half; --prime q u : Raw_Types.RSA_half; --inverse of p mod q; for faster calculations end record; --RSA_skey -- Encryption RSA+OAEP (i.e. using public key) -- NB: at most OAEP.MAX_LEN_MSG octets from Plain will be encrypted! -- Relies directly on: -- oaep.adb/ads -- c_wrappers.c ( RSA ) -- rng.adb/ads ( random padding ) procedure Encrypt( Plain: in Raw_Types.Octets; Key : in RSA_pkey; Encr : out Raw_Types.RSA_len ); -- Decryption RSA+OAEP (i.e. using private/secret key) -- The opposite of Encrypt above. -- NB: Plain has to have ENOUGH space for result! -- Result can be at most OAEP.MAX_LEN_MSG octets. -- Relies directly on: -- oaep.adb/ads -- c_wrappers.c (RSA) -- Plain_Len gives the length in OCTETS of the decrypted message. procedure Decrypt( Encr : in Raw_Types.RSA_len; Key : in RSA_skey; Plain : out Raw_Types.Octets; Plain_Len : out Natural; Success : out Boolean); --helper methods: -- mainly conversions to/from C's char* and imports from C_wrappers -- encrypt with public RSA key given as struct, Ada style -- NB: result is potentially 0-led (i.e. at end of Encr not at start!) procedure Public_RSA( Plain: in Raw_Types.Octets; Key : in RSA_pkey; Encr : out Raw_Types.RSA_len); -- encrypt with public RSA key given as char arrays, via C_Wrappers -- this returns the length of result because C functions trim leading 0s! function Public_RSA_C( Encr : out Interfaces.C.char_array; Encr_Len : in Integer; Plain : in Interfaces.C.char_array; Plain_Len : in Integer; RSA_N : in Interfaces.C.char_array; N_Len : in Integer; RSA_E : in Interfaces.C.char_array; E_Len : in Integer) return Integer; pragma Import(C, Public_RSA_C, "public_rsa_octets"); -- decrypt with private RSA key given as struct, Ada style -- NB: Plain has to have ENOUGH space! -- NB: Result is potentially 0-led (i.e. at the end of Plain, not at start!) -- @return actual length of result procedure Private_RSA( Encr : in Raw_Types.RSA_len; Key : in RSA_skey; Plain : out Raw_Types.Octets); -- encrypt with private/secret RSA key given as char arrays (via C_wrappers) -- this returns length because C methods trim leading 0s function Private_RSA_C( Plain : out Interfaces.C.char_array; Plain_Len : in Integer; Encr : in Interfaces.C.char_array; Encr_Len : in Integer; RSA_N : in Interfaces.C.char_array; N_Len : in Integer; RSA_E : in Interfaces.C.char_array; E_Len : in Integer; RSA_D : in Interfaces.C.char_array; D_Len : in Integer; RSA_P : in Interfaces.C.char_array; P_Len : in Integer; RSA_Q : in Interfaces.C.char_array; Q_Len : in Integer; RSA_U : in Interfaces.C.char_array; U_Len : in Integer) return Integer; pragma Import( C, Private_RSA_C, "private_rsa_octets" ); -- convert from Ada's Octets (array of octets) to C's char* (char_array) -- This copies the octets from O to the beginning of A -- NB: there are NO checks or memory allocations here! -- Caller has to make sure that: -- A has allocated space for at least O'Length octets! procedure Octets_To_Char_Array( O : in Raw_Types.Octets; A : out Interfaces.C.char_array); -- This copies first O'Length characters from A to O -- NB: this does NOT allocate /check memory! -- Caller has to ensure that: -- A has space at least O'Length characters procedure Char_Array_To_Octets( A : in Interfaces.C.char_array; O : out Raw_Types.Octets); end RSA_OAEP;
With the corresponding implementation in rsa_oaep.adb:
-- S.MG, 2018 with Interfaces; use Interfaces; with OAEP; with RNG; package body RSA_OAEP is -- OAEP + RSA with given public key. -- Steps: -- 1. repeat oaep.encrypt of Plain -- until the result has first octet < than first octet of Key's modulus. -- 2. RSA.encrypt with Key on the result from 1. procedure Encrypt( Plain: in Raw_Types.Octets; Key : in RSA_pkey; Encr : out Raw_Types.RSA_len ) is -- block obtained through OAEP padding of given input Plain Blk : OAEP.OAEP_Block := ( others => 0 ); Entropy : OAEP.OAEP_Block; Len : constant Natural := entropy'Length; begin -- loop cond: 1st octet of oaep block is < 1st octet of key's modulus loop -- get random bits via RNG (FG) RNG.Get_Octets( Entropy ); -- oaep encrypt OAEP.OAEP_Encrypt( Plain, Entropy, Blk ); -- check that the oaep block is suitable i.e. comparison of 1st octet if Blk(Blk'First) < Key.n(Key.n'First) then exit; end if; end loop; -- oaep block < modulus -- RSA encrypt Public_RSA( Blk, Key, Encr ); end; --Encrypt (OAEP+RSA) -- RSA+OAEP Decrypt -- Steps: -- 1. RSA Decrypt (secret key) -- 2. OAEP Decrypt procedure Decrypt( Encr : in Raw_Types.RSA_len; Key : in RSA_skey; Plain: out Raw_Types.Octets; Plain_Len: out Natural; Success: out Boolean) is Blk : OAEP.OAEP_Block; Msg : OAEP.OAEP_HALF; Msg_Len : Natural; begin -- RSA decrypt with provided secret key -- NB: result HAS TO BE AN OAEP BLOCK HERE! Even if - potentially - 0 led! Private_RSA( Encr, Key, Blk ); -- OAEP decrypt OAEP.OAEP_Decrypt(Blk, Msg_Len, Msg, Success); -- switch to Length in OCTETS! Msg_Len := Msg_Len / 8; -- check that result FITS in given output array - otherwise, fail if Msg_Len > Plain'Length then Success := FALSE; Plain_Len := Msg_Len; --to give a clue to caller else Plain_Len := Msg_Len; Plain( Plain'First .. Plain'First + Plain_Len-1) := Msg( Msg'First .. Msg'First + Plain_Len -1 ); end if; end Decrypt; -- helper methods -- encrypt with public RSA key given as struct, Ada style procedure Public_RSA( Plain: in Raw_Types.Octets; Key : in RSA_pkey; Encr : out Raw_Types.RSA_len) is Encr_char : char_array( size_t(Encr'First) .. size_t(Encr'Last)); Plain_char : char_array( size_t(Plain'First) .. size_t(Plain'Last)); N_char : char_array( size_t(Key.n'First) .. size_t(Key.n'Last)); E_char : char_array( size_t(Key.e'First) .. size_t(Key.e'Last)); out_len : Integer; begin -- convert to char array Octets_To_Char_Array( Plain, Plain_char ); Octets_To_Char_Array( Key.n, N_char ); Octets_To_Char_Array( Key.e, E_char ); -- call C imported function out_len := Public_RSA_C( Encr_char , Encr'Length, Plain_char , Plain'Length, N_char , Key.n'Length, E_char , Key.e'Length); -- convert back to octets Char_Array_To_Octets( Encr_char, Encr ); -- C code trims leading 0s -> need to move octets if out_len0); end if; -- no need to return anything! end Public_RSA; procedure Private_RSA( Encr : in Raw_Types.RSA_len; Key : in RSA_skey; Plain : out Raw_Types.Octets) is Plain_Char : char_array( size_t(Plain'First) .. size_t(Plain'Last) ); Plain_Len : Integer; Encr_Char : char_array( size_t(Encr'First) .. size_t(Encr'Last) ); N_Char : char_array( size_t(Key.n'First) .. size_t(Key.n'Last) ); E_Char : char_array( size_t(Key.e'First) .. size_t(Key.e'Last) ); D_Char : char_array( size_t(Key.d'First) .. size_t(Key.d'Last) ); P_Char : char_array( size_t(Key.p'First) .. size_t(Key.p'Last) ); Q_Char : char_array( size_t(Key.q'First) .. size_t(Key.q'Last) ); U_Char : char_array( size_t(Key.u'First) .. size_t(Key.u'Last) ); begin -- convert key and encrypted message to C char_arrays Octets_To_Char_Array( Encr, Encr_Char ); Octets_To_Char_Array( Key.n, N_Char ); Octets_To_Char_Array( Key.e, E_Char ); Octets_To_Char_Array( Key.d, D_Char ); Octets_To_Char_Array( Key.p, P_Char ); Octets_To_Char_Array( Key.q, Q_Char ); Octets_To_Char_Array( Key.u, U_Char ); -- call RSA decrypt via C_Wrappers Plain_Len := Private_RSA_C( Plain_Char, Plain'Length, Encr_Char , Encr'Length, N_Char , Key.n'Length, E_Char , Key.e'Length, D_Char , Key.d'Length, P_Char , Key.p'Length, Q_Char , Key.q'Length, U_Char , Key.u'Length); -- convert result back to Octets Char_Array_To_Octets( Plain_Char, Plain ); -- if length < OAEP_Block'Length,it's 0-led and got trimmed, so move it if Plain_Len < Plain'Length then Plain(Plain'Last-Plain_Len+1..Plain'Last):= Plain(Plain'First .. Plain'First + Plain_Len -1); Plain(Plain'First .. Plain'Last-Plain_Len) := (others => 0); end if; -- no need to return anything! end Private_RSA; procedure Octets_To_Char_Array( O : in Raw_Types.Octets; A : out Interfaces.C.char_array) is begin -- check that lengths ARE THE SAME! if A'Length /= O'Length then raise Mismatched_Lengths_Error; end if; -- copy values over octet by octet for I in 0 .. O'Length-1 loop A( A'First + Interfaces.C.size_t( I )) := Interfaces.C.Char( Character'Val(O(O'First + I))); end loop; end Octets_To_Char_Array; procedure Char_Array_To_Octets( A : in Interfaces.C.char_array; O : out Raw_Types.Octets) is begin -- check that lengths ARE THE SAME! if A'Length /= O'Length then raise Mismatched_Lengths_Error; end if; -- copy values over octet by octet for I in 0..O'Length -1 loop O( O'First + I ) := Character'Pos( Character( A( A'First + Interfaces.C.size_t( I )) )); end loop; end Char_Array_To_Octets; end RSA_OAEP;
The new oaep.ads using Raw_Types.Octets:
-- Implementation of TMSR's OAEP with Keccak as hash function -- NB: this uses Eulora's protocol constants (esp. RSA key length) and types. -- -- S.MG, 2018 with Keccak; -- Keccak is used as hash function with Raw_Types; -- Eulora's protocol raw types and constant values with Interfaces; use Interfaces; -- for Unsigned_8 type and bit-level ops package OAEP is pragma Pure( OAEP ); -- stateless, no side effects -> can cache calls -- constants for OAEP OAEP_LENGTH_OCTETS : constant := Raw_Types.RSA_KEY_OCTETS; OAEP_LENGTH_BITS : constant := OAEP_LENGTH_OCTETS * 8; OAEP_HALF_OCTETS : constant := OAEP_LENGTH_OCTETS / 2; TMSR_STR : constant String := "TMSR-RSA"; -- "TMSR-RSA" as unsigned_8 values: TMSR : constant Raw_Types.Octets := (84,77,83,82,45,82,83,65); MAX_LEN_MSG : constant Natural := OAEP_HALF_OCTETS - TMSR_STR'Length - 3; -- subtypes for OAEP encrypt/decrypt subtype OAEP_Block is Raw_Types.Octets( 1 .. OAEP_LENGTH_OCTETS ); subtype OAEP_HALF is Raw_Types.Octets( 1 .. OAEP_HALF_OCTETS ); -- padding & formatting of maximum MAX_LEN_MSG octets of the given input -- uses TMSR's OAEP schema: -- 1.format M00 as: [random octet][sz1][sz2]"TMSR-RSA"[random]*Message -- where sz1 and sz2 store the length of the message in bits -- the random octets before message are padding to make OAEP_LENGTH_OCTETS -- 2. R = OAEP_HALF_OCTETS random bits -- 3. X = M00 xor hash(R) -- 4. Y = R xor hash(X) -- 5. Result is X || Y -- NB: the Entropy parameter should be random octets from which this method -- will use as many as required for the OAEP encryption of given Msg -- NB: at MOST MAX_LEN_MSG octets of Msg! (Msg at most MAX_LEN_MSG*8 bits!) procedure OAEP_Encrypt( Msg : in Raw_Types.Octets; Entropy : in OAEP_Block; Output : out OAEP_Block); -- This is the opposite of OAEP_Encrypt above. -- @param Encr - an OAEP block previously obtained from OAEP_Encrypt -- @param Len - this will hold the length of the obtained message (in bits!) -- @param Output - the first Len octets of this are the recovered message -- @param Success - set to TRUE if message was recovered, false otherwise -- NB: when Success is FALSE, both Len and Output have undefined values procedure OAEP_Decrypt( Encr : in OAEP_Block; Len : out Natural; Output : out OAEP_HALF; Success : out Boolean); private -- gnat-specific methods for bit-level operations function Shift_Right( Value : Unsigned_8; Amount : Natural ) return Unsigned_8; pragma Import(Intrinsic, Shift_Right); function Shift_Left( Value : Unsigned_8; Amount : Natural ) return Unsigned_8; pragma Import(Intrinsic, Shift_Left); -- helper method: xor 2 half-oaep blocks function XOR_Octets(A : in OAEP_HALF; B : in OAEP_HALF) return OAEP_HALF; -- conversions between bitstream and string -- NB: caller has to ensure correct size of output parameter! no checks here. procedure ToOctets ( B : in Keccak.Bitstream; O : out Raw_Types.Octets ); procedure ToBitstream( O : in Raw_Types.Octets; B : out Keccak.Bitstream ); -- wrapper for Sponge to use Octets for input/output procedure HashKeccak( Input : in Raw_Types.Octets; Output : out Raw_Types.Octets; Block_Len : in Keccak.Keccak_Rate := Keccak.Default_Bitrate); end OAEP;
And the implementation in oaep.adb:
-- S.MG, 2018 package body OAEP is -- padding & formatting of maximum MAX_LEN_MSG*8 bits of the given input -- uses TMSR's OAEP schema: -- 1.format M00 as: [random octet][sz1][sz2]"TMSR-RSA"[random]*Message -- where sz1 and sz2 store the length of the message in bits -- the random octets before message are padding to make OAEP_LENGTH_OCTETS -- 2. R = OAEP_HALF_OCTETS random bits -- 3. X = M00 xor hash(R) -- 4. Y = R xor hash(X) -- 5. Result is X || Y -- NB: the Entropy parameter should be random octets from which this method -- will use as many as required for the OAEP encryption of given Msg -- NB: at MOST MAX_LEN_MSG octets of Msg! (Msg at most 1960 bits) procedure OAEP_Encrypt( Msg : in Raw_Types.Octets; Entropy : in OAEP_Block; Output : out OAEP_Block) is M00 : OAEP_HALF; R : OAEP_HALF; HashR : OAEP_HALF; X : OAEP_HALF; HashX : OAEP_HALF; Y : OAEP_HALF; MsgLen : Natural; PadLen : Natural; begin -- calculate maximum length of msg and needed amount of padding -- make sure also that only MAX_LEN_MSG octets at most are used from Msg MsgLen := Msg'Length; -- real msg length if MsgLen > MAX_LEN_MSG then MsgLen := MAX_LEN_MSG; --only first MAX_LEN_MSG octets used PadLen := 0; --no padding needed else PadLen := MAX_LEN_MSG - MsgLen; -- add padding as needed end if; -- step 1: header and format to obtain M00 -- first octet is random bits M00( M00'First ) := Entropy( Entropy'First ); -- next 2 octets hold the used length of Msg (number of octets) M00( M00'First + 2) := Unsigned_8( ( MsgLen * 8 ) mod 256 ); M00( M00'First + 1) := Unsigned_8( ( (MsgLen * 8 ) / 256 ) mod 256 ); -- next 8 octets are reserved for later use, currently "TMSR-RSA" M00( M00'First + 3 .. M00'First + 10 ) := TMSR; -- random bits for padding, if Msg is less than maximum length for I in 1 .. PadLen loop M00( M00'First + 10 + I ) := Entropy( Entropy'First + I ); end loop; -- the message itself M00( M00'Last - MsgLen + 1 .. M00'Last ) := Msg( Msg'First .. Msg'First + MsgLen - 1 ); -- step 2: R = Raw_Types.OAEP_HALF_OCTETS random octets -- can take LAST octets from given entropy as they are NOT used before -- (even if original message was empty, padding uses at most half - 10 -- while entropy has full block length) R := Entropy( Entropy'Last - OAEP_HALF_OCTETS + 1 .. Entropy'Last ); -- step 3: X = M00 xor hash(R) HashKeccak( R, HashR ); X := XOR_Octets(M00, HashR); -- step 4: Y = R xor hash(X) HashKeccak( X, HashX ); Y := XOR_Octets(R, HashX); -- step 5: Output is X || Y Output( Output'First .. Output'First + X'Length - 1 ) := X; Output( Output'Last - Y'Length + 1 .. Output'Last ) := Y; end OAEP_Encrypt; procedure OAEP_Decrypt( Encr : in OAEP_Block; Len : out Natural; Output : out OAEP_HALF; Success : out Boolean ) is X, Y, M, R : OAEP_HALF; HashX, HashR : OAEP_HALF; LenOctets : Natural; begin -- step 1: separate X and Y X := Encr( Encr'First .. Encr'First + X'Length - 1 ); Y := Encr( Encr'Last - Y'Length + 1 .. Encr'Last ); -- step 2: R = Y xor hash(X) HashKeccak( X, HashX ); R := XOR_Octets(Y, HashX); -- step 3: M = X xor hash(R) HashKeccak( R, HashR ); M := XOR_Octets(X, HashR); -- step 4: extract length and message Len := Natural(M( M'First + 1 )) * 256 + Natural(M( M'First + 2 )); LenOctets := Len / 8; if LenOctets > MAX_LEN_MSG or LenOctets < 0 then Success := False; -- error, failed to retrieve message else Success := True; Output( Output'First .. Output'First + LenOctets - 1 ) := M( M'Last - LenOctets + 1 .. M'Last ); end if; end OAEP_Decrypt; -- private, helper methods procedure HashKeccak(Input : in Raw_Types.Octets; Output : out Raw_Types.Octets; Block_Len : in Keccak.Keccak_Rate := Keccak.Default_Bitrate) is BIn : Keccak.Bitstream( 0 .. Input'Length * 8 - 1 ); BOut : Keccak.Bitstream( 0 .. Output'Length * 8 - 1 ); begin ToBitstream( Input, BIn ); Keccak.Sponge( BIn, BOut, Block_Len ); ToOctets( BOut, Output ); end HashKeccak; function XOR_Octets(A : in OAEP_HALF; B : in OAEP_HALF) return OAEP_HALF is R : OAEP_HALF; begin for I in R'Range loop R(I) := A(I) xor B(I); end loop; return R; end XOR_Octets; -- conversion between types procedure ToOctets(B: in Keccak.Bitstream; O: out Raw_Types.Octets ) is Pos : Natural; begin Pos := B'First; for I in O'Range loop O(I) := Unsigned_8( B( Pos ) ) + Unsigned_8( B( Pos + 1 ) ) * 2 + Unsigned_8( B( Pos + 2 ) ) * 4 + Unsigned_8( B( Pos + 3 ) ) * 8 + Unsigned_8( B( Pos + 4 ) ) * 16 + Unsigned_8( B( Pos + 5 ) ) * 32 + Unsigned_8( B( Pos + 6 ) ) * 64 + Unsigned_8( B( Pos + 7 ) ) * 128; Pos := Pos + 8; end loop; end ToOctets; procedure ToBitstream(O: in Raw_Types.Octets; B: out Keccak.Bitstream ) is V : Unsigned_8; Pos : Natural; begin Pos := B'First; for I in O'Range loop V := O( I ); B( Pos ) := Keccak.Bit( V and 1 ); B( Pos + 1 ) := Keccak.Bit( Shift_Right( V, 1 ) and 1 ); B( Pos + 2 ) := Keccak.Bit( Shift_Right( V, 2 ) and 1 ); B( Pos + 3 ) := Keccak.Bit( Shift_Right( V, 3 ) and 1 ); B( Pos + 4 ) := Keccak.Bit( Shift_Right( V, 4 ) and 1 ); B( Pos + 5 ) := Keccak.Bit( Shift_Right( V, 5 ) and 1 ); B( Pos + 6 ) := Keccak.Bit( Shift_Right( V, 6 ) and 1 ); B( Pos + 7 ) := Keccak.Bit( Shift_Right( V, 7 ) and 1 ); Pos := Pos + 8; end loop; end ToBitstream; end OAEP;
The new tests added to tests/test_rsa_oaep.adb and called from tests/testall.adb, basically calling naked rsa from Ada, OAEP by itself and then rsa+oaep on a few different inputs:
--S.MG, 2018 with Interfaces; use Interfaces; with Interfaces.C; use Interfaces.C; with RSA_OAEP; use RSA_OAEP; with OAEP; use OAEP; with Raw_Types; use Raw_Types; with RNG; use RNG; with Keccak; use Keccak; with Ada.Text_IO; use Ada.Text_IO; package body Test_RSA_OAEP is procedure test_char_array is S : String := OAEP.TMSR_STR; O : Octets := OAEP.TMSR; A : char_array(0..O'Length-1) := (others => '0'); B : Octets(0..O'Length -1) := (others => 0); Fail : Boolean := FALSE; begin Octets_To_Char_Array(O, A); Char_Array_To_Octets(A, B); if B /= O then Put_Line("FAIL: char_array_to_octets"); else Put_Line("PASS: char_array_to_octets"); end if; for I in 0..S'Length-1 loop declare C : Character := Character(A(A'First + size_t(I))); E : Character := S(S'First + I); begin if C /= E then Fail := TRUE; Put("Error at pos " & Integer'Image(I) & ": "); Put(Integer'Image(Character'Pos(C))); Put_Line(" instead of " & Integer'Image(Character'Pos(E))); end if; end; end loop; if FAIL then Put_Line("FAIL: test octets_to_char_array"); else Put_Line("PASS: test octets_to_char_array"); end if; end test_char_array; -- test OAEP encrypt + decrypt procedure test_oaep is Plain: Octets(1..MAX_LEN_MSG); Short: Octets(0..10); Encr : OAEP_Block; Decr : OAEP_HALF; Len : Natural; Entropy: OAEP_Block; Success : Boolean; begin RNG.Get_Octets(Plain); RNG.Get_Octets(Entropy); RNG.Get_Octets(Short); -- test full length message OAEP_Encrypt(Plain, Entropy, Encr); OAEP_Decrypt(Encr, Len, Decr, Success); if not Success or Len/8 /= Plain'Length then Put_Line("FAIL: oaep encrypt/decrypt on max len message."); else if Decr(Decr'First..Decr'First+Len/8-1) /= Plain(Plain'First..Plain'First+Len/8-1) then Put_Line("FAIL: oaep encrypt/decrypt on max len message - " & "result different from expected."); else Put_Line("PASS: oaep encrypt/decrypt on max len message."); end if; end if; -- test short message OAEP_Encrypt(Short, Entropy, Encr); OAEP_Decrypt(Encr, Len, Decr, Success); if not Success or Len/8 /= Short'Length then Put_Line("FAIL: oaep encrypt/decrypt on short message."); else if Decr(Decr'First..Decr'First+Len/8-1) /= Short(Short'First..Short'First+Len/8-1) then Put_Line("FAIL: oaep encrypt/decrypt on short message - " & "result different from expected."); else Put_Line("PASS: oaep encrypt/decrypt on short message."); end if; end if; end test_oaep; -- test JUST RSA (i.e. without oaep) with RSA key pair previously generated procedure test_rsa is n: String := "C6579F8646180EED0DC1F02E0DDD2B43EABB3F702D79D9928E2CDA5E1D42DF5D9ED7773F80B1F8D9B0DB7D4D00F55647640D70768F63D3CED56A39C681D08D6191F318BB79DC969B470A7364D53335C8318EF35E39D5DF706AB6F2393C6DD2128C142DBAB1806EB35E26C908F0A48419313D2D0F33DD430655DBFEC722899EC21C238E8DB7003430BBC39BAD990F9887F6B03E1344F537EC97389B78DBC656718ACD7B0FDC13DD24534F417BC7A18F077A0C4227354CEA19670331B6CAA3DFC17BBA7E70C14510D9EB3B63F3014994EC87BD23E868C0AE6E9EC55027577F62C0280B2D7DD1135001844923E5455C4566E066B3FDE968C6BC4DC672F229FCE366440403D7A4F4A8BFBA5679B7D0844BA1231277D13A77C9E2B5A1CB138C1B7AB5B4D4832448723A3DE70ED2E86D5FC5174F949A02DE8E404304BEB95F9BF40F3AA3CA15622D2776294BE7E19233406FF563CB8C25A1CB5AADBC1899DA3F2AE38533931FE032EE3232C2CD4F219FADF95B91635C0762A476A4DE5013F4384093F0FB715028D97F93B2E6F057B99EE344D83ADF2686FD5C9C793928BEF3182E568C4339C36C744C8E9CA7D4B9A16AA039CBF6F38CC97B12D87644E94C9DBD6BC93A93A03ED61ECC5874586E3A310E958F858735E30019D345C62E5127B80652C8A970A14B31F03B3A157CD5"; e: String := "F74D78E382FC19B064411C6C20E0FDB2985F843007A54C7D8400BB459468624126E7D175F397E55C57AF25858EAE2D2952FB7998C119A6103606733EB5E1D27FCA1FACF14ADE94101D383D1B25DA511805569BC344EAD384EDBF3F3A541B34887FE199D99D7F62E6E9D516F88D6F5AD3E020DF04D402A02CC628A0064362FE8516CF7CD6040E9521407AB90EE6B5AFFF9EA9EBB16A7D3407CE81FD3844F519880556AB94AB349C1F3BBB6FDB4C4B377FE4C091EBDC2C3A1BD3AA56382D8D80E7742B5C751008FD6ECDD2EC3B2E3B6C566F698ED672000B403766DD63C3ACBDE16A14FB02E83A2EB6AA018BFC0020401E790DEE24E9"; d: String := "698DA05DA25B230211EEF0CBA12083A1457B749A11937AC9993859F69A3BF38D575E5166AF2EC88D77F1DF04E68AEA358EACF7659FD4722A4F5A1C8BA7676DA97A9FBA75451152F8F68887D3451A9CCFFFE9EB80979786E37495B17687A6212F77FA616E4C0CD8A8EB7AEB88EA6CCABB7F3E854FB94B35394A09F95F0D6F997947E865CC0606F437C30FE8C48D96FBF5E2F52807BC9E9ED7BBEB23D5C45EDDCD16FE2BF410A9A1E5EF879E71C0D41FAE270C0C5D442860103F8C3944E802F33DB38432F11F763A7AF593656108E4A98A44A8549913CE5DCEC1A6500F280E3190991B2B938561CFACD8BC5183AAC9A4914BFE52C3BE39BB83688E1DE52479107EF8E087DCDB409432FC954C6349407E81DDFB11AE92BABB32A31868597958C9C76E0B4156F380955F0E09C1F3B98BB4CDD59E1B5C7D8CC2AA7491B0D319D219CF459A527CE1AA2729DEC53269653BF0ED3E0253F4451168437E3B069E48350CA4C3EC82134E87135624C768D1330B0D70C6E447FD9945BF06FCB91AA334C0FD8EEF1ADBC15928B3DB62077B537F7E9F468CC95CD5AAFEAE1F760A863B48D07B163F670E2E5B550BB3E960230BA9FDAED9903AE2E669A7F3C4D1F1E25B8E8EDB8CC6E6FD2164E66F4E64ED77BEF1EC9E6CEA5624FD84C0680248746DC1C8187145F3CD2411659DAEAD11D"; p: String := "CDD6F7673A501FB24C44D56CA1D434F6CB3334E193E02F8E906241906BCB7412DD2159825B24C22002F373E647C2DA62A854F3841C00FD5985D03227CA9B54A69380BA9D63BE738BDF9E65C247E43E1220EEDD9281DCA78B32A4E1B786B7697ED0C3195D5AF2990881B11D6FC9EC9F940067B2DEA2A516FAA5F269C98F0B67628A6D2708515A4A58041AA17A93E4C4DD95C85BC38351DDA1DCF3DFD91C505B22383132649CF9F9233852C7207075BCF43C71038F043F1EC53E9787FB051B7927D020903233C16897B993C8089D8464451F086E756CF20E46CE6ED4A6AC5C327A0AAFBECBAAFD177969E7C952C76A4F4E7C85BF7F63"; q: String := "F6ACF0790A250802C8D45DAC549CDBEF7806D5877A5DF0069136A458FAC4F0B0858060A873DA6355A965A064A0BC1BBB874872CD7ED89674AD407533041E74BCA317EC73597D335115523F61A05071E5ED81EE2A05331F65D4DC7A25AD7938B124CF03F49154B6693FB0B598B33ABDEF85C599A57A9B7347EAFF82638E1CBC28FCDFFF1FF04A18C2DBF3938395C2F8D1782B43D3A25EF7633B5DDAC89EFD3BAA64D976425A0891E00B876E9DE9FE4B6492B0EA8DFC7C8DEEC61721356EC816295B1BD9CD9DA3E30D2D90DC9CB3987F4BE042104900E036F3044A016749EF910CCFB9F377A90849B4CCCF4471A74E67EF6C814C9467"; u: String := "854B89ED10F52258D00D6B3FA7F1FD22752804668F51FF7806DB82E22CB8B3AA8448D9B8E9DB14D31A36AEC2BCFA89E341B7334D494E97ED8051244136192233332C4612D963E7B6AF2535FDB7FE97E28DDFEBDFB3E1AFC29D05DBDF37106A817D3AB1864C7F7F247982897EDA6A92BED47D9C68305CD170C7301ACEB05F8A6382E73CC7614B2D8D758669B3A99AB64114809254B0BE21F40341A5B48B9B032603B14875B87EB5E16603FD16552E146A0FC6964958DFC25AA9FFCCD1ED1F4DEAF9FBAA0D7357F5FF0803FEB9BA78E74AC6B3070F417CEC6CFC7A3CF1E305FC7B76B7ED71893999AF797B2EBDE41FE90F076CCEDBFB"; Plain: OAEP_Block := (others => 0); Decr : OAEP_Block := (others => 0); Encr : RSA_len; pkey: RSA_pkey; skey: RSA_skey; begin -- initialize with RSA pair previously generated Hex2Octets( n, skey.n ); Hex2Octets( e, skey.e ); Hex2Octets( d, skey.d ); Hex2Octets( p, skey.p ); Hex2Octets( q, skey.q ); Hex2Octets( u, skey.u ); -- copy n and e for public key pkey.n := skey.n; pkey.e := skey.e; -- get random data RNG.Get_Octets(Plain); -- make first octet < RSA key's modulus first octet Plain(Plain'First) := 16#00#; -- naked rsa encrypt/decrypt Put_Line("Encrypting with RSA public key..."); Public_RSA( Plain, pkey, Encr ); Put_Line("Decrypting with RSA private key..."); Private_RSA( Encr, skey, Decr ); Put_Line("Checking..."); -- check result if Decr /= Plain then Put_Line("FAIL: RSA encrypt/decrypt result doesn't match plain."); else Put_Line("PASS: RSA encrypt/decrypt"); end if; end test_rsa; -- test rsa+oaep with RSA key pair previously generated procedure test_rsa_oaep is n: String := "C6579F8646180EED0DC1F02E0DDD2B43EABB3F702D79D9928E2CDA5E1D42DF5D9ED7773F80B1F8D9B0DB7D4D00F55647640D70768F63D3CED56A39C681D08D6191F318BB79DC969B470A7364D53335C8318EF35E39D5DF706AB6F2393C6DD2128C142DBAB1806EB35E26C908F0A48419313D2D0F33DD430655DBFEC722899EC21C238E8DB7003430BBC39BAD990F9887F6B03E1344F537EC97389B78DBC656718ACD7B0FDC13DD24534F417BC7A18F077A0C4227354CEA19670331B6CAA3DFC17BBA7E70C14510D9EB3B63F3014994EC87BD23E868C0AE6E9EC55027577F62C0280B2D7DD1135001844923E5455C4566E066B3FDE968C6BC4DC672F229FCE366440403D7A4F4A8BFBA5679B7D0844BA1231277D13A77C9E2B5A1CB138C1B7AB5B4D4832448723A3DE70ED2E86D5FC5174F949A02DE8E404304BEB95F9BF40F3AA3CA15622D2776294BE7E19233406FF563CB8C25A1CB5AADBC1899DA3F2AE38533931FE032EE3232C2CD4F219FADF95B91635C0762A476A4DE5013F4384093F0FB715028D97F93B2E6F057B99EE344D83ADF2686FD5C9C793928BEF3182E568C4339C36C744C8E9CA7D4B9A16AA039CBF6F38CC97B12D87644E94C9DBD6BC93A93A03ED61ECC5874586E3A310E958F858735E30019D345C62E5127B80652C8A970A14B31F03B3A157CD5"; e: String := "F74D78E382FC19B064411C6C20E0FDB2985F843007A54C7D8400BB459468624126E7D175F397E55C57AF25858EAE2D2952FB7998C119A6103606733EB5E1D27FCA1FACF14ADE94101D383D1B25DA511805569BC344EAD384EDBF3F3A541B34887FE199D99D7F62E6E9D516F88D6F5AD3E020DF04D402A02CC628A0064362FE8516CF7CD6040E9521407AB90EE6B5AFFF9EA9EBB16A7D3407CE81FD3844F519880556AB94AB349C1F3BBB6FDB4C4B377FE4C091EBDC2C3A1BD3AA56382D8D80E7742B5C751008FD6ECDD2EC3B2E3B6C566F698ED672000B403766DD63C3ACBDE16A14FB02E83A2EB6AA018BFC0020401E790DEE24E9"; d: String := "698DA05DA25B230211EEF0CBA12083A1457B749A11937AC9993859F69A3BF38D575E5166AF2EC88D77F1DF04E68AEA358EACF7659FD4722A4F5A1C8BA7676DA97A9FBA75451152F8F68887D3451A9CCFFFE9EB80979786E37495B17687A6212F77FA616E4C0CD8A8EB7AEB88EA6CCABB7F3E854FB94B35394A09F95F0D6F997947E865CC0606F437C30FE8C48D96FBF5E2F52807BC9E9ED7BBEB23D5C45EDDCD16FE2BF410A9A1E5EF879E71C0D41FAE270C0C5D442860103F8C3944E802F33DB38432F11F763A7AF593656108E4A98A44A8549913CE5DCEC1A6500F280E3190991B2B938561CFACD8BC5183AAC9A4914BFE52C3BE39BB83688E1DE52479107EF8E087DCDB409432FC954C6349407E81DDFB11AE92BABB32A31868597958C9C76E0B4156F380955F0E09C1F3B98BB4CDD59E1B5C7D8CC2AA7491B0D319D219CF459A527CE1AA2729DEC53269653BF0ED3E0253F4451168437E3B069E48350CA4C3EC82134E87135624C768D1330B0D70C6E447FD9945BF06FCB91AA334C0FD8EEF1ADBC15928B3DB62077B537F7E9F468CC95CD5AAFEAE1F760A863B48D07B163F670E2E5B550BB3E960230BA9FDAED9903AE2E669A7F3C4D1F1E25B8E8EDB8CC6E6FD2164E66F4E64ED77BEF1EC9E6CEA5624FD84C0680248746DC1C8187145F3CD2411659DAEAD11D"; p: String := "CDD6F7673A501FB24C44D56CA1D434F6CB3334E193E02F8E906241906BCB7412DD2159825B24C22002F373E647C2DA62A854F3841C00FD5985D03227CA9B54A69380BA9D63BE738BDF9E65C247E43E1220EEDD9281DCA78B32A4E1B786B7697ED0C3195D5AF2990881B11D6FC9EC9F940067B2DEA2A516FAA5F269C98F0B67628A6D2708515A4A58041AA17A93E4C4DD95C85BC38351DDA1DCF3DFD91C505B22383132649CF9F9233852C7207075BCF43C71038F043F1EC53E9787FB051B7927D020903233C16897B993C8089D8464451F086E756CF20E46CE6ED4A6AC5C327A0AAFBECBAAFD177969E7C952C76A4F4E7C85BF7F63"; q: String := "F6ACF0790A250802C8D45DAC549CDBEF7806D5877A5DF0069136A458FAC4F0B0858060A873DA6355A965A064A0BC1BBB874872CD7ED89674AD407533041E74BCA317EC73597D335115523F61A05071E5ED81EE2A05331F65D4DC7A25AD7938B124CF03F49154B6693FB0B598B33ABDEF85C599A57A9B7347EAFF82638E1CBC28FCDFFF1FF04A18C2DBF3938395C2F8D1782B43D3A25EF7633B5DDAC89EFD3BAA64D976425A0891E00B876E9DE9FE4B6492B0EA8DFC7C8DEEC61721356EC816295B1BD9CD9DA3E30D2D90DC9CB3987F4BE042104900E036F3044A016749EF910CCFB9F377A90849B4CCCF4471A74E67EF6C814C9467"; u: String := "854B89ED10F52258D00D6B3FA7F1FD22752804668F51FF7806DB82E22CB8B3AA8448D9B8E9DB14D31A36AEC2BCFA89E341B7334D494E97ED8051244136192233332C4612D963E7B6AF2535FDB7FE97E28DDFEBDFB3E1AFC29D05DBDF37106A817D3AB1864C7F7F247982897EDA6A92BED47D9C68305CD170C7301ACEB05F8A6382E73CC7614B2D8D758669B3A99AB64114809254B0BE21F40341A5B48B9B032603B14875B87EB5E16603FD16552E146A0FC6964958DFC25AA9FFCCD1ED1F4DEAF9FBAA0D7357F5FF0803FEB9BA78E74AC6B3070F417CEC6CFC7A3CF1E305FC7B76B7ED71893999AF797B2EBDE41FE90F076CCEDBFB"; Plain: Octets(1..MAX_LEN_MSG) := (others=>20); Short: Octets(1..10); Decr : RSA_len; Encr : RSA_len; pkey: RSA_pkey; skey: RSA_skey; Success: Boolean; Len : Natural; begin -- initialize with RSA pair previously generated Hex2Octets( n, skey.n ); Hex2Octets( e, skey.e ); Hex2Octets( d, skey.d ); Hex2Octets( p, skey.p ); Hex2Octets( q, skey.q ); Hex2Octets( u, skey.u ); -- copy n and e for public key pkey.n := skey.n; pkey.e := skey.e; -- test with 0 message of length Plain'Length RSA_OAEP.Encrypt(Plain, pkey, Encr); RSA_OAEP.Decrypt(Encr, skey, Decr, Len, Success); if (not Success) or Len /= Plain'Length or Plain /= Decr(Decr'First..Decr'First+Plain'Length-1) then Put_Line("FAIL: RSA_OAEP on max len message 20-filled."); else Put_Line("PASS: RSA_OAEP on max len message 20-filled."); end if; -- get random data for "plain" message RNG.Get_Octets(Plain); RSA_OAEP.Encrypt(Plain, pkey, Encr); RSA_OAEP.Decrypt(Encr, skey, Decr, Len, Success); if (not Success) or Len /= Plain'Length or Plain /= Decr(Decr'First..Decr'First+Plain'Length-1) then Put_Line("FAIL: RSA_OAEP on random data of max length."); else Put_Line("PASS: RSA_OAEP on random data of max length."); end if; -- get random data for "short" message RNG.Get_Octets(Short); RSA_OAEP.Encrypt(Short, pkey, Encr); RSA_OAEP.Decrypt(Encr, skey, Decr, Len, Success); if (not Success) or Len /= Short'Length or Short /= Decr(Decr'First..Decr'First+Short'Length-1) then Put_Line("FAIL: RSA_OAEP on random data of short length."); else Put_Line("PASS: RSA_OAEP on random data of short length."); end if; end test_rsa_oaep; -- helper methods procedure Hex2Octets( Hex: in String; O: out Raw_Types.Octets ) is S : String := "16#AA#"; -- to make sure that input String has EVEN number of chars (ie full octets) H : String(1..Hex'Length+Hex'Length mod 2) := (others=>'0'); begin -- first char is 0 if needed to cover full octet... H(H'Length-Hex'Length+1..H'Length) := Hex; O := (others => 0); for I in 0 .. H'Length/2-1 loop S := "16#" & H(H'First + I*2 .. H'First + I*2 + 1) & "#"; O(O'Last - H'Length/2 + 1 + I) := Unsigned_8'Value(S); end loop; end Hex2Octets; procedure PrintOctets( O: in Raw_Types.Octets; Title: in String ) is begin Put_Line(Title); for V of O loop Put(Unsigned_8'Image(V) & " "); end loop; New_Line; end PrintOctets; end Test_RSA_OAEP;
The full .vpatch for this chapter is quite long at 1714 lines but a significant part of that is essentially imported from EuCrypt (Keccak most notably but also the main part of OAEP). As usual, the .vpatch and my signature for it can be found on my Reference Code Shelf or directly from the links below:
Yes, I adopted Stanislav's approach to reading from the FG. I also had a look at his code at www.loper-os.org/?p=2175 ↩
The exact line is this one: http://www.ossasepia.com/2018/01/18/eucrypt-chapter-6-keccak-transformations/#selection-159.735-159.812 and it's been pointed out already (in the logs, where else?) that there is an implicit check against div by 0 which could be avoided simply by rewriting the line as Output(X, Y) := Rotate_Left(Input(X,Y), ( (T+1)*(T+2)/2) and (Z_Length -1) ); ↩
Comments feed: RSS 2.0
[...] on the new convenient way to simply call rsa+oaep encrypt/decrypt from Ada, this chapter provides the Pack/Unpack functions that convert between SMG Comms RSA packets (each [...]
[...] 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 [...]
[...] server have been communicating with one another happily and uninterruptedly - as well as actually RSA-authenticatedly and Serpent-encryptedly, generating, exchanging and automatically burning keys as [...]