diff -uNr a/eucrypt/smg_keccak/smg_keccak.adb b/eucrypt/smg_keccak/smg_keccak.adb --- a/eucrypt/smg_keccak/smg_keccak.adb 589991a191e8d120f9338fd279017cad6c04aaace3045c9fcf337ac156c7df9aae8d8d353fa32c08439d005a8cb30d309e957eb26d5a1cb8bea2d4fadd44c8c6 +++ b/eucrypt/smg_keccak/smg_keccak.adb 88e40423c88ba2ac7d44225b388794d61719746b02412e2dae4684bcfa72399978d599f9301b4a2be101b41769d3c5b20de6ff94e76a01ff767edc00746b8b96 @@ -5,8 +5,8 @@ -- public function, sponge procedure Sponge( Input : in Bitstream; - Block_Len : in Keccak_Rate; - Output : out Bitstream) is + Output : out Bitstream; + Block_Len : in Keccak_Rate := Default_Bitrate ) is Internal : State := (others => (others => 0)); begin --absorb input into sponge in a loop on available blocks, including padding diff -uNr a/eucrypt/smg_keccak/smg_keccak.ads b/eucrypt/smg_keccak/smg_keccak.ads --- a/eucrypt/smg_keccak/smg_keccak.ads 63a6a9f8168017265c7ccf9b3d7dffd7c3cd21f529ae88993c3b05e0dcaff9530c68b3420b58f643ff2425f575c66e9e0df19ed25c8a38f56a0d1e713e27b15c +++ b/eucrypt/smg_keccak/smg_keccak.ads 7503d06b8f87f1cd8a4246a7baf27ba9431646e65f07fea64173f24852be71dc493c3e866d5c16f6723f80c6e6ed1479a46fac9dea5fdf02b0387724bdb99e08 @@ -13,6 +13,9 @@ --therefore keccak function 1600 with current --constants (5*5*2^6) + Default_Bitrate: constant := 1344; --max bits the sponge can eat/spit without + --needing to scramble the state + --constants: dimensions of keccak state and number of rounds XY_Length: constant := 5; Z_Length: constant := 2**Keccak_L; @@ -49,12 +52,12 @@ -- public function, the sponge itself -- Keccak sponge structure using Keccak_Function, Pad and a given bitrate; -- Input - the stream of bits to hash (the message) + -- Output - a bitstream of desired size for holding output -- Block_Len - the bitrate to use; this is effectively the block length -- for splitting Input AND squeezing output between scrambles - -- Output - a bitstream of desired size for holding output procedure Sponge(Input : in Bitstream; - Block_Len : in Keccak_Rate; - Output : out Bitstream); + Output : out Bitstream; + Block_Len : in Keccak_Rate := Default_Bitrate ); private -- these are internals of the keccak implementation, not meant to be directly diff -uNr a/eucrypt/smg_keccak/smg_oaep.adb b/eucrypt/smg_keccak/smg_oaep.adb --- a/eucrypt/smg_keccak/smg_oaep.adb false +++ b/eucrypt/smg_keccak/smg_oaep.adb 399c9451d688077b5f5701c05032a4f39b0cf3fae9cb841123f23680c0c2d2dce5ba5e1f7322b9158642380d1f9ef948a44f7a425de699965d8706323883cff7 @@ -0,0 +1,202 @@ +-- S.MG, 2018 + +package body SMG_OAEP is + + procedure HashKeccak( Input : in String; + Output : out String; + Block_Len : in Keccak_Rate := Default_Bitrate) is + BIn : Bitstream( 0 .. Input'Length * 8 - 1 ); + BOut : Bitstream( 0 .. Output'Length * 8 - 1 ); + begin + ToBitstream( Input, BIn); + Sponge( BIn, BOut, Block_Len); + ToString( BOut, Output ); + end HashKeccak; + + function Hash( Input : Interfaces.C.Char_Array; + LenIn : Interfaces.C.size_t; + LenOut : Interfaces.C.size_t; + Block_Len : Interfaces.C.int := Default_Bitrate) + return Interfaces.C.Char_Array is + AdaLenIn : Natural := Natural(LenIn); + AdaLenOut : Natural := Natural(LenOut); + InStr : String( 0 .. AdaLenIn-1 ) := (others => '0'); + OutStr : String( 0 .. AdaLenOut-1 ) := (others => '0'); + COut : Interfaces.C.Char_Array( 0 .. LenOut-1 ); + Count : Natural := AdaLenOut; + CCount : Interfaces.C.size_t := LenOut; + begin + Interfaces.C.To_Ada( Input, InStr, AdaLenIn ); + HashKeccak( InStr, OutStr, Keccak_Rate(Block_Len) ); + Interfaces.C.To_C( OutStr, COut, CCount ); + return COut; + end Hash; + + -- conversion between types + procedure ToString(B: in Bitstream; S: out String ) is + N : Natural; + Pos : Natural; + begin + Pos := B'First; + for I in S'Range loop + N := Natural( B( Pos ) ) + + Natural( B( Pos + 1 ) ) * 2 + + Natural( B( Pos + 2 ) ) * 4 + + Natural( B( Pos + 3 ) ) * 8 + + Natural( B( Pos + 4 ) ) * 16 + + Natural( B( Pos + 5 ) ) * 32 + + Natural( B( Pos + 6 ) ) * 64 + + Natural( B( Pos + 7 ) ) * 128; + Pos := Pos + 8; + S( I ) := Character'Val( N ); + end loop; + end ToString; + + procedure ToBitstream(S: in String; B: out Bitstream ) is + V : Unsigned_8; + Pos : Natural; + begin + Pos := B'First; + for C of S loop + V := Character'Pos( C ); + B( Pos ) := Bit( V and 1 ); + B( Pos + 1 ) := Bit( Shift_Right( V, 1 ) and 1 ); + B( Pos + 2 ) := Bit( Shift_Right( V, 2 ) and 1 ); + B( Pos + 3 ) := Bit( Shift_Right( V, 3 ) and 1 ); + B( Pos + 4 ) := Bit( Shift_Right( V, 4 ) and 1 ); + B( Pos + 5 ) := Bit( Shift_Right( V, 5 ) and 1 ); + B( Pos + 6 ) := Bit( Shift_Right( V, 6 ) and 1 ); + B( Pos + 7 ) := Bit( Shift_Right( V, 7 ) and 1 ); + + Pos := Pos + 8; + end loop; + end ToBitstream; + + -- padding & formatting of maximum 1960 bits of the given String + -- 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 OAEP_LENGTH_OCTETS - 11 octets of Msg! (Msg at most 1960 bits) + procedure OAEP_Encrypt( Msg : in String; + 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; + MaxLen : Natural; + PadLen : Natural; + TMSR : constant String := "TMSR-RSA"; + begin + -- calculate maximum length of msg and needed amount of padding + -- make sure also that only MaxLen octets at most are used from Msg + MaxLen := OAEP_HALF_OCTETS - TMSR'Length - 3; -- maximum msg that fits + MsgLen := Msg'Length; -- real msg length + if MsgLen > MaxLen then + MsgLen := MaxLen; --only first MaxLen octets will be considered + PadLen := 0; --no padding needed + else + PadLen := MaxLen - MsgLen; -- msg is potentially too short, add padding + 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) := Character'Val( ( MsgLen * 8 ) mod 255 ); + M00( M00'First + 1) := Character'Val( ( (MsgLen * 8 ) / 255 ) mod 255 ); + + -- 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 245 octets + 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 = 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 ); + XOR_Strings( M00, HashR, X ); + + -- step 4: Y = R xor hash(X) + HashKeccak( X, HashX ); + XOR_Strings( R, HashX, Y ); + + -- 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; + MaxLen : constant Natural := OAEP_LENGTH_OCTETS - 11; + 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 ); + XOR_Strings( Y, HashX, R ); + + -- step 3: M = X xor hash(R) + HashKeccak( R, HashR ); + XOR_Strings( X, HashR, M ); + + -- step 4: extract length and message + Len := Character'Pos( M( M'First + 1 ) ) * 255 + + Character'Pos( M( M'First + 2 ) ); + LenOctets := Len / 8; + + if LenOctets > MaxLen 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; + + -- helper method, xor on strings + -- NB: only Output'Length bits will be considered from S1 and S2 + -- NB: caller is responsible for S1 and S2 being long enough! + procedure XOR_Strings( S1: in String; S2: in String; Output: out String ) is + V1, V2: Unsigned_8; + begin + for I in Output'Range loop + V1 := Character'Pos( S1( I ) ); + V2 := Character'Pos( S2( I ) ); + Output( I ) := Character'Val( V1 xor V2 ); + end loop; + end XOR_Strings; + + +end SMG_OAEP; diff -uNr a/eucrypt/smg_keccak/smg_oaep.ads b/eucrypt/smg_keccak/smg_oaep.ads --- a/eucrypt/smg_keccak/smg_oaep.ads false +++ b/eucrypt/smg_keccak/smg_oaep.ads ed6fc57f63def71e7c286f1e9115264412611ad19e925cb0ede29c3bda6cd40f6f63cce6cdc9ecc2c7f55e555292d7eba058fbcf4c9a49af24cab43b8345f253 @@ -0,0 +1,90 @@ +-- Implementation of TMSR's OAEP with Keccak as hash function +-- +-- S.MG, 2018 + +with SMG_Keccak; use SMG_Keccak; -- Keccak is used as hash function +with Interfaces; use Interfaces; -- for Unsigned_8 type and bit-level ops +with Interfaces.C; use Interfaces.C; -- for interop with C + +package SMG_OAEP is + pragma Pure( SMG_OAEP ); -- stateless, no side effects -> can cache calls + + -- fixed length of OAEP block in bits and in octets + OAEP_LENGTH_BITS : constant := 4096; + OAEP_LENGTH_OCTETS : constant := 512; + OAEP_HALF_OCTETS : constant := OAEP_LENGTH_OCTETS / 2; + + -- subtypes used by the OAEP encrypt/decrypt + subtype OAEP_Block is String( 1 .. OAEP_LENGTH_OCTETS ); + subtype OAEP_HALF is String( 1 .. OAEP_HALF_OCTETS ); + + -- padding & formatting of maximum 1960 bits of the given String + -- 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 OAEP_LENGTH_OCTETS - 11 octets of Msg! (Msg at most 1960 bits) + procedure OAEP_Encrypt( Msg : in String; + 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); + + -- helper method, xor on strings + -- NB: only Output'Length bits will be considered from S1 and S2 + -- NB: caller is responsible for S1 and S2 being long enough! + procedure XOR_Strings( S1: in String; S2: in String; Output: out String ); + + -- 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); + + -- conversions between bitstream and string + -- NB: caller has to ensure correct size of output parameter! no checks here. + procedure ToString( B: in Bitstream; S: out String ); + procedure ToBitstream( S: in String; B: out Bitstream ); + + -- public wrapper for Sponge to use String for input/output + procedure HashKeccak( Input : in String; + Output : out String; + Block_Len : in Keccak_Rate := Default_Bitrate); + + -- wrapper for calling from C + -- @param Input the input string, as array of characters (C style) + -- @param LenIn the length of the input string (as number of BITS) + -- @param LenOut the desired number of bits to be returned as output + -- @param Block_Len the bitrate used by the Keccak sponge (number of BITS) + -- @return an array of characters with first LenOut bits set to Keccak output + + -- NB: caller HAS TO provide the length of the Input (parameter LenIn) + -- NB: caller HAS TO provide the length of the Output (parameter LenOut) + function Hash( Input : Interfaces.C.Char_Array; + LenIn : Interfaces.C.size_t; + LenOut : Interfaces.C.size_t; + Block_Len : Interfaces.C.int := Default_Bitrate) + return Interfaces.C.Char_Array; + pragma Export( C, Hash, "hash" ); + +end SMG_OAEP; diff -uNr a/eucrypt/smg_keccak/tests/smg_keccak-test.adb b/eucrypt/smg_keccak/tests/smg_keccak-test.adb --- a/eucrypt/smg_keccak/tests/smg_keccak-test.adb f90807132cf1ad922f6b901a4cd35190646baad554e0cbae71c570909af0025260ded9bd163657917af4b08f5b29aeb704076d4f5949d8f6b1d14f60c837800b +++ b/eucrypt/smg_keccak/tests/smg_keccak-test.adb b9dd611c05352bc2dcd237ee59409cbbabf02068d8639cbde73b6f273ea1480bb9c40cab85f83fd7d56ce6cd59bbbe42f171ce90bd1aaad265109b745fabb808 @@ -1,3 +1,4 @@ +with SMG_OAEP; use SMG_OAEP; with SMG_Keccak; use SMG_Keccak; with Ada.Exceptions; use Ada.Exceptions; with Ada.Text_IO; use Ada.Text_IO; @@ -268,7 +269,7 @@ HexString : String( 1 .. ExpHex'Length ); begin Put_Line("---sponge test---"); - Sponge(Input, Bitrate, Output); + Sponge(Input, Output, Bitrate); Put_Line("Input is:"); for I of Input loop Put(Bit'Image(I)); @@ -314,6 +315,123 @@ end if; end test_keccak_function; + procedure test_bitstream_conversion is + S: String := "Aa*/"; + E: Bitstream( 0 .. 31 ) := (1, 0, 0, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 1, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 0, + 1, 1, 1, 1, 0, 1, 0, 0); + B: Bitstream( 0 .. 31 ); + SS: String := " t "; + begin + Put_Line("---Testing string to bitstream conversion---"); + ToBitstream( S, B ); + if E /= B then + Put_Line("FAILED: string to bitstream conversion."); + else + Put_Line("PASSED: string to bitstream conversion."); + end if; + + Put_Line("---Testing bitstream to string conversion---"); + ToString( B, SS ); + if SS /= S then + Put_Line("FAILED: bitstream to string conversion"); + Put_Line("EXPECTED: " & S); + Put_Line("OUTPUT: " & SS); + else + Put_Line("PASSED: bitstream to string conversion"); + end if; + end test_bitstream_conversion; + + procedure test_hash_keccak is + S: String := "X"; + O: String := "abc"; + B: Bitstream( 0 .. 23 ); + BB: Bitstream( 1.. 8):= (0, 0, 0, 1, 1, 0, 1, 0); + Exp: Bitstream( 0 .. 23 ) := (1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 0, 0, 0, 1, 0, + 1, 1, 1, 0, 0, 0, 1, 1); + begin + Put_Line("----Testing hash keccak on string " & S & "----"); + HashKeccak(S, O); + Put_Line("OUTPUT: " & O); + ToBitstream( O, B ); + if B /= Exp then + Put_Line("FAILED: testing hash keccak on string"); + Put_Line("Output:"); + for I of B loop + Put( Bit'Image( I ) ); + end loop; + new_line(1); + Put_Line("Expected:"); + for I of Exp loop + Put( Bit'Image( I ) ); + end loop; + else + Put_Line("PASSED: testing hash keccak on string"); + end if; + new_line(1); + end test_hash_keccak; + + procedure test_xor_strings is + S1 : String := "ABC"; + S2 : String := "CBA"; + Exp : String := "..."; + Result : String := "..."; + begin + Exp( Exp'First ) := Character'Val( 2 ); + Exp( Exp'First + 1 ) := Character'Val( 0 ); + Exp( Exp'First + 2 ) := Character'Val( 2 ); + + Put_Line("----Testing xor on strings---"); + XOR_Strings( S1, S2, Result); + Put_Line("S1 is " & S1); + Put_Line("S2 is " & S2); + Put_Line("S1 xor S2 is " & Result); + Put_Line("Result is: "); + for C of Result loop + Put( Natural'Image( Character'Pos( C ) ) ); + end loop; + new_line(1); + + if Result /= Exp then + Put_Line("FAILED: xor on strings"); + else + Put_Line("PASSED: xor on strings"); + end if; + end test_xor_strings; + + procedure test_oaep is + Msg : String := "abcdefghij jihgfedcba123456789"; + Encr : OAEP_Block := ( others => ' ' ); + Decr : OAEP_HALF := ( others => ' ' ); + Entropy : OAEP_Block := ( others => 'e' ); + Len : Natural; + Flag : Boolean; + begin + Put_Line("----Testing OAEP Encrypt----"); + OAEP_Encrypt( Msg, Entropy, Encr ); + + Put_Line("----Testing OAEP Decrypt----"); + OAEP_Decrypt( Encr, Len, Decr, Flag ); + + Put_Line("Msg is: " & Msg); + Put_Line("Encr is: " & Encr); + Put_Line("Decr is: " & Decr); + Put_Line("Flag is: " & Boolean'Image( Flag ) ); + Put_Line("Len is: " & Natural'Image( Len ) ); + + if Flag = False or + Len /= Msg'Length * 8 or + Decr( Decr'First .. Decr'First + Msg'Length - 1 ) /= Msg + then + Put_Line("FAILED: oaep test"); + else + Put_Line("PASSED: oaep test"); + end if; + + end test_oaep; + -- end of helper methods --variables @@ -354,4 +472,16 @@ -- test flipping octets test_flip; + -- test bitstream conversion + test_bitstream_conversion; + + -- test hash keccak (strings version) + test_hash_keccak; + + -- test oaep encrypt + decrypt + test_oaep; + + -- test xor on strings + test_xor_strings; + end SMG_Keccak.Test;