Prehash وSignPrehash

تقسّم عناصر Prehash وSignPrehash الأساسية عملية احتساب التوقيع الرقمي إلى خطوتَين:

  1. لا يتطلّب Prehash سوى المفتاح العام. تحوّل هذه الدالة رسالة بأي طول إلى قيمة مسبقة التجزئة قصيرة وثابتة الحجم.
  2. يتطلّب 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_65 لمعظم حالات الاستخدام.

يجب أن يتضمّن المفتاح شرطًا بشأن المعرّف، لأنّ كل قيمة تجزئة مسبقة تبدأ ببادئة تحتوي على معرّف المفتاح الذي تم احتسابها له. يتم قبول الصيغ التالية:

  • TINK -- تبدأ التوقيع الناتج ببادئة Tink المعتادة المكوّنة من 5 بايتات.
  • NO_PREFIX_WITH_PREHASH_ID -- لا يحمل التوقيع الناتج أي بادئة للإخراج، بينما يظل المفتاح يتضمّن المعرّف الذي تحتاجه قيمة prehash.

لا تتوافق المفاتيح التي تستخدم صيغة NO_PREFIX (الأولية)، لأنّها لا تتضمّن معرّف مفتاح.

أمثلة

توقّع الأمثلة التالية رسالة في خطوتَين ثم تتحقّق من التوقيع الناتج باستخدام عنصر التوقيع الرقمي العادي.

ويتم عرضها كمجموعة واحدة لتسهيل قراءتها. في عملية نشر حقيقية، يتم تنفيذ الخطوتَين في أماكن مختلفة، راجِع أريد توقيع البيانات باستخدام خدمة توقيع عن بُعد للحصول على شرح تفصيلي.

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
}