הפרימיטיבים Prehash ו-SignPrehash מפצלים את החישוב של חתימה דיגיטלית לשני שלבים:
- Prehash צריך רק את המפתח הציבורי. הפונקציה הופכת הודעה באורך שרירותי לערך קצר של טרום-גיבוב בגודל קבוע.
- הפונקציה SignPrehash צריכה את המפתח הפרטי. הפונקציה הופכת ערך לפני גיבוב לחתימה.
החתימה שמתקבלת היא חתימה רגילה על ההודעה המקורית. האימות מתבצע באמצעות הפרימיטיב הרגיל של חתימה דיגיטלית PublicKeyVerify, והמאמתים לא צריכים לדעת – או לא אכפת להם – שהחתימה נוצרה בשני שלבים.
הערך שלפני הגיבוב נחשב לבייטים אטומים. הגודל שלו קבוע והוא מכיל את המזהה של המפתח שעבורו הוא חושב, אבל הפריסה שלו היא חלק מפורמט הנתונים של Tink, ואסור לנתח אותו או ליצור אותו בעצמכם. אם אתם מעבירים נתונים באמצעות Tink או שאתם צריכים את הפרטים ברמת הבייט, כדאי לעיין במאמר בנושא פורמט Tink wire.
משתמשים בצמד הפרימיטיבים הזה במקרים הבאים:
- מפתח החתימה נמצא במקום אחר, למשל ב-HSM, ב-KMS או מאחורי גבול RPC, ואתם לא רוצים לשלוח את ההודעה המלאה מעבר לגבול הזה.
- ההודעה גדולה, והחתימה מרחוק אוכפת מגבלת גודל על הבקשה.
אם אף אחת מהאפשרויות האלה לא רלוונטית, אפשר להשתמש במקום זאת בפרימיטיב הפשוט של חתימה דיגיטלית: הוא פשוט יותר וקשה יותר לשימוש לא נכון.
ערכות מפתחות
שני הפרימיטיבים בוחרים מפתחות בצורה שונה, כמו שפרימיטיבים של חתימה ואימות עושים עבור הפרימיטיב חתימה דיגיטלית:
-
Prehash.Computeתמיד משתמש במפתח הראשי של קבוצת המפתחות הציבורית, ומתעד את מזהה המפתח הזה בערך שלפני הגיבוב. הצד שבוחר באיזה מפתח תיצור החתימה. -
SignPrehash.Signקורא את מזהה המפתח מתוך ערך הגיבוב המוקדם וחותם באמצעות המפתח התואם המופעל של קבוצת המפתחות הפרטיים. זה הצד שמציית לבחירה שכבר נעשתה על ידי מישהו אחר. אם אין מפתח מופעל ב-keyset עם המזהה הזה, הקריאה תיכשל.
לכל מפתח בערכת מפתחות SignPrehash צריך להיות מזהה, אחרת יצירת הפרימיטיב תיכשל.
ערבויות אבטחה מינימליות
- לחתימה שמתקבלת יש את אותם מאפיינים כמו לחתימה שנוצרת על ידי הפרימיטיב Digital Signature עם אותו סוג מפתח.
- Tink מוסיף לערך שלפני הגיבוב קידומת של 5 בייטים שמכילה ערך מיוחד שמור ואת המזהה של המפתח שעבורו בוצע החישוב.
SignPrehashחותם על הערך רק באמצעות המפתח הזה. האם הערך קשור באופן קריפטוגרפי למפתח הזה תלוי באלגוריתם. במקרה של External Mu ML-DSA, הוא קשור. אפשר לקרוא על כך במאמר Tink wire format. - ההודעות יכולות להיות בכל אורך.
דברים שכדאי לשים לב אליהם
- החותם לא יכול לבדוק את מה שהוא חותם עליו. כל מי שיכול להתקשר אל
SignPrehashיכול לקבל חתימה על הודעה שרירותית, ולחותם אין אפשרות להחיל מדיניות על תוכן ההודעה. הגנה על הגישה אלSignPrehashבדיוק כמו הגנה על הגישה אלPublicKeySign. - הגנה על הערך לפני הגיבוב בזמן ההעברה. הוא לא סודי, אבל תוקף שיכול לשנות אותו במהלך ההעברה שולט במה שייחתם.
בחירת סוג המפתח
ML-DSA במצב External Mu, כפי שמתואר ב-RFC 9881, הוא האלגוריתם היחיד ש-Tink תומך בו עבור Prehash ו-SignPrehash. מומלץ להשתמש במפתח ML-DSA רגיל עם הפרימיטיבים האלה.
מומלץ להשתמש ב-ML_DSA_65 ברוב תרחישי השימוש.
למפתח חייבת להיות דרישת מזהה, כי כל ערך של גיבוב מראש מתחיל בקידומת שמכילה את המזהה של המפתח שעבורו הוא חושב. אלה הווריאציות המותרות:
-
TINK– החתימה שמתקבלת מתחילה בקידומת הרגילה של Tink, שהיא פלט של 5 בייטים -
NO_PREFIX_WITH_PREHASH_ID– לחתימה שמתקבלת אין קידומת פלט, אבל למפתח עדיין יש את המזהה שערך הגיבוב המקדים צריך.
לא ניתן להשתמש במפתחות שמשתמשים בווריאציה NO_PREFIX (raw), כי אין להם מזהה מפתח.
דוגמאות
בדוגמאות הבאות, הודעה נחתמת בשני שלבים ואז החתימה שמתקבלת מאומתת באמצעות הפרימיטיב הרגיל Digital Signature.
הם מוצגים כבלוק אחד כדי שיהיה קל לקרוא אותם. בפריסה אמיתית, שני השלבים האלה מתבצעים במקומות שונים. במאמר אני רוצה לחתום על נתונים באמצעות חותם מרחוק מוסבר איך עושים את זה שלב אחר שלב.
C++
#include "tink/keyset_handle.h" #include "tink/public_key_verify.h" #include "tink/signature/config_2026.h" #include "tink/signature/prehash.h" #include "tink/signature/sign_prehash.h" using ::crypto::tink::ConfigSignature2026; using ::crypto::tink::Prehash; using ::crypto::tink::PublicKeyVerify; using ::crypto::tink::SignPrehash; // 1. Wherever the message is: compute the prehash value. This needs only // the public keyset. absl::StatusOr<std::unique_ptr<Prehash>> prehasher = public_handle.GetPrimitive<Prehash>(ConfigSignature2026()); if (!prehasher.ok()) return prehasher.status(); absl::StatusOr<std::string> prehash = (*prehasher)->Compute(message); if (!prehash.ok()) return prehash.status(); // 2. Wherever the private key is: turn the prehash value into a signature. absl::StatusOr<std::unique_ptr<SignPrehash>> signer = private_handle.GetPrimitive<SignPrehash>(ConfigSignature2026()); if (!signer.ok()) return signer.status(); absl::StatusOr<std::string> signature = (*signer)->Sign(*prehash); if (!signature.ok()) return signature.status(); // 3. Anywhere: verify the signature over the original message. absl::StatusOr<std::unique_ptr<PublicKeyVerify>> verifier = public_handle.GetPrimitive<PublicKeyVerify>(ConfigSignature2026()); if (!verifier.ok()) return verifier.status(); absl::Status verified = (*verifier)->Verify(*signature, message);
Go
import ( "github.com/tink-crypto/tink-go/v2/signature" "github.com/tink-crypto/tink-go/v2/signprehash" ) // 1. Wherever the message is: compute the prehash value. This needs only // the public keyset. prehasher, err := signprehash.NewPrehash(publicHandle) if err != nil { return err } prehash, err := prehasher.ComputePrehash(message) if err != nil { return err } // 2. Wherever the private key is: turn the prehash value into a signature. signer, err := signprehash.NewPrehashSigner(privateHandle) if err != nil { return err } sig, err := signer.SignPrehash(prehash) if err != nil { return err } // 3. Anywhere: verify the signature over the original message. verifier, err := signature.NewVerifier(publicHandle) if err != nil { return err } if err := verifier.Verify(sig, message); err != nil { return err }
נושאים קשורים
- אני רוצה לחתום על נתונים באמצעות חותם מרחוק – הסבר מפורט על התהליך הדו-שלבי.
- חתימה דיגיטלית – חתימה ואימות בשלב אחד, כשהחותם יכול לראות את ההודעה.
- סוגי מפתחות פרימיטיביים נתמכים לפי שפה
- פורמט Tink wire – הפריסה ברמת הבייט של ערך לפני גיבוב, להעברת Tink לשפה אחרת.