|
![]() |
Registriert seit: 28. Feb 2016 Ort: Nordost Baden-Württemberg 3.084 Beiträge Delphi 12 Athens |
#1
Hallo,
durch die Meldung gewisser Bugs in der DEC ( ![]() wie ein gewisses API funktionieren sollte. Konkret geht es u.a. um diesen Bugreport: ![]() Es gibt für die Blockchiffren auch die Möglichkeit mit Streams zu arbeiten. Dazu gibt es EncodeStream und DecodeStream (beide DECCipherFormats.pas). Als Parameter haben diese den Quellstream, den Zielstream, die Anzahl zu bearbeitender Bytes und einen optionale OnProgress Eventhandler. Außerdem gibt es eine Done Methode, diese führt Aufräumarbeiten durch, wenn man das Cipher Objekt nicht gleich freigeben will sondern später für den nächsten Vorgang wiederverwenden will. Nun scheint es Problematisch (sowohl mit den vor einiger Zeit hinzugefügten Paddings für unvollständige letzte Blöcke als auch für Authentifizierte Blockverkettungsmodes wie GCM, wenn EncodeStream oder DecodeStream mehrfach auf den selben Quellstream angewendet werden, um die Daten "abschnittsweise" zu verarbeiten. Die Frage ist also, ob es überhaupt realistisch ist, für ein und den selben Quellstream (bin nicht 100% fit in Streams, gerne eine gute Quelle nennen die erläutert wie die intern funktionieren bzw. wie deren Verwendung erwartet wird) mehrfach EncodeStream oder DecodeStream aufzurufen oder ob nicht sowieso immer erst alles im Quellstream gesammelt wird und dann einmalig der Aufruf stattfindet. Nur: was ist mit Datenmengen, die nicht in dem Speicher passen? Behandelt das der interne Stream Aufbau schon passend? Oder wie würde sowas auf einen verschlüsselten Videostream angewendet aussehen? WIe wäre da pseudocode mäßig der Ablauf zu erwarten? Danke für alle Ideen/Meinungen, egal wie kontrovers.
Grüße
TurboMagic |
![]() |
Registriert seit: 3. Sep 2023 436 Beiträge |
#2
Well, first sorry for bad English and for that i will be (very) verbose trying to explain my opinion and my findings on this.
I looked at the code and browsed the concerning files, and now i see the problem(s) and shortcoming in utilizing streams. So to confirm my doubts i wrote my own test to amplify and pin point the real cause, and my doubts was on the point: the test project with cherry picked test vectors from DEC own test vectors
Delphi-Quellcode:
The output for the above test vector is
program AesGCMStream;
{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, Generics.Collections, classes, System.Math, DECBaseClass, DECCipherBase, DECCipherModes, DECCipherFormats, DECFormat, DECCiphers; const { TV_Key = '11754cd72aec309bf52f7687212e8957'; TV_IV = '3c819d9a9bed087615030b65'; TV_PT = ''; TV_AAD = ''; TV_CT = ''; TV_Tag = '250327c674aaf477aef2675748cf6971'; } {TV_Key = 'fe0121f42e599f88ff02a985403e19bb'; TV_IV = '3bb9eb7724cbe1943d43de21'; TV_PT = 'fd331ca8646091c29f21e5f0a1'; TV_AAD = '2662d895035b6519f3510eae0faa3900ad23cfdf'; TV_CT = '59fe29b07b0de8d869efbbd9b4'; TV_Tag = 'd24c3e9c1c73c0af1097e26061c857de'; } TV_Key = 'a976d8ae4528bff2911094a421f4ed83'; TV_IV = '6904cb03535dce4b080148df37e81a5c9a9bebfa0037658228456a8a1a3db8e5c0be70c082bfbf50f43ab59cdbf24312ad6dd041e'+ '1d179df5d36f9fa7b33acb2983f8d66443b44c1478253ee2cdea9840a6698e9be130de46947121af9223d411cc4f7c715da83a30dcd4c54f5b52701ee315b52c44b5d58cb6caaae946ad940'; TV_PT = 'f235d5c4f71b40d2afe4b85a6d62a5bb426e3606f086bee79f6788d3b7ce82a6'; TV_AAD = '348ae1548058be59efd0f830ca3b9b0805320632'; TV_CT = '837af64be0d0bcfb9688e7043f5594e5c483cfc06a5e809fbe091cd702bf419c'; TV_Tag = '71e22c2b15d4d2caeba54036'; { TV_Key = '88c81827cb514632c8b0c76b7ecbd1cc'; TV_IV = 'b3632bb439c8811f1454e6a368c4c9d0bbd3d9507ed1050cac3f19ba085063af9d162eb1c02077a51bad143be939d32c68'+ '5b6fb3f330b8b382cc6567c55f2f4ecfeff88ff281e1e5ee1cfc813a13c9e69096761f58d13b9cad6221b5aaee03e40ad56f1a61c250ef57f94985ab6a603ded02b513e035ac8b2e3c3b69d35d2918'; TV_PT = 'e254bf464879b4c48200541d359ecce478c67a62f4f5aaaa047d8e4a4ad6adff19da9a535a0be0758d5e7e992ccbb936d3c496'; TV_AAD = ''; TV_CT = '453ca80e69d37a6c8338da0deabb5ed1d5f8c006e67aca5d0bfbcd3aa32290521e91f379b7db57764c2755bf8691451e72a295'; TV_Tag = '01485fb4f9675740b354bf7557f0f23a'; } procedure DoTest(Step:Integer); var CipherAES: TCipher_AES; Key, IV, PT, AAD, CT, Tag, OutPut: TBytes; ctbStream, ptbStream: TBytesStream; dataLeftToEncode: Integer; begin Writeln('Feeding Step = ',Step); CipherAES := TCipher_AES.Create; try CipherAES.Mode := TCipherMode.cmGCM; Key := TFormat_HEX.Decode(BytesOf(TV_Key)); IV := TFormat_HEX.Decode(BytesOf(TV_IV)); PT := TFormat_HEX.Decode(BytesOf(TV_PT)); AAD := TFormat_HEX.Decode(BytesOf(TV_AAD)); CT := TFormat_HEX.Decode(BytesOf(TV_CT)); Tag := TFormat_HEX.Decode(BytesOf(TV_Tag)); CipherAES.Init(Key, IV); CipherAES.AuthenticationResultBitLength := Length(Tag) * 8; CipherAES.DataToAuthenticate := AAD; ptbStream := TBytesStream.Create(PT); ctbStream := TBytesStream.Create; dataLeftToEncode := ptbStream.Size; repeat CipherAES.EncodeStream(ptbStream, ctbStream, Min(STEP, ptbStream.Size - ptbStream.Position)); ctbStream.SetSize(ctbStream.Position); Writeln('Step : ' + StringOf(TFormat_HEX.Encode(ctbStream.Bytes))); Dec(dataLeftToEncode,STEP); until dataLeftToEncode <= 0; CipherAES.Done; ctbStream.SetSize(ctbStream.Position); OutPut := ctbStream.Bytes; Writeln('Expected : ' + StringOf(TFormat_HEX.Encode(CT))); Write('Encrypted: ' + StringOf(TFormat_HEX.Encode(OutPut))); if CompareMem(CT,OutPut,Length(ct)) then Writeln(#9'OK') else Writeln(#9'FAIL'); Writeln('Tag expected : ' + StringOf(TFormat_HEX.Encode(Tag))); Write('Tag encrypted: ' + StringOf(TFormat_HEX.Encode(CipherAES.CalculatedAuthenticationResult))); if CompareMem(Tag,CipherAES.CalculatedAuthenticationResult,Length(Tag)) then Writeln(#9'OK') else Writeln(#9'FAIL'); finally CipherAES.Free; end; Writeln; end; begin try DoTest(100); DoTest(1); DoTest(5); DoTest(16); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Writeln('Done.'); Readln; end.
Delphi-Quellcode:
This show exactly what went wrong and where, like for 5 bytes the first 5 of the cipher is correct the rest is wrong, and for step=16 the encryption is correct but the tag is off.
Feeding Step = 100
Step : 837AF64BE0D0BCFB9688E7043F5594E5C483CFC06A5E809FBE091CD702BF419C Expected : 837AF64BE0D0BCFB9688E7043F5594E5C483CFC06A5E809FBE091CD702BF419C Encrypted: 837AF64BE0D0BCFB9688E7043F5594E5C483CFC06A5E809FBE091CD702BF419C OK Tag expected : 71E22C2B15D4D2CAEBA54036 Tag encrypted: 71E22C2B15D4D2CAEBA54036 OK Feeding Step = 1 Step : 83 Step : 83B3 Step : 83B337 Step : 83B33726 Step : 83B3372677 Step : 83B3372677D9 Step : 83B3372677D9C7 Step : 83B3372677D9C7B1 Step : 83B3372677D9C7B17B Step : 83B3372677D9C7B17B91 Step : 83B3372677D9C7B17B91D3 Step : 83B3372677D9C7B17B91D3FD Step : 83B3372677D9C7B17B91D3FD0A Step : 83B3372677D9C7B17B91D3FD0AE5 Step : 83B3372677D9C7B17B91D3FD0AE5CE Step : 83B3372677D9C7B17B91D3FD0AE5CE7C Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE6013 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE601333 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BC Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9F1 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9F15B Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9F15B22 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9F15B2236 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9F15B2236AC Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9F15B2236ACFA Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9F15B2236ACFA90 Step : 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9F15B2236ACFA9047 Expected : 837AF64BE0D0BCFB9688E7043F5594E5C483CFC06A5E809FBE091CD702BF419C Encrypted: 83B3372677D9C7B17B91D3FD0AE5CE7CBE60133390BCC4D9F15B2236ACFA9047 FAIL Tag expected : 71E22C2B15D4D2CAEBA54036 Tag encrypted: 92D5435B6AB1ECF5B3AC7B15 FAIL Feeding Step = 5 Step : 837AF64BE0 Step : 837AF64BE09DAD2B697E Step : 837AF64BE09DAD2B697E5AF67EBD71 Step : 837AF64BE09DAD2B697E5AF67EBD7159A08E74A9 Step : 837AF64BE09DAD2B697E5AF67EBD7159A08E74A970706309C1 Step : 837AF64BE09DAD2B697E5AF67EBD7159A08E74A970706309C1A5D6D2E10B Step : 837AF64BE09DAD2B697E5AF67EBD7159A08E74A970706309C1A5D6D2E10B0571 Expected : 837AF64BE0D0BCFB9688E7043F5594E5C483CFC06A5E809FBE091CD702BF419C Encrypted: 837AF64BE09DAD2B697E5AF67EBD7159A08E74A970706309C1A5D6D2E10B0571 FAIL Tag expected : 71E22C2B15D4D2CAEBA54036 Tag encrypted: F4904F062055FD0124C1D3A3 FAIL Feeding Step = 16 Step : 837AF64BE0D0BCFB9688E7043F5594E5 Step : 837AF64BE0D0BCFB9688E7043F5594E5C483CFC06A5E809FBE091CD702BF419C Expected : 837AF64BE0D0BCFB9688E7043F5594E5C483CFC06A5E809FBE091CD702BF419C Encrypted: 837AF64BE0D0BCFB9688E7043F5594E5C483CFC06A5E809FBE091CD702BF419C OK Tag expected : 71E22C2B15D4D2CAEBA54036 Tag encrypted: A7CF85F2D18FD9BC086E2663 FAIL Done. First lets apply the AES with GCM correctly and for that i will refer to ![]() ![]() This visual representation explain AES in GCM mode, E_k is encrypted block and mult_H is the Galois field multiplication, now both must be 128 bit (16 bytes), this is important, notice 1) E_k is irrelevant to the input (text/Plaintext), and we get the cipher (Ciphertext) by XORing the E_k with Plaintext 2) mult_H is chained on the Ciphertext. These are the most important details concern us here, now to explain what is going wrong i will take example same as my tests above, lets say we have text length of 20 bytes and want encrypted it in chunks, same as performing using a stream. assuming we passed 5 bytes then E_k is correct and will be the same no matter how much bytes we feed, as it generate 16 bytes, then we XOR 5 bytes and get the Ciphertext for 5 bytes, this is shown in the output of my example where first 5 bytes is correct, on the second step, i mean another 5 bytes comes the problem and the whole thing collapse, why ? Because we should saved E_k that was 16 bytes and used only 5 from it, we should kept the rest and XOR the new input (second 5 bytes) with the rest, and only generate new E_k when we used all the 16 bytes of the last one. As for the tag correctness, see in my test above the last case where the steps are at 16 bytes, meaning and showing evidently the correctness of the encryption, but the tag is wrong, why ? Because similar to above the multiplicaiton must be performed on 16 bytes, not any less, so full block on Ciphertext, if there is no data then a padding should present and only then the multiplication should be performed. Thoughts here: 1) a refactor and re-structure is due, DoEncodeDecodeStream is wrong and depending on TGCM.EncodeGCM is also short and miss the accumulation. 2) Most important thing is the use of "Done;" this should be the trigger for finalization same or similar to hash functions, don't add any padding unless "Done" being called, otherwise save E_k and incomplete Ciphertext until they are multiple of 16 bytes then drop E_k and calculate the multiplication, for me accessing the tag if there is residue or accumulation should trigger an exception, this is best practice to prevent users from wrongly using encryption and enforcing the correct values, see here if the encryption is not ended (called done) this mean the encrption is correct abut the tag is wrong and only can be accessed after "Done" 3) I think if you managed to fix the code to make my test above pass then it will work for any thing. 4) 8k bytes can't be excessive nor small, should be available to the user to pick or adjust, can be useful for speed, or when want to encrypt big files, i see no problem with it, the problem is in (2) 5) in (2) either save E_k in full or just the rest, as shown in AESGCM representation next chunk should be xored at the right index, and this will fix the tests above. 6) Again voting to raise exception on accessing CalculatedAuthenticationResult if Done is not called. Hope it was clear and helpful.
Kas
|
![]() |
Registriert seit: 28. Feb 2016 Ort: Nordost Baden-Württemberg 3.084 Beiträge Delphi 12 Athens |
#3
Hello,
thanks for this detailed and precise description. I do think this is really helpful. It will just take quite some time to work out a real fix for this.
Grüße
TurboMagic |
![]() |
Registriert seit: 13. Dez 2007 Ort: Bruck an der Mur 77 Beiträge |
#4
Meine Insights bezüglich des Bugreports hast du ja schon
![]() Ich wollte hier nur noch kurz ein paar Anmerkungen machen: * GCM ist soweit ich weiss ja nur für 128 bit Ciphers möglich -> es wäre eventuell eine Idee GCM nur in spezialisierten Klassen zu verwenden -> wäre rauslösen aus den TDECCipherModes eine Option, die GMC Variable ist dort eigentlich nur für die AES Implemtierung (oder liege ich da falsch?) ? * Ich bastle gerad am ChaCha20 Poly1305 Encoding scheme - auch dieses Schema ist nur für 256 bit ciphers gedacht (es würde also auch eine Option für AES 256 geben) aber sonst für keine anderen Cipher. Da die Poly1305 und ChaCha teilweise miteinander verwoben sind wäre es evt. auch hier sinnvoll eine extra Klasse zu coden... * Ich denke multiple Calls zu EncodeBytes / EncodeStream sollten evt. doch zugelassen werden - alle anderen Cipher, die ja nicht voneinander abhängig sind funktionieren ja so auch... Es bräuchte hier nur etwas mehr "housekeeping" (status, counter, schon initialiert usw.) gemacht. Ein Call von "Done" würde dann natürlich das Ganze abschließen und das Finale Tag berechnen.... |
![]() |
Registriert seit: 3. Sep 2023 436 Beiträge |
#5
Meine Insights bezüglich des Bugreports hast du ja schon
![]() Ich wollte hier nur noch kurz ein paar Anmerkungen machen:
* GCM ist soweit ich weiss ja nur für 128 bit Ciphers möglich -> es wäre eventuell eine Idee GCM nur in spezialisierten Klassen zu verwenden -> wäre rauslösen aus den TDECCipherModes eine Option, die GMC Variable ist dort eigentlich nur für die AES Implemtierung (oder liege ich da falsch?) ? Though GCM is in need for 128bit block and has nothing to do with key size. * Ich bastle gerad am ChaCha20 Poly1305 Encoding scheme - auch dieses Schema ist nur für
256 bit ciphers gedacht (es würde also auch eine Option für AES 256 geben) aber sonst für keine anderen Cipher. Da die Poly1305 und ChaCha teilweise miteinander verwoben sind wäre es evt. auch hier sinnvoll eine extra Klasse zu coden... * Ich denke multiple Calls zu EncodeBytes / EncodeStream sollten evt. doch zugelassen werden -
alle anderen Cipher, die ja nicht voneinander abhängig sind funktionieren ja so auch... Es bräuchte hier nur etwas mehr "housekeeping" (status, counter, schon initialiert usw.) gemacht. Ein Call von "Done" würde dann natürlich das Ganze abschließen und das Finale Tag berechnen.... See stream functionality does need signal to end as it can and most likely used as chunk or repeated calls, "Done" will signal the sealing of the GCM tag calculation by multiply the last chained result with the first block (the one with counter=0), but enforcing requirement will break the one hit EncodeBytes/DecodeBytes, i saw these being used in many places in one line of code, enforcing "Done" with them is wrong, yet i support the functionality itself, but it must be different, may be different pair of functions, like EncodeChunk that accept TBytes or ... But in general moving GCM into own class is not bad idea, just costly in implementation, and will make the code cleaner as one line operation like EncodeBytes can use that GCM and call done internally, yet it might be not small refactor.
Kas
|
![]() |
Registriert seit: 13. Dez 2007 Ort: Bruck an der Mur 77 Beiträge |
#6
![]() See stream functionality does need signal to end as it can and most likely used as chunk or repeated calls, "Done" will signal the sealing of the GCM tag calculation by multiply the last chained result with the first block (the one with counter=0), but enforcing requirement will break the one hit EncodeBytes/DecodeBytes, i saw these being used in many places in one line of code, enforcing "Done" with them is wrong, yet i support the functionality itself, but it must be different, may be different pair of functions, like EncodeChunk that accept TBytes or ...
be updated further (this ends the stream...). There would also be some housekeeping involved regarding the cipher! If it does not fit the e.g. AES blocksize one would keep the residual in memory. When Done is called the final block is encrypted + the Tag is calculated. ![]() But in general moving GCM into own class is not bad idea, just costly in implementation, and will make the code cleaner as one line operation like EncodeBytes can use that GCM and call done internally, yet it might be not small refactor.
|
![]() |
Registriert seit: 28. Feb 2016 Ort: Nordost Baden-Württemberg 3.084 Beiträge Delphi 12 Athens |
#7
Meine Insights bezüglich des Bugreports hast du ja schon
![]() Ich wollte hier nur noch kurz ein paar Anmerkungen machen: * GCM ist soweit ich weiss ja nur für 128 bit Ciphers möglich -> es wäre eventuell eine Idee GCM nur in spezialisierten Klassen zu verwenden -> wäre rauslösen aus den TDECCipherModes eine Option, die GMC Variable ist dort eigentlich nur für die AES Implemtierung (oder liege ich da falsch?) ? * Ich bastle gerad am ChaCha20 Poly1305 Encoding scheme - auch dieses Schema ist nur für 256 bit ciphers gedacht (es würde also auch eine Option für AES 256 geben) aber sonst für keine anderen Cipher. Da die Poly1305 und ChaCha teilweise miteinander verwoben sind wäre es evt. auch hier sinnvoll eine extra Klasse zu coden... * Ich denke multiple Calls zu EncodeBytes / EncodeStream sollten evt. doch zugelassen werden - alle anderen Cipher, die ja nicht voneinander abhängig sind funktionieren ja so auch... Es bräuchte hier nur etwas mehr "housekeeping" (status, counter, schon initialiert usw.) gemacht. Ein Call von "Done" würde dann natürlich das Ganze abschließen und das Finale Tag berechnen.... In absehbarer Zeit kommt vermutlich noch ein authentifizierender Algorithmus hinzu (Details verrate ich noch nicht) und der bietet dann diesselbe Schnittstelle wie der GCM, weshalb diese dann in eine gemeinsame Basisklasse für die beiden ausgelagert wurde. Die 128 bit sind ja die Blockgröße und nicht die Schlüssellänge, die soweit ich mich erinnere beim AES die Anzahl Runden bestimmt. Früher gab es in der DEC nur eine AES Klasse und je nach Schlüssellänge wurde daraus AES128, AES192 und AES256. Inzwischen gibt es auch jeweils eine eigene AES128, AES192 und AES256 Klasse. Magst du Poly1305 zur DEC beitragen? Falls ja kann ich dir auch einen Branch und COmmit Rechte geben. ChaCha und SalSa stehen mittelfristig auch auf meiner ToDo Liste, habe aber auch noch jede Menge anderes Zeugs zu tun.
Grüße
TurboMagic |
![]() |
Registriert seit: 28. Feb 2016 Ort: Nordost Baden-Württemberg 3.084 Beiträge Delphi 12 Athens |
#8
Ich sehe wenig Sinn darin, die Flexibilität zu beschränken, wenn es auch anders geht.
Ja, GCM wird hautsächlich auf AES angewendet. Das kann man ja so dokumentieren. Aber wenn ein neuer Standard mal kommt der auch 128 Bit Blöcke nuntzt und GCM für den auch einigermaßen üblich ist müsste man ja wieder ran. Lieber die Zeit dafür nutzen und die problematische Umsetzung für Streams ändern. Das muss man so wie es aussieht für die vor kurzem eingeführten Paddings wie Pkcs#7 sowieso auch tun. Und dann vermutlich zuerst für diese und danach für GCM, oder arbeitet das auf den ungepaddeten Daten? Mir schwebt eine überladene Done Variante mit Parametern vor (vermutlich der Input und der Output Stream). Wenn man will kann man kann man nih ein Flag einbauen, welches beim Aufruf von Encode/DecodeStream auf True gesetzt werden und würde, wenn das gesetzt ist, im normalen Done eine Exception werfen. Damit würde man die Entwickler sicher erziehen (bis auf die, die ihren Code nicht testen)... ![]() * Ich denke multiple Calls zu EncodeBytes / EncodeStream sollten evt. doch zugelassen werden -
alle anderen Cipher, die ja nicht voneinander abhängig sind funktionieren ja so auch... Es bräuchte hier nur etwas mehr "housekeeping" (status, counter, schon initialiert usw.) gemacht. Ein Call von "Done" würde dann natürlich das Ganze abschließen und das Finale Tag berechnen.... See stream functionality does need signal to end as it can and most likely used as chunk or repeated calls, "Done" will signal the sealing of the GCM tag calculation by multiply the last chained result with the first block (the one with counter=0), but enforcing requirement will break the one hit EncodeBytes/DecodeBytes, i saw these being used in many places in one line of code, enforcing "Done" with them is wrong, yet i support the functionality itself, but it must be different, may be different pair of functions, like EncodeChunk that accept TBytes or ... But in general moving GCM into own class is not bad idea, just costly in implementation, and will make the code cleaner as one line operation like EncodeBytes can use that GCM and call done internally, yet it might be not small refactor.[/QUOTE] GCM ist bereits eine separate Klasse und Unit, weil das komplizierter/aufwändiger als die anderen Blockmodi ist. Das wird dann halt in DECCipherModes aufgerufen. Wie oben geschrieben: meine aktuelle Idee ist es, das Ende der Streamoperation per überladener Done Methode zu signalisieren.
Grüße
TurboMagic |
![]() |
Registriert seit: 13. Dez 2007 Ort: Bruck an der Mur 77 Beiträge |
#9
Ich sehe wenig Sinn darin, die Flexibilität zu beschränken, wenn es auch anders geht.
Ja, GCM wird hautsächlich auf AES angewendet. Das kann man ja so dokumentieren. Aber wenn ein neuer Standard mal kommt der auch 128 Bit Blöcke nuntzt und GCM für den auch einigermaßen üblich ist müsste man ja wieder ran. Lieber die Zeit dafür nutzen und die problematische Umsetzung für Streams ändern. Das muss man so wie es aussieht für die vor kurzem eingeführten Paddings wie Pkcs#7 sowieso auch tun. Und dann vermutlich zuerst für diese und danach für GCM, oder arbeitet das auf den ungepaddeten Daten? Mir schwebt eine überladene Done Variante mit Parametern vor (vermutlich der Input und der Output Stream). Wenn man will kann man kann man nih ein Flag einbauen, welches beim Aufruf von Encode/DecodeStream auf True gesetzt werden und würde, wenn das gesetzt ist, im normalen Done eine Exception werfen. Damit würde man die Entwickler sicher erziehen (bis auf die, die ihren Code nicht testen)... ![]() letzten noch zwischegespeicherten Daten zurück liefern.... Ich bin nicht unbedingt ein Fan von super algemeinen Methoden, die niemand benutzt und oft die Sache nur verkompliziert und fehleranfällig macht... Gibt es ein RFC dass GCM nicht in Verbindung mit AES enthält? Selbes gilt für Poly1305 - Es wird praktisch nicht mit AES verwendet! ![]() |
![]() |
Registriert seit: 3. Sep 2023 436 Beiträge |
#10
Well here ChaCha implementation to save you both some time, it is correct and working but i mixed the following code form two different project in haste, also while i tested it for many edge case, it is far from optimized for speed
Delphi-Quellcode:
unit ChaCha;
interface uses SysUtils; type TChaChaRounds = (cr8, cr12, cr20); TChaChaKey = array[0..31] of Byte; // 256-bit key TChaChaNonce = array[0..11] of Byte; // 96-bit nonce TChaChaBlock = array[0..63] of Byte; // 512-bit block TChaCha = class private FState: array[0..15] of Cardinal; // Internal state procedure QuarterRound(var a, b, c, d: Cardinal); procedure InnerBlock; procedure SetupState(const Key: TChaChaKey; const Nonce: TChaChaNonce; Counter: Cardinal); {$IFDEF CHACHA_DEBUG} procedure DebugState(const DbgStr: string); {$ENDIF} public procedure Encrypt(const Key: TChaChaKey; const Nonce: TChaChaNonce; Counter: Cardinal; Rounds: TChaChaRounds; const Input: TBytes; var Output: TBytes); class procedure SelfTest; end; implementation const SIGMA: array[0..3] of Cardinal = ($61707865, $3320646e, $79622d32, $6b206574); // "expand 32-byte k" { TChaCha } {$IFDEF CHACHA_DEBUG} procedure TChaCha.DebugState(const DbgStr: string); var I: Integer; begin WriteLn(DbgStr); for I := 0 to 15 do begin Write(Format('%08x ', [FState[I]])); if (I + 1) mod 4 = 0 then WriteLn; end; WriteLn; end; {$ENDIF} procedure TChaCha.QuarterRound(var a, b, c, d: Cardinal); begin {$IFOPT R+}{$DEFINE RND_HasRangeChecks}{$ENDIF} {$IFOPT Q+}{$DEFINE RND_HasOverflowChecks}{$ENDIF} {$RANGECHECKS OFF} {$OVERFLOWCHECKS OFF} a := a + b; d := d xor a; d := (d shl 16) or (d shr 16); c := c + d; b := b xor c; b := (b shl 12) or (b shr 20); a := a + b; d := d xor a; d := (d shl 8) or (d shr 24); c := c + d; b := b xor c; b := (b shl 7) or (b shr 25); {$IFDEF RND_HasRangeChecks}{$RANGECHECKS ON}{$ENDIF} {$IFDEF RND_HasOverflowChecks}{$OVERFLOWCHECKS ON}{$ENDIF} end; procedure TChaCha.InnerBlock; var x: array[0..15] of Cardinal; begin Move(FState, x, SizeOf(x)); QuarterRound(x[0], x[4], x[8], x[12]); QuarterRound(x[1], x[5], x[9], x[13]); QuarterRound(x[2], x[6], x[10], x[14]); QuarterRound(x[3], x[7], x[11], x[15]); QuarterRound(x[0], x[5], x[10], x[15]); QuarterRound(x[1], x[6], x[11], x[12]); QuarterRound(x[2], x[7], x[8], x[13]); QuarterRound(x[3], x[4], x[9], x[14]); Move(x, FState, SizeOf(FState)); end; procedure TChaCha.SetupState(const Key: TChaChaKey; const Nonce: TChaChaNonce; Counter: Cardinal); begin FState[0] := SIGMA[0]; FState[1] := SIGMA[1]; FState[2] := SIGMA[2]; FState[3] := SIGMA[3]; Move(Key[0], FState[4], 32); FState[12] := Counter; Move(Nonce[0], FState[13], 12); end; procedure ChaChaBlockSIMD(State: Pointer; Rounds: Integer); {$IF Defined(CPUX86) or Defined(CPUX64)} asm // Save state movdqu xmm0, dqword [State+00] // Use movdqa for aligned access or switch to movdqu for unaligned movdqu xmm1, dqword [State+16] movdqu xmm2, dqword [State+32] movdqu xmm3, dqword [State+48] // Main loop @ROUND: // Column round paddd xmm0, xmm1 pxor xmm3, xmm0 pshuflw xmm3, xmm3, 0B1h pshufhw xmm3, xmm3, 0B1h paddd xmm2, xmm3 pxor xmm1, xmm2 movdqa xmm4, xmm1 pslld xmm1, 12 psrld xmm4, 20 por xmm1, xmm4 paddd xmm0, xmm1 pxor xmm3, xmm0 movdqa xmm4, xmm3 pslld xmm3, 8 psrld xmm4, 24 por xmm3, xmm4 paddd xmm2, xmm3 pxor xmm1, xmm2 movdqa xmm4, xmm1 pslld xmm1, 7 psrld xmm4, 25 por xmm1, xmm4 // Shuffle for diagonal pshufd xmm1, xmm1, 39h pshufd xmm2, xmm2, 4Eh pshufd xmm3, xmm3, 93h // Diagonal round paddd xmm0, xmm1 pxor xmm3, xmm0 pshuflw xmm3, xmm3, 0B1h pshufhw xmm3, xmm3, 0B1h paddd xmm2, xmm3 pxor xmm1, xmm2 movdqa xmm4, xmm1 pslld xmm1, 12 psrld xmm4, 20 por xmm1, xmm4 paddd xmm0, xmm1 pxor xmm3, xmm0 movdqa xmm4, xmm3 pslld xmm3, 8 psrld xmm4, 24 por xmm3, xmm4 paddd xmm2, xmm3 pxor xmm1, xmm2 movdqa xmm4, xmm1 pslld xmm1, 7 psrld xmm4, 25 por xmm1, xmm4 // Shuffle back pshufd xmm1, xmm1, 93h pshufd xmm2, xmm2, 4Eh pshufd xmm3, xmm3, 39h dec Rounds jnz @ROUND {// Add original state paddd xmm0, dqword [State+00] // Use dqword for aligned access, paddd must have memory aligned ! paddd xmm1, dqword [State+16] paddd xmm2, dqword [State+32] paddd xmm3, dqword [State+48] } movdqu xmm4, dqword [State+00] // unaligned memory state addition paddd xmm0, xmm4 movdqu xmm4, dqword [State+16] paddd xmm1, xmm4 movdqu xmm4, dqword [State+32] paddd xmm2, xmm4 movdqu xmm4, dqword [State+48] paddd xmm3, xmm4 // Store result movdqu dqword [State+00], xmm0 // Use movdqa for aligned store or switch to movdqu for unaligned movdqu dqword [State+16], xmm1 movdqu dqword [State+32], xmm2 movdqu dqword [State+48], xmm3 end; {$ELSE} begin RaiseError(ERR_SIMD_NOT_SUPPORTED); end; {$ENDIF} const SIMDEnabled = true; procedure TChaCha.Encrypt(const Key: TChaChaKey; const Nonce: TChaChaNonce; Counter: Cardinal; Rounds: TChaChaRounds; const Input: TBytes; var Output: TBytes); var I, J, RoundCount: Integer; OrigState: array[0..15] of Cardinal; Keystream: TChaChaBlock; PKeystream: PByte; begin SetLength(Output, Length(Input)); if Length(Input) = 0 then Exit; case Rounds of cr8: RoundCount := 8; cr12: RoundCount := 12; cr20: RoundCount := 20; else RoundCount := 20; end; for I := 0 to (Length(Input) - 1) div 64 do begin SetupState(Key, Nonce, Counter + Cardinal(I)); Move(FState, OrigState, SizeOf(FState)); {$IFDEF CHACHA_DEBUG} DebugState('Before InnerBlock:'); {$ENDIF} if SIMDEnabled then begin ChaChaBlockSIMD(@FState[0],RoundCount shr 1); end else begin for J := 1 to RoundCount shr 1 do begin InnerBlock; {$IFDEF CHACHA_DEBUG} DebugState(Format('After InnerBlock %d:', [J])); {$ENDIF} end; for J := 0 to 15 do FState[J] := Cardinal(UInt64(FState[J]) + UInt64(OrigState[J])); end; {$IFDEF CHACHA_DEBUG} DebugState('After adding OrigState:'); {$ENDIF} Move(FState, Keystream, SizeOf(Keystream)); {$IFDEF CHACHA_DEBUG} Write('Keystream: '); for J := 0 to 63 do Write(Format('%02x ', [Keystream[J]])); WriteLn; {$ENDIF} PKeystream := @Keystream; for J := 0 to 63 do begin if (I * 64 + J) >= Length(Input) then Break; Output[I * 64 + J] := Input[I * 64 + J] xor PKeystream^; Inc(PKeystream); end; end; // erase memory FillChar(FState, SizeOf(FState), 0); FillChar(Keystream, SizeOf(Keystream), 0); FillChar(OrigState, SizeOf(OrigState), 0); end; class procedure TChaCha.SelfTest; const KEY1: TChaChaKey = ( $00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $0a, $0b, $0c, $0d, $0e, $0f, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1a, $1b, $1c, $1d, $1e, $1f ); NONCE1: TChaChaNonce = ( $00, $00, $00, $00, $00, $00, $00, $4a, $00, $00, $00, $00 ); PLAINTEXT1: array[0..113] of Byte = ( $4c, $61, $64, $69, $65, $73, $20, $61, $6e, $64, $20, $47, $65, $6e, $74, $6c, $65, $6d, $65, $6e, $20, $6f, $66, $20, $74, $68, $65, $20, $63, $6c, $61, $73, $73, $20, $6f, $66, $20, $27, $39, $39, $3a, $20, $49, $66, $20, $49, $20, $63, $6f, $75, $6c, $64, $20, $6f, $66, $66, $65, $72, $20, $79, $6f, $75, $20, $6f, $6e, $6c, $79, $20, $6f, $6e, $65, $20, $74, $69, $70, $20, $66, $6f, $72, $20, $74, $68, $65, $20, $66, $75, $74, $75, $72, $65, $2c, $20, $73, $75, $6e, $73, $63, $72, $65, $65, $6e, $20, $77, $6f, $75, $6c, $64, $20, $62, $65, $20, $69, $74, $2e ); EXPECTED_CIPHERTEXT1: array[0..113] of Byte = ( $6e, $2e, $35, $9a, $25, $68, $f9, $80, $41, $ba, $07, $28, $dd, $0d, $69, $81, $e9, $7e, $7a, $ec, $1d, $43, $60, $c2, $0a, $27, $af, $cc, $fd, $9f, $ae, $0b, $f9, $1b, $65, $c5, $52, $47, $33, $ab, $8f, $59, $3d, $ab, $cd, $62, $b3, $57, $16, $39, $d6, $24, $e6, $51, $52, $ab, $8f, $53, $0c, $35, $9f, $08, $61, $d8, $07, $ca, $0d, $bf, $50, $0d, $6a, $61, $56, $a3, $8e, $08, $8a, $22, $b6, $5e, $52, $bc, $51, $4d, $16, $cc, $f8, $06, $81, $8c, $e9, $1a, $b7, $79, $37, $36, $5a, $f9, $0b, $bf, $74, $a3, $5b, $e6, $b4, $0b, $8e, $ed, $f2, $78, $5e, $42, $87, $4d ); KEY2: TChaChaKey = ( $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01 ); NONCE2: TChaChaNonce = ( $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $02 ); PLAINTEXT2: array[0..374] of Byte = ( $41, $6e, $79, $20, $73, $75, $62, $6d, $69, $73, $73, $69, $6f, $6e, $20, $74, $6f, $20, $74, $68, $65, $20, $49, $45, $54, $46, $20, $69, $6e, $74, $65, $6e, $64, $65, $64, $20, $62, $79, $20, $74, $68, $65, $20, $43, $6f, $6e, $74, $72, $69, $62, $75, $74, $6f, $72, $20, $66, $6f, $72, $20, $70, $75, $62, $6c, $69, $63, $61, $74, $69, $6f, $6e, $20, $61, $73, $20, $61, $6c, $6c, $20, $6f, $72, $20, $70, $61, $72, $74, $20, $6f, $66, $20, $61, $6e, $20, $49, $45, $54, $46, $20, $49, $6e, $74, $65, $72, $6e, $65, $74, $2d, $44, $72, $61, $66, $74, $20, $6f, $72, $20, $52, $46, $43, $20, $61, $6e, $64, $20, $61, $6e, $79, $20, $73, $74, $61, $74, $65, $6d, $65, $6e, $74, $20, $6d, $61, $64, $65, $20, $77, $69, $74, $68, $69, $6e, $20, $74, $68, $65, $20, $63, $6f, $6e, $74, $65, $78, $74, $20, $6f, $66, $20, $61, $6e, $20, $49, $45, $54, $46, $20, $61, $63, $74, $69, $76, $69, $74, $79, $20, $69, $73, $20, $63, $6f, $6e, $73, $69, $64, $65, $72, $65, $64, $20, $61, $6e, $20, $22, $49, $45, $54, $46, $20, $43, $6f, $6e, $74, $72, $69, $62, $75, $74, $69, $6f, $6e, $22, $2e, $20, $53, $75, $63, $68, $20, $73, $74, $61, $74, $65, $6d, $65, $6e, $74, $73, $20, $69, $6e, $63, $6c, $75, $64, $65, $20, $6f, $72, $61, $6c, $20, $73, $74, $61, $74, $65, $6d, $65, $6e, $74, $73, $20, $69, $6e, $20, $49, $45, $54, $46, $20, $73, $65, $73, $73, $69, $6f, $6e, $73, $2c, $20, $61, $73, $20, $77, $65, $6c, $6c, $20, $61, $73, $20, $77, $72, $69, $74, $74, $65, $6e, $20, $61, $6e, $64, $20, $65, $6c, $65, $63, $74, $72, $6f, $6e, $69, $63, $20, $63, $6f, $6d, $6d, $75, $6e, $69, $63, $61, $74, $69, $6f, $6e, $73, $20, $6d, $61, $64, $65, $20, $61, $74, $20, $61, $6e, $79, $20, $74, $69, $6d, $65, $20, $6f, $72, $20, $70, $6c, $61, $63, $65, $2c, $20, $77, $68, $69, $63, $68, $20, $61, $72, $65, $20, $61, $64, $64, $72, $65, $73, $73, $65, $64, $20, $74, $6f ); EXPECTED_CIPHERTEXT2: array[0..374] of Byte = ( $a3, $fb, $f0, $7d, $f3, $fa, $2f, $de, $4f, $37, $6c, $a2, $3e, $82, $73, $70, $41, $60, $5d, $9f, $4f, $4f, $57, $bd, $8c, $ff, $2c, $1d, $4b, $79, $55, $ec, $2a, $97, $94, $8b, $d3, $72, $29, $15, $c8, $f3, $d3, $37, $f7, $d3, $70, $05, $0e, $9e, $96, $d6, $47, $b7, $c3, $9f, $56, $e0, $31, $ca, $5e, $b6, $25, $0d, $40, $42, $e0, $27, $85, $ec, $ec, $fa, $4b, $4b, $b5, $e8, $ea, $d0, $44, $0e, $20, $b6, $e8, $db, $09, $d8, $81, $a7, $c6, $13, $2f, $42, $0e, $52, $79, $50, $42, $bd, $fa, $77, $73, $d8, $a9, $05, $14, $47, $b3, $29, $1c, $e1, $41, $1c, $68, $04, $65, $55, $2a, $a6, $c4, $05, $b7, $76, $4d, $5e, $87, $be, $a8, $5a, $d0, $0f, $84, $49, $ed, $8f, $72, $d0, $d6, $62, $ab, $05, $26, $91, $ca, $66, $42, $4b, $c8, $6d, $2d, $f8, $0e, $a4, $1f, $43, $ab, $f9, $37, $d3, $25, $9d, $c4, $b2, $d0, $df, $b4, $8a, $6c, $91, $39, $dd, $d7, $f7, $69, $66, $e9, $28, $e6, $35, $55, $3b, $a7, $6c, $5c, $87, $9d, $7b, $35, $d4, $9e, $b2, $e6, $2b, $08, $71, $cd, $ac, $63, $89, $39, $e2, $5e, $8a, $1e, $0e, $f9, $d5, $28, $0f, $a8, $ca, $32, $8b, $35, $1c, $3c, $76, $59, $89, $cb, $cf, $3d, $aa, $8b, $6c, $cc, $3a, $af, $9f, $39, $79, $c9, $2b, $37, $20, $fc, $88, $dc, $95, $ed, $84, $a1, $be, $05, $9c, $64, $99, $b9, $fd, $a2, $36, $e7, $e8, $18, $b0, $4b, $0b, $c3, $9c, $1e, $87, $6b, $19, $3b, $fe, $55, $69, $75, $3f, $88, $12, $8c, $c0, $8a, $aa, $9b, $63, $d1, $a1, $6f, $80, $ef, $25, $54, $d7, $18, $9c, $41, $1f, $58, $69, $ca, $52, $c5, $b8, $3f, $a3, $6f, $f2, $16, $b9, $c1, $d3, $00, $62, $be, $bc, $fd, $2d, $c5, $bc, $e0, $91, $19, $34, $fd, $a7, $9a, $86, $f6, $e6, $98, $ce, $d7, $59, $c3, $ff, $9b, $64, $77, $33, $8f, $3d, $a4, $f9, $cd, $85, $14, $ea, $99, $82, $cc, $af, $b3, $41, $b2, $38, $4d, $d9, $02, $f3, $d1, $ab, $7a, $c6, $1d, $d2, $9c, $6f, $21, $ba, $5b, $86, $2f, $37, $30, $e3, $7c, $fd, $c4, $fd, $80, $6c, $22, $f2, $21 ); var ChaCha: TChaCha; Input, Output: TBytes; I: Integer; Pass: Boolean; begin ChaCha := TChaCha.Create; try SetLength(Input, Length(PLAINTEXT1)); Move(PLAINTEXT1, Input[0], Length(PLAINTEXT1)); ChaCha.Encrypt(KEY1, NONCE1, 1, cr20, Input, Output); Pass := True; if Length(Output) <> Length(EXPECTED_CIPHERTEXT1) then Pass := False else for I := 0 to Length(EXPECTED_CIPHERTEXT1) - 1 do if Output[I] <> EXPECTED_CIPHERTEXT1[I] then begin Pass := False; WriteLn(Format('Test Vector 1: Mismatch at byte %d: got %02x, expected %02x', [I, Output[I], EXPECTED_CIPHERTEXT1[I]])); Break; end; if not Pass then raise Exception.Create('ChaCha20 Test Vector 1 failed!'); SetLength(Input, Length(PLAINTEXT2)); Move(PLAINTEXT2, Input[0], Length(PLAINTEXT2)); ChaCha.Encrypt(KEY2, NONCE2, 1, cr20, Input, Output); Pass := True; if Length(Output) <> Length(EXPECTED_CIPHERTEXT2) then Pass := False else for I := 0 to Length(EXPECTED_CIPHERTEXT2) - 1 do if Output[I] <> EXPECTED_CIPHERTEXT2[I] then begin Pass := False; WriteLn(Format('Test Vector 2: Mismatch at byte %d: got %02x, expected %02x', [I, Output[I], EXPECTED_CIPHERTEXT2[I]])); Break; end; if not Pass then raise Exception.Create('ChaCha20 Test Vector 2 failed!'); SetLength(Input, Length(PLAINTEXT1)); Move(PLAINTEXT1, Input[0], Length(PLAINTEXT1)); ChaCha.Encrypt(KEY1, NONCE1, 1, cr8, Input, Output); ChaCha.Encrypt(KEY1, NONCE1, 1, cr12, Input, Output); WriteLn('ChaCha self-test passed successfully.'); finally ChaCha.Free; end; end; end.
Kas
|
![]() |
Ansicht |
![]() |
![]() |
![]() |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
![]() |
![]() |