ننصح باستخدام العنصرَين الأساسيَّين Prehash وSignPrehash مع مفتاح ML_DSA_44 عندما يكون المفتاح الخاص مخزّنًا في مكان لا يمكن إرسال الرسالة منه.
في بعض الأحيان، لا يمكن للجهة التي تحتفظ بمفتاح التوقيع تلقّي الرسالة نفسها، أو لا يُفترض أن تتلقّاها، وذلك لأنّ المفتاح مخزّن في وحدة أمان الأجهزة (HSM) أو خدمة إدارة المفاتيح (KMS)، أو لأنّ حجم الرسالة أكبر من الحدّ الأقصى لحجم الطلب الذي يمكن للموقّع إرساله.
تعمل الدالتان الأساسيتان Prehash وSignPrehash على حلّ هذه المشكلة من خلال تقسيم عملية التوقيع إلى خطوتَين. تحسب قيمة قصيرة مسبقة للتجزئة حيث توجد الرسالة، وذلك باستخدام المفتاح العام فقط، ثم ترسلها إلى الموقّع. يحوّل الموقّع الرسالة إلى توقيع باستخدام المفتاح الخاص، بدون أن يرى الرسالة مطلقًا. في وضع ML-DSA في External Mu، وهو الوضع الذي يتيحه خوارزمية Tink، تبلغ قيمة التجزئة المسبقة 69 بايت، بغض النظر عن حجم الرسالة.
التوقيع الذي يتم إرجاعه هو توقيع عادي على الرسالة الأصلية: تستخدم أدوات التحقّق عنصر التوقيع الرقمي العادي ولا تحتاج إلى معرفة أنّ العملية تتضمّن خطوتين.
قبل البدء
أنشئ مجموعة مفاتيح ML-DSA تتضمّن مفاتيح تتطلّب معرّفًا، إما TINK أو NO_PREFIX_WITH_PREHASH_ID إذا كنت لا تريد بادئة إخراج في التوقيع الناتج. قدِّم للموقِّع مجموعة المفاتيح الخاصة، وقدِّم للطرف الذي سيجري عملية التجزئة المسبقة مجموعة المفاتيح العامة المقابلة. يجب أن يكون لدى الموقّع مفتاح مفعّل لكل معرّف مفتاح يمكن أن ينتجه جانب التجزئة المسبقة، راجِع مجموعات المفاتيح.
الخطوة 1: احتساب قيمة التجزئة المسبقة
يمكنك تشغيل هذا الرمز البرمجي في أي مكان تظهر فيه الرسالة، وهو يحتاج فقط إلى مجموعة المفاتيح العامة.
C++
#include "tink/keyset_handle.h" #include "tink/signature/config_2026.h" #include "tink/signature/prehash.h" absl::StatusOr<std::unique_ptr<crypto::tink::Prehash>> prehasher = public_handle.GetPrimitive<crypto::tink::Prehash>( crypto::tink::ConfigSignature2026()); if (!prehasher.ok()) return prehasher.status(); absl::StatusOr<std::string> prehash = (*prehasher)->Compute(message); if (!prehash.ok()) return prehash.status();
Go
import "github.com/tink-crypto/tink-go/v2/signprehash" prehasher, err := signprehash.NewPrehash(publicHandle) if err != nil { return err } prehash, err := prehasher.ComputePrehash(message) if err != nil { return err }
Python
from tink import signature # Due to the internal structure of Tink Python, Prehash and SignPrehash # are an exception where calling signature.register() is not necessary. # Do not expect the same for other primitives (such as PublicKeyVerify in # Step 4). prehasher = public_handle.primitive(signature.Prehash) prehash = prehasher.compute(message)
الخطوة 2: إرسال قيمة التجزئة المسبقة إلى الموقّع
أرسِل القيمة التي تم تشفيرها مسبقًا إلى الجهة التي تحتفظ بالمفتاح الخاص. وهي ليست سرية، ولكن يجب حماية سلامتها أثناء نقلها، لأنّ أي مهاجم يمكنه تعديلها أثناء نقلها سيتحكّم في المحتوى الذي يتم توقيعه.
الخطوة 3: توقيع قيمة التجزئة المسبقة
نفِّذ هذا الأمر أينما كان المفتاح الخاص.
C++
#include "tink/keyset_handle.h" #include "tink/signature/config_2026.h" #include "tink/signature/sign_prehash.h" absl::StatusOr<std::unique_ptr<crypto::tink::SignPrehash>> signer = private_handle.GetPrimitive<crypto::tink::SignPrehash>( crypto::tink::ConfigSignature2026()); if (!signer.ok()) return signer.status(); absl::StatusOr<std::string> signature = (*signer)->Sign(prehash); if (!signature.ok()) return signature.status();
Go
import "github.com/tink-crypto/tink-go/v2/signprehash" signer, err := signprehash.NewPrehashSigner(privateHandle) if err != nil { return err } sig, err := signer.SignPrehash(prehash) if err != nil { return err }
Python
from tink import signature # Due to the internal structure of Tink Python, Prehash and SignPrehash # are an exception where calling signature.register() is not necessary. # Do not expect the same for other primitives (such as PublicKeyVerify in # Step 4). signer = private_handle.primitive(signature.SignPrehash) sig = signer.sign(prehash)
الخطوة 4: التحقّق من التوقيع
التحقّق هو عملية التوقيع الرقمي العادية على الرسالة الأصلية، وليس على قيمة التجزئة المسبقة.
C++
absl::StatusOr<std::unique_ptr<crypto::tink::PublicKeyVerify>> verifier = public_handle.GetPrimitive<crypto::tink::PublicKeyVerify>( crypto::tink::ConfigSignature2026()); if (!verifier.ok()) return verifier.status(); absl::Status verified = (*verifier)->Verify(signature, message);
Go
import "github.com/tink-crypto/tink-go/v2/signature" verifier, err := signature.NewVerifier(publicHandle) if err != nil { return err } if err := verifier.Verify(sig, message); err != nil { return err }
Python
from tink import signature signature.register() verifier = public_handle.primitive(signature.PublicKeyVerify) verifier.verify(sig, message)
Prehash وSignPrehash
تقسّم عناصر Prehash وSignPrehash الأساسية عملية احتساب التوقيع الرقمي إلى خطوتَين:
- لا يتطلّب Prehash سوى المفتاح العام. تحوّل هذه الدالة رسالة بأي طول إلى قيمة تجزئة مسبقة قصيرة وثابتة الحجم.
- يتطلّب SignPrehash المفتاح الخاص. تحوّل هذه الدالة قيمة ما قبل التجزئة إلى توقيع.
التوقيع الناتج هو توقيع عادي على الرسالة الأصلية. يمكنك إثبات صحة التوقيع باستخدام العنصر الأساسي العادي للتوقيع الرقمي PublicKeyVerify، ولا يحتاج المدقّقون إلى معرفة أنّ التوقيع تم إنشاؤه في خطوتين.
يجب التعامل مع قيمة التجزئة المسبقة كبايتات غير شفافة. له حجم ثابت ويحمل معرّف المفتاح الذي تم احتسابه له، ولكن تنسيقه هو جزء من تنسيق النقل في Tink، ولا يجب تحليله أو إنشاؤه بنفسك. إذا كنت تريد نقل بيانات Tink أو كنت بحاجة إلى تفاصيل على مستوى البايت، يمكنك الاطّلاع على تنسيق نقل بيانات Tink.
استخدِم هذا الزوج من العناصر الأساسية في الحالات التالية:
- مفتاح التوقيع مخزّن في مكان آخر، مثلاً في وحدة أمان الأجهزة (HSM) أو نظام إدارة المفاتيح (KMS) أو خلف حدود استدعاء الإجراء عن بُعد (RPC)، ولا تريد إرسال الرسالة الكاملة عبر هذه الحدود.
- حجم الرسالة كبير، ويفرض برنامج التوقيع عن بُعد حدًا أقصى لحجم الطلب.
إذا لم ينطبق أي من هذين الشرطين، استخدِم العنصر الأساسي التوقيع الرقمي العادي بدلاً من ذلك، فهو أبسط وأقل عرضة لإساءة الاستخدام.
مجموعات المفاتيح
تختار العمليتان الأساسيتان المفاتيح بشكل مختلف، بالطريقة نفسها التي يتم بها التوقيع والتحقّق من صحة التوقيع في العملية الأساسية التوقيع الرقمي:
- يستخدم
Prehash.Computeدائمًا المفتاح الأساسي لمجموعة المفاتيح العامة، ويسجّل رقم تعريف هذا المفتاح في قيمة التجزئة المسبقة. وهو الطرف الذي يختار المفتاح الذي سيتم إنشاء التوقيع باستخدامه. - تقرأ
SignPrehash.Signمعرّف المفتاح من قيمة التجزئة المسبقة وتوقّع باستخدام المفتاح المفعَّل المطابق من مجموعة المفاتيح الخاصة. وهو الجانب الذي يتبع خيارًا اتخذه شخص آخر. إذا لم يكن أي مفتاح مفعّل في مجموعة المفاتيح يتضمّن رقم التعريف هذا، سيتعذّر إجراء المكالمة.
يجب أن يتضمّن كل مفتاح في مجموعة مفاتيح SignPrehash شرطًا بشأن المعرّف، وإلا سيتعذّر إنشاء العنصر الأساسي.
الحدّ الأدنى من ضمانات الأمان
- يحتوي التوقيع الناتج على الخصائص نفسها التي يحتوي عليها التوقيع الذي تم إنشاؤه باستخدام العنصر الأساسي التوقيع الرقمي مع نوع المفتاح نفسه.
- تضيف Tink بادئة إلى قيمة التجزئة المسبقة تتألف من 5 بايتات تحتوي على قيمة خاصة محجوزة ومعرّف المفتاح الذي تم احتسابها له. توقّع
SignPrehashالقيمة باستخدام هذا المفتاح فقط. يعتمد ما إذا كانت القيمة مرتبطة بالتشفير بهذا المفتاح على الخوارزمية. بالنسبة إلى External Mu ML-DSA، تكون القيمة مرتبطة بالمفتاح، راجِع تنسيق Tink السلكي. - يمكن أن يكون طول الرسائل عشوائيًا.
أمور يجب الانتباه إليها
- لا يمكن للموقِّع فحص ما يوقِّع عليه. يمكن لأي شخص الاتصال بـ "
SignPrehash" الحصول على رسالة موقعة بشكل عشوائي، ولا يمكن للموقّع تطبيق سياسة على محتوى الرسالة. يجب حماية إمكانية الوصول إلىSignPrehashتمامًا كما تحمي إمكانية الوصول إلىPublicKeySign. - حماية القيمة قبل التجزئة أثناء نقلها وهي ليست سرية، ولكن يمكن للمهاجم الذي يمكنه تعديلها أثناء التنقل أن يتحكّم في ما يتم توقيعه.
اختيار نوع المفتاح
خوارزمية ML-DSA في وضع External Mu، كما هو موضّح في RFC 9881، هي الخوارزمية الوحيدة التي تتيحها Tink لوظيفتَي Prehash وSignPrehash. يجب استخدام مفتاح ML-DSA عادي مع هذه العناصر الأساسية.
ننصح باستخدام ML_DSA_44 لمعظم حالات الاستخدام.
يجب أن يتضمّن المفتاح شرطًا بشأن المعرّف، لأنّ كل قيمة مجزّأة مسبقًا تبدأ ببادئة تحتوي على معرّف المفتاح الذي تم احتسابها له. يتم قبول الصيغ التالية:
TINK-- تبدأ التوقيع الناتج بالبادئة المعتادة المكوّنة من 5 بايتات في Tink.NO_PREFIX_WITH_PREHASH_ID-- لا يتضمّن التوقيع الناتج بادئة ، بينما يظل المفتاح يتضمّن المعرّف الذي تحتاجه قيمة prehash.
لا تتوافق المفاتيح التي تستخدم صيغة NO_PREFIX (الأولية)، لأنّه ليس لديها معرّف مفتاح.