Since the client data model includes Keccak hashes for files received from Eulora's server and the files themselves may be of any size whatsoever, it follows that both client and server have to be able to use EuCrypt's Keccak sponge sequentially too - basically feeding it data in chunks as opposed to all in one go, as a single input (be it bitstream or bytestream).1 So I got my Ada reference book out on the desk again and it turns out that it's not even all that difficult really - even though this addition has to be yet another package on top of the existing ones in EuCrypt, mainly because such type of use breaks by definition the stateless guarantee provided by the "single input" use and in turn, this would then propagate to all code that uses the Keccak package (and that's the encryption scheme, mainly). Therefore, rather than forcing now stateful code everywhere just because there's a need to calculate hashes for files on the disk, I simply provide this file-hashing as a separate package using the same underlying sponge. Quite as it should even be, I would say: there is only one sponge implementation but there are now two options to using it for hashing, namely a stateless one for data that is held entirely in memory (the one that existed already) and a stateful one for data that is fed sequentially (my new code). As this is now implemented, tested and integrated into the respective parts on both client and server, I'd rather take the time and write it down as well, to unload it and have it all in one place.
The approach here is very straightforward: keep a sponge's state locally, read from the input file blocks as big as the sponge can handle in one go (and add if needed padding to the last block), pass each block as soon as read on to the sponge, scramble the state and repeat until the whole file has been processed; then squeeze out of the sponge a block and return its first 8 octets given that the hash is meant to be that size. Ada's Sequential_IO package simply needs to be provided with the type of element to read and then it works like any other file input/output, without any trouble. In principle, the most effective implementation would be to read a whole block (ie as many octets as the sponge can absorb in one go) each time but this means that one has to handle at file reading time the special case of the last block that may be incomplete. For now at least I preferred to sidestep this and I went instead for the cheap and angry solution that seems however perfectly adequate for current needs: simply read a file octet by octet, so that there is no special case at all. Here's the code that does it all:
-- for reading files one Octet at a time package Octet_IO is new Ada.Sequential_IO(Element_Type => Interfaces.Unsigned_8); function Hash_File(Filename: in String; Hash: out Raw_Types.Octets_8) return Boolean is F: Octet_IO.File_Type; S: Keccak.State := (others => (others => 0)); Block_Len: Keccak.Keccak_Rate := Keccak.Default_Byterate; Block: Keccak.Bytestream(1..Block_Len); Pos: Keccak.Keccak_Rate; begin Octet_IO.Open(F, Octet_IO.In_File, Filename); -- check that this is not an empty file as hashing of that is nonsense if Octet_IO.End_Of_File(F) then Octet_IO.Close(F); -- close it before returning! return False; end if; -- read from file and absorb into the sponge while not Octet_IO.End_Of_File(F) loop -- read block by block Pos := 1; while Pos <= Block_Len and (not Octet_IO.End_Of_File(F)) loop Octet_IO.Read(F, Block(Pos)); Pos := Pos + 1; end loop; -- single block loop -- if it's an incomplete block, it needs padding if Pos <= Block_Len then -- pad it with 10*1 Block(Pos..Block'Last) := (others => 0); Block(Pos) := 1; Block(Block'Last) := Block(Block'Last) + 16#80#; end if; -- here the block is complete, padded if needed. -- absorb it into the state Keccak.AbsorbBlock( Block, S); -- scramble state S := Keccak.Keccak_Function( S ); end loop; -- full file loop Octet_IO.Close(F); -- now squeeze a block and get the 8 octets required Keccak.SqueezeBlock( Block, S); Hash := Block(1..8); -- if it got here, all is well, return true. return True; exception when others => Octet_IO.Close(F); return False; end Hash_File;
Note that the above returns the *raw* Keccak hash, meaning the direct output of the sponge, as a set of octets. This is normally fine and well but if one specifies the hash as "unsigned 64", it means that the above set of octets has to be interpreted as a number - and in turn, this means that byte/bit order matters. Since this can and does create confusion quite easily, I'll state it here plainly again: the raw output of the Keccak sponge is MSB/b, meaning that on a little endian machine, you'll need to flip both bytes and bits if you want to get the exact same number as you would on a big endian machine! Since this is however something that I already sorted out before, I added to the above convenient wrappers to do this properly so that the whole code should work seamlessly on both big and little endian computers anyway:
function Hash_File(Filename: in String; Hash: out Interfaces.Unsigned_64) return Boolean is Raw: Raw_Types.Octets_8; begin -- calculate the raw hash and then convert it if Hash_File(Filename, Raw) then Hash := Hash2Val(Raw); return True; else return False; end if; end Hash_File; function Hash2Val( Raw: in Raw_Types.Octets_8 ) return Interfaces.Unsigned_64 is B8: Raw_Types.Octets_8; U64: Interfaces.Unsigned_64; begin -- convert to U64 (NB: no need to squeeze etc, as block_len has to be > 8) -- for this to remain consistent on both little and big endian machines: -- on little endian, octets and bits need to be flipped before conversion if Default_Bit_Order = Low_Order_First then for BI in 1..8 loop B8(BI) := Keccak.Reverse_Table(Natural(Raw(Raw'First+8-BI))); end loop; else --simply copy as it is B8 := Raw(1..8); end if; -- convert and return U64 := Raw_Types.Cast(B8); return U64; end Hash2Val;
Using the above, one can now test Keccak hashes of files both raw and in the more usual numerical format (e.g. hex as given for instance by the keksum implementation). More importantly for me, Eulora's client can now check the hash of a received file when it gets the last chunk of it and therefore it can decide whether the whole thing is any good or not! I'm quite happy to inform as well that initial tests are running fine - the data acquisition part of the client successfully requests files and receives them apparently unmolested (even when there are quite a few chunks, so far I tested with several thousands meaning up to 10MB files), writing them neatly to disk exactly as and where intended. Hooray!
The above out of the way, the uglier next step is to get that hideous gui-code to actually *use* those files properly, too!
This is not something entirely new, since vtools for instance has previously adapted my Keccak implementation to a similar use, in order to calculate the hashes for vpatches. However, the approach taken there (by phf) apparently aimed to wrap the Ada implementation for C/CPP use and I don't want this at all. First, I'd much rather move C/CPP code to Ada if/when possible than the other way around. Second, there is absolutely no good reason in this case to force any C/CPP code in the mix since Ada actually provides all that is needed to interact with files on the disk without any trouble whatsoever. ↩
Comments feed: RSS 2.0
Just to check here: if I recall correctly, we had decided that *all* values sent through the communication protocol are considered Big Endian (MSB/b), is this correct? Hence, for the above, the server would send the *raw* hash essentially (and the client is free to convert it to a U64 value or not, in whatever format it wants to, as well).
That's what I remember ; nor do I see any great impediment to sticking to BE over the wire.
> simply read a file octet by octet, so that there is no special case at all
This promises to be slow, not to mention aren't we wasting most of the benefits ? How about : 1. always read the file in memory ; 2. as a ring buffer ; 3 such that there's no "padding" or problems : if the last block is "too short" such length will be read from the beginning as satisfies, wouldn't that be both better from a computing pov (let alone ideologically closer) ?
Re BE - ok; I'll have to keep this in mind actually also for *all* values since otherwise locally they are not all BE for sure.
>This promises to be slow, not to mention aren't we wasting most of the benefits ?
Uhm, I don't quite follow this. Specifically:
1. if the file is 1GB or 10GB, do you mean that the client & server should read all that in memory each time they need to recalculate the hash? Or keep it in memory at all times? If yes, then indeed, there's no need for this stateful package, can just use the stateless existing code and be done with it but I am not quite sure it helps a lot.
2. the padding is from the keccak-spec, meaning that if we decide now to fill it from the beginning instead, the result will *not* be keccak hash of the file really - it would be more-ideologically-closer-keccak-spec I guess, but that's about it and it will require creating and maintaining that ring buffer ie certainly no implementation gain as such. (For that matter, the existing stateless keccak-hashing does handle the padding of last block if incomplete since it is part of the original spec so if reading the whole file in memory, it could just handle it directly - it's only that I didn't think it was meant to keep files in memory; and hm, changing that part now again, ugh).
3. re speed, the *only* gain would be based on difference in reading speed from disk ie how much faster it is to read a block than an octet (or the full file if I figure out exactly how to ask it that ie how to get upfront the size without too much cost); otherwise there is no speed gain at hashing really because the sponge has a capacity and that is fixed - ie in *all* cases keccak itself (the sponge) reads/absorbs one block at a time, not more nor less, there is no difference there at all, whether stateless or stateful, whether padded or not padded whether whatever in the world.
Just to clarify as maybe it got confused in there: it reads as many octets as needed to fill a block and then passes *that* to the keccak sponge; so yes, there can be in principle some speed-gain in reading block-size-octets in one go (with the drawback of having to check /handle last possibly incomplete read) but the keccak-part is always exactly the same, no speed gained or lost there in either case.
Meanwhile and since it was grating anyway, I implemented also the block-sized read version of this (+ octet-based only for the last, incomplete block, since apparently on incomplete element it doesn't guarantee reading as much as there is) and a rough timing test on an AMD fx-8350, 16G RAM: the octet-based version takes reliably 0.1 seconds per MB (hence 4MB file takes ~0.4 seconds, 8MB file takes ~0.8 seconds, 680MB file takes ~68 seconds etc); the block-sized read version is trickier to pin down (as it depends in a more complex way on file size) but in any case, 4MB file takes ~0.2 seconds and 680MB file takes ~35 seconds.
Clearly block-read + octet-for-last is an improvement (and since ahem, I just proved it can be done, probably now I *have to* use it, too). Whether it's enough or not, I can't tell - it depends I suppose on what sort of files we end up sending, though either way the potential problems start anyway at the same end aka on large files so not sure what is gained, as such.
Aite, so then this discussion was indeed quite productive, and I'm quite happy with using the blocks-read-octets-last approach.
Noted.
Version: GnuPG v1.4.10 (GNU/Linux)
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
wd6OlmFUfAu2Gjuyd1W+81Ahj6S29d4bILNPqzs=
=ghIz
-----END PGP MESSAGE-----
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
DMS+pFottUoamKx6DaUHFGL41g==
=lm2g
-----END PGP MESSAGE-----
Nua ca copiasem io airuea. Ty!
Version: GnuPG v1.4.10 (GNU/Linux)
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
hFFhZA==
=EhsA
-----END PGP MESSAGE-----
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
btltDKdG4cOtSaMbaxykZGuig4xlvMMv
=k+jc
-----END PGP MESSAGE-----