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_44 لمعظم حالات الاستخدام.

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

  • TINK -- تبدأ التوقيع الناتج بالبادئة المعتادة المكوّنة من 5 بايتات في Tink.
  • 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
}

Python

from tink import signature

# Needed for PublicKeyVerify in step 3. 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.
signature.register()

# 1. Wherever the message is: compute the prehash value. This needs only
#    the public keyset.
prehasher = public_handle.primitive(signature.Prehash)
prehash = prehasher.compute(message)

# 2. Wherever the private key is: turn the prehash value into a signature.
signer = private_handle.primitive(signature.SignPrehash)
sig = signer.sign(prehash)

# 3. Anywhere: verify the signature over the original message.
verifier = public_handle.primitive(signature.PublicKeyVerify)
verifier.verify(sig, message)