أريد توقيع البيانات باستخدام خدمة توقيع عن بُعد

ننصح باستخدام العنصرَين الأساسيَّين Prehash وSignPrehash مع مفتاح ML_DSA_65 عندما يكون المفتاح الخاص مخزَّنًا في مكان لا يمكن إرسال الرسالة منه.

في بعض الأحيان، لا يمكن للجهة التي تحتفظ بمفتاح التوقيع استلام الرسالة نفسها، أو لا يُفترض أن تستلمها، لأنّ المفتاح يكون مخزّنًا في وحدة أمان الأجهزة (HSM) أو خدمة إدارة المفاتيح (KMS)، أو لأنّ حجم الرسالة أكبر من الحدّ الأقصى لحجم الطلب الذي يمكن أن يقدّمه الموقّع.

تعمل الدالتان الأساسيتان Prehash وSignPrehash على حلّ هذه المشكلة من خلال تقسيم عملية التوقيع إلى خطوتَين. تحسب قيمة قصيرة مسبقة التجزئة حيث توجد الرسالة، باستخدام المفتاح العام فقط، وترسلها إلى الموقّع. يحوّل الموقّع الرسالة إلى توقيع باستخدام المفتاح الخاص بدون أن يرى الرسالة مطلقًا. في وضع ML-DSA في External Mu، وهو الوضع الذي يتيحه خوارزمية Tink، تبلغ قيمة prehash 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
}

الخطوة 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
}

الخطوة 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
}

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 (الأولية)، لأنّها لا تتضمّن معرّف مفتاح.