Τεχνικό Άρθρο

Ed448 και Brainpool ECDSA σε καθαρό Pascal για PDF

Το PDFlibPas υπογράφει και επαληθεύει με Ed448 και με τις τρεις καμπύλες Brainpool ECDSA σε καθαρό Object Pascal. Χωρίς εξωτερική κρυπτογραφική βιβλιοθήκη, χωρίς πάροχο πλατφόρμας, χωρίς DLL: το PDFlibEd448 υλοποιεί το PureEdDSA του RFC 8032 στο edwards448, και το PDFlibBrainpool υλοποιεί τις brainpoolP256r1, brainpoolP384r1 και brainpoolP512r1 του RFC 5639. Και οι δύο χτίστηκαν με τον ίδιο τρόπο, έναντι δοκιμαστικών διανυσμάτων γνωστής απάντησης που δημιουργήθηκαν ανεξάρτητα πριν γραφτεί οποιοδήποτε Pascal, και οι δύο αξίζουν να παρουσιαστούν κυρίως για τα σφάλματά τους

Η αριθμητική σωμάτων είναι ασυνήθιστα ειλικρινής κώδικας. Είτε ταιριάζει με τα δημοσιευμένα διανύσματα byte προς byte είτε δεν ταιριάζει, οπότε δεν υπάρχει χώρος για «πάνω κάτω λειτουργικό». Το δύσκολο είναι ότι μια λανθασμένη υλοποίηση εξακολουθεί να παράγει υπογραφές, εξακολουθεί να επαληθεύει τις δικές της υπογραφές και εξακολουθεί να φαίνεται απολύτως πειστική

Γιατί αυτές οι καμπύλες, και γιατί σε Pascal

Οι καμπύλες Brainpool εμφανίζονται στα ευρωπαϊκά προφίλ πιστοποιημένης υπογραφής, οπότε μια βιβλιοθήκη που υπογράφει έγγραφα για αυτή την αγορά δεν μπορεί να τις αντιμετωπίζει ως περιθωριακές. Το Ed448 ανήκει στο σύνολο αλγορίθμων που φέρνει το ISO/TS 32002 στο PDF, όπου η εσωτερική του περίληψη είναι SHAKE256 και όχι SHA-2. Καμία από τις δύο οικογένειες δεν είναι διαθέσιμη στις συνήθεις κρυπτογραφικές βιβλιοθήκες Pascal, οπότε μια βιβλιοθήκη PDF που τις χρειάζεται πρέπει να τις διαθέτει η ίδια

Το επιχείρημα ανάπτυξης είναι το ίδιο που ισχύει για όλη την κρυπτογραφία αυτής της βιβλιοθήκης: μια εφαρμογή που διανέμει ένα εκτελέσιμο χωρίς κρυπτογραφική εξάρτηση δεν έχει πάροχο προς ανίχνευση, δεν έχει έκδοση προς ταίριασμα και δεν έχει συμπεριφορά που αλλάζει όταν ο υπολογιστής δέχεται ενημερώσεις. Η υπογραφή είναι ακριβώς ο τομέας όπου λιγότερο θέλετε μια εξάρτηση που μετακινείται

Οι σταθερές προέρχονται από το κείμενο της προδιαγραφής, ποτέ από μνήμη

Η πρώτη απόπειρα για το βασικό σημείο του edwards448 γράφτηκε από μνήμη και ήταν λανθασμένη. Δεν είναι μια σπάνια αβελτηρία, αλλά κοστίζει πολύ ακριβά, επειδή ένα λανθασμένο βασικό σημείο παράγει ένα αυτοσυνεπές σύστημα: η δημιουργία κλειδιών, η υπογραφή και η επαλήθευσή σας συμφωνούν μεταξύ τους και διαφωνούν με τον υπόλοιπο κόσμο

Η σωστή διαδικασία είναι να λαμβάνεται κάθε παράμετρος πεδίου από το κείμενο της προδιαγραφής και μετά να γίνεται διασταύρωση. Για το edwards448 αυτό σημαίνει τον πρώτο αριθμό, τη σταθερά της καμπύλης, την τάξη της ομάδας και τις δύο δεκαδικές συντεταγμένες του βασικού σημείου από το RFC 8032, τη μετατροπή τους στην εσωτερική αναπαράσταση limb και τον έλεγχό τους έναντι δημοσιευμένων δοκιμαστικών διανυσμάτων του ίδιου εγγράφου. Για τις καμπύλες Brainpool σημαίνει τις παραμέτρους από το RFC 5639, μια ανεξάρτητη υλοποίηση γραμμένη για τη δημιουργία διανυσμάτων και έναν αμφίδρομο έλεγχο έναντι βιβλιοθήκης συστήματος και προς τις δύο κατευθύνσεις, πριν εκτελεστεί οποιοδήποτε Pascal

Οι παράμετροι πεδίου των Ed448 και Brainpool ρέουν από το κείμενο των προδιαγραφών RFC 8032 και RFC 5639 σε μορφή limb και διασταυρώνονται πριν εκτελεστεί οποιοδήποτε Pascal
Οι παράμετροι πεδίου για το edwards448 και τις καμπύλες Brainpool λαμβάνονται από το κείμενο RFC, μετατρέπονται σε limbs και διασταυρώνονται έναντι ανεξάρτητων διανυσμάτων

Μια συντόμευση παραγωγής αξίζει προειδοποίηση επειδή μοιάζει καθολική και δεν είναι: η ανάκτηση του βασικού σημείου από σταθερή τιμή y λειτουργεί για την καμπύλη 25519 και δεν λειτουργεί για το edwards448, όπου η τιμή αυτή δεν έχει τετραγωνική ρίζα. Ένα script την κατέρριψε σε δευτερόλεπτα, που είναι πολύ φθηνότερο από το να την ανακαλύψετε μέσω debugger

Η μέθοδος: ένας κατοπτρισμός σε επίπεδο limb πριν από οποιοδήποτε Pascal

Η τεχνική που έκανε και τις δύο μονάδες διαχειρίσιμες είναι μια υλοποίηση-κατοπτρισμός σε γλώσσα με απεριόριστους ακεραίους, χτισμένη από κάτω προς τα πάνω. Πρώτα μόνο το στρώμα αριθμητικής: πολλαπλασιασμός και αφαίρεση στο σώμα και διάδοση κρατούμενου, με δοκιμές καταπόνησης βάσει των αλγεβρικών τους αναλλοίωτων σε μερικές εκατοντάδες τυχαίες περιπτώσεις. Μετά η πλήρης δημιουργία κλειδιών μέσα στον κατοπτρισμό, που είναι εκεί όπου ζουν τα σημασιολογικά σφάλματα και όπου η εύρεσή τους κοστίζει λίγο. Και μόνο ύστερα η μεταγραφή σε Pascal

Ροή εργασίας υλοποίησης-κατοπτρισμού με απεριόριστους ακεραίους που επικυρώνει την αριθμητική σώματος και τη δημιουργία κλειδιών Pascal για Ed448 και Brainpool
Η ροή εργασίας του κατοπτρισμού από κάτω προς τα πάνω: πρώτα η αριθμητική, μετά η δημιουργία κλειδιών μέσα στον κατοπτρισμό, ύστερα η μεταγραφή σε Pascal και η σύγκριση ενδιάμεσων τιμών

Το όφελος είναι διαγνωστικό και όχι αναπτυξιακό. Μόλις ο κατοπτρισμός είναι γνωστός ως σωστός, κάθε ασυμφωνία μεταξύ κατοπτρισμού και Pascal είναι ολίσθηση μεταγραφής, και η εξέταση της ίδιας ενδιάμεσης τιμής και στις δύο υλοποιήσεις την εντοπίζει αμέσως. Έτσι μια κατηγορία σφαλμάτων που αλλιώς είναι σχεδόν αδύνατη να αποσφαλματωθεί, ένα μοναδικό λάθος limb βαθιά μέσα σε βαθμωτό πολλαπλασιασμό, γίνεται μια σύγκριση πέντε λεπτών

Τέσσερις βασικές αιτίες στο Ed448

Και οι τέσσερις βρέθηκαν με εξέταση ενδιάμεσων τιμών, και οι τέσσερις είναι ο τύπος του σφάλματος που παράγει έξοδο που μοιάζει έγκυρη

Η πρώτη είναι μια παγίδα συμβολισμού. Οι περισσότεροι δημοσιευμένοι τύποι για ενοποιημένη πρόσθεση Edwards προϋποθέτουν σταθερά καμπύλης ίση με μείον ένα, ενώ το edwards448 έχει συν ένα. Αν μεταφερθεί αναλλοίωτος, ο αριθμητής της συντεταγμένης y γράφεται ως άθροισμα εκεί που πρέπει να είναι διαφορά. Η διόρθωση δεν είναι να διορθωθεί το πρόσημο, αλλά να εξαχθεί εκ νέου η μορφή γινομένου χωρίς αντιστροφή από τον νόμο πρόσθεσης affine της σωστής καμπύλης, κάτι που παράγει τις τέσσερις εκφράσεις συντεταγμένων και δεν αφήνει περιθώριο να κληρονομηθεί πρόσημο από λάθος πηγή

Η δεύτερη βρίσκεται στην αποσυμπίεση σημείων. Η ανάκτηση του affine x από προβολικές συντεταγμένες απαιτεί έναν πολλαπλασιασμό με το αντίστροφο του Z. Ο πολλαπλασιασμός με το αντίστροφο στο τετράγωνο δίνει μια τιμή που εξακολουθεί να είναι έγκυρη προβολική αναπαράσταση και είναι η λάθος affine συντεταγμένη, οπότε το σύμπτωμα είναι ένα σωστό y με λάθος x. Κάθε φορά που η μία συντεταγμένη είναι σωστή και η άλλη όχι, το σφάλμα βρίσκεται στην κανονικοποίηση, όχι στην αριθμητική

Η τρίτη είναι μια συνήθεια που κουβαλήθηκε από τη βραχύτερη καμπύλη. Τόσο ο βαθμωτός ανά υπογραφή όσο και ο βαθμωτός πρόκλησης πρέπει να ανάγονται από την πλήρη περίληψη, που για το Ed448 είναι 114 bytes, και όχι από τα πρώτα 57 της. Η καμπύλη των 32 bytes χρησιμοποιεί επίσης ολόκληρη την περίληψη των 64 bytes της, οπότε ο κανόνας είναι συνεπής· λανθασμένη είναι μόνο η υπόθεση ότι «το μισό μήκος της περίληψης είναι το πλάτος του βαθμωτού»

Η τέταρτη είναι η σειρά. Το πρόθεμα διαχωρισμού πεδίου έρχεται πρώτο, πριν από το πρόθεμα του context και το μήνυμα, που δεν είναι η σειρά που υπονοεί η διαισθητική ανάγνωση των R και A στην προδιαγραφή. Το λάθος εδώ παράγει υπογραφές που επαληθεύονται μόνο στη δική σας υλοποίηση και πουθενά αλλού, που είναι η πιο παραπλανητική δυνατή αποτυχία

// Σχεδιασμός κρατούμενου σώματος: καθαρή διάδοση με σημασιολογία
// floor, ώστε να λειτουργούν θετικά και αρνητικά limbs και η αφαίρεση
// να μην χρειάζεται προσαρμογή. Το κρατούμενο κορυφής αναδιπλώνεται
// μέσω 2^448 = 2^224 + 1 (mod p), που αγγίζει το limb 0 και το limb 8.
// Περιορισμένο σε τέσσερις γύρους· στην πράξη παρατηρήθηκαν δύο
procedure FeCarry(var A: TFe448);
var
  I, Round: Integer;
  Carry: Int64;
begin
  for Round := 1 to 4 do
  begin
    Carry := 0;
    for I := 0 to 15 do
    begin
      A[I] := A[I] + Carry;
      Carry := Floor28(A[I]);          // floor, όχι περικοπή
      A[I] := A[I] - (Carry shl 28);
    end;
    if Carry = 0 then
      Break;
    A[0] := A[0] + Carry;              // 2^448 == 1
    A[8] := A[8] + Carry;              // 2^448 == 2^224
  end;
end;

Μια προηγούμενη έκδοση της ρουτίνας εφάρμοζε προσαρμογή πριν τη διάδοση, και με μεγάλες εισόδους δίπλωνε ψευδές κρατούμενο λάθος τάξης μεγέθους στα χαμηλά limbs. Τα σχήματα κρατούμενου με προσαρμογή είναι διαρκής πηγή αυτής της κατηγορίας ελαττωμάτων· η σημασιολογία floor με δεσμευμένο βρόχο επανάληψης είναι ευκολότερη στη συλλογιστική και μετρήσιμα αρκετά γρήγορη

Δύο βασικές αιτίες στο Brainpool

Η πρώτη δεν είναι καθόλου κρυπτογραφία. Η λειτουργική αναπαράσταση είναι 33 limbs, οπότε το γινόμενο δύο τιμών χρειάζεται 66, και ο πίνακας γινομένου είχε δηλωθεί με 64. Η εγγραφή πέρα από το τέλος κατέστρεψε γειτονική μνήμη, που εμφανίστηκε πρώτα ως λάθος αποτελέσματα και έγινε κατάρρευση μόνο όταν προστέθηκε ευρύτερος έλεγχος. Ο κανόνας που προέκυψε αξίζει να εφαρμόζεται σε κάθε αριθμητικό buffer σταθερού μεγέθους: υπολογίστε το μέγεθος από το πλάτος γινομένου χειρότερης περίπτωσης, προσθέστε περιθώριο, και μετά μην το ξανασκεφτείτε. Ο πίνακας στον κώδικα που διανέμεται είναι 68 limbs

Η δεύτερη είναι ένα μπερδεμένο σχήμα ύψωσης σε δύναμη. Υπάρχουν δύο σωστές μορφές square-and-multiply και καταναλώνουν τον εκθέτη σε αντίθετες κατευθύνσεις: η μορφή από δεξιά προς αριστερά πολλαπλασιάζει και μετά υψώνει στο τετράγωνο τη βάση, και πρέπει να διαβάζει bits από το λιγότερο σημαντικό άκρο, ενώ η μορφή από αριστερά προς δεξιά υψώνει στο τετράγωνο και μετά πολλαπλασιάζει και διαβάζει από το πιο σημαντικό άκρο. Ο βρόχος modular αντιστροφής είχε σώμα από δεξιά προς αριστερά με διάβαση bits ξεκινώντας από το πιο σημαντικό άκρο. Και τα δύο μισά είναι σχολικά, ο συνδυασμός δεν είναι, και το αποτέλεσμα είναι λάθος αντίστροφο που εξακολουθεί να μοιάζει με πειστικό στοιχείο του σώματος

Δύο σχήματα ύψωσης square-and-multiply με αντίθετες κατευθύνσεις bits και η μικτή μορφή που υπολόγιζε λάθος modular αντίστροφα Brainpool
Και οι δύο μορφές square-and-multiply είναι σωστές μόνες τους· συνδυασμός σώματος δεξιά-προς-αριστερά με διάβαση από το πιο σημαντικό άκρο δίνει ένα πειστικό λάθος αντίστροφο
// Διπλασιασμός και πρόσθεση Jacobian όπου η εγγραφή προορισμού μπορεί
// να είναι η ίδια μεταβλητή με μια πηγή. Αντίγραφο ολόκληρης της
// εγγραφής στην είσοδο είναι η μόνη αξιόπιστη άμυνα: η εγγραφή στα
// limbs του R μολύνει τις μεταγενέστερες αναγνώσεις του P
procedure BPPointDouble(var R: TBPPoint; const P: TBPPoint;
  const Curve: TBPCurve);
var
  Pin: TBPPoint;
begin
  Pin := P;        // πρώτα αντίγραφο, μετά υπολογισμός μόνο από το Pin
  // ... M = 3X^2 + A*Z^4, S = 4*X*Y^2, X3 = M^2 - 2S, ...
end;

Δύο μαθήματα διαδικασίας που κόστισαν περισσότερο από τα σφάλματα

Η σταδιακή επιδιόρθωση εν ώρα λειτουργία δεν συγκλίνει σε κρυπτογραφική μονάδα. Ένα προσχέδιο μαλώθηκε επανειλημμένα μέχρι να κουβαλά 32 διπλότυπες ρουτίνες και κατεστραμμένη δομή, και διορθώθηκε μόνο με πλήρη επανεγγραφή. Το μοτίβο προς υιοθέτηση είναι είτε να το γράψετε μια φορά από επικυρωμένο κατοπτρισμό είτε να το ξαναγράψετε· μια σειρά τοπικών διορθώσεων σε αριθμητική που δεν καταλαβαίνετε ακόμη συσσωρεύεται ταχύτερα από όσο διορθώνει

Και ελέγξτε τη χρονική σήμανση στο εκτελέσιμο πριν πιστέψετε ένα αποτέλεσμα δοκιμής. Ένα σταδιακό build που μεταγλωττίζει αλλά δεν επανασυνδέει τρέχει το προηγούμενο εκτελέσιμο, κάτι που κατασκεύασε έναν ολόκληρο γύρο ψευδών ιχνών για δοκιμές που έλειπαν και διπλότυπη έξοδο. Όταν αποσφαλματώνετε κρυπτογραφία, ένα ανεξήγητο αποτέλεσμα πρέπει να προκαλεί την ερώτηση «αυτό είναι το εκτελέσιμο που μόλις έφτιαξα;» πριν το «μήπως ο αλγόριθμος είναι λάθος;»

Απόδοση, εύρος και πώς να το καλείτε

Η modular αναγωγή στη μονάδα Brainpool είναι σειριακή σε bits αφαίρεση με ολίσθηση από το υψηλότερο αναμένο bit του γινομένου, οπότε ένας πολλαπλασιασμός κοστίζει κατά προσέγγιση όσο η τάξη του πλάτους bits. Μια επαλήθευση P-256 καταλήγει στα χαμηλά εκατοντάδες χιλιοστά του δευτερολέπτου, κάτι απόλυτα φυσιολογικό για υπογραφή ή επαλήθευση εγγράφων και ανεπαρκές για έναν τερματισμό TLS. Η αναγωγή Barrett είναι η προφανής αναβάθμιση και χρειάζεται ευρύτερη τιμή εργασίας από όση μεταφέρει η τρέχουσα αναπαράσταση, οπότε είναι αλλαγή για τη στιγμή που ένας φόρτος εργασίας θα τη ζητήσει, όχι προληπτική

uses
  PDFlibEd448, PDFlibBrainpool;

var
  PublicKey, Signature: AnsiString;
  Curve: TBPCurve;
  R, S, PubX, PubY: TBPValue;
begin
  // Ed448: PureEdDSA, SHAKE256 εσωτερικά, κλειδιά 57 bytes
  if Ed448PublicKeyFromSeed(Seed, PublicKey) and
     Ed448Sign(DocumentDigest, Seed, Signature) then
    Assert(Ed448Verify(DocumentDigest, PublicKey, Signature));

  // Brainpool: ο καλών παρέχει το nonce ανά υπογραφή, οπότε η
  // πολιτική nonce παραμένει στην εφαρμογή
  Curve := BPLoadCurve(bpP256r1);
  if BPKeyGen(PubX, PubY, PrivateD, Curve) and
     BPSignFixedK(R, S, Hash, PrivateD, Nonce, Curve) then
    Assert(BPVerify(R, S, Hash, PubX, PubY, Curve));
end;

Σημειώστε ότι το σημείο εισόδου υπογραφής Brainpool λαμβάνει το nonce αντί να το παράγει. Είναι σκόπιμο: η δημιουργία nonce είναι το ενιαίο πιο καταστροφικό λάθος στην ECDSA, αφού μια επαναλαμβανόμενη ή προβλέψιμη τιμή αποκαλύπτει το ιδιωτικό κλειδί, και η απόφαση για το πού προέρχεται η τυχαιότητα ανήκει στην εφαρμογή και στο καθεστώς συμμόρφωσής της, όχι σε μια βιβλιοθήκη PDF

Αυτές οι καμπύλες συνοδεύουν τη μετα-κβαντική εργασία που περιγράφεται στο άρθρο για το FIPS 204 ML-DSA, και συνδέονται στον ίδιο αγωγό υπογραφής και επικύρωσης που καλύπτεται στην υπογραφή και επικύρωση PAdES. Για δοκιμαστικά πιστοποιητικά σε αυτές τις καμπύλες, η τοπική διαδρομή δημιουργίας περιγράφεται στα αυτοϋπογεγραμμένα πιστοποιητικά με CryptoAPI. Ο πλήρης αλγοριθμικός πίνακας καταγράφεται στη σελίδα προϊόντος losLab PDF Developer Library