Tôi muốn ký dữ liệu bằng một trình ký từ xa

Bạn nên dùng các nguyên hàm Prehash và SignPrehash với khoá ML_DSA_65 khi khoá riêng tư nằm ở nơi không thể gửi thông báo.

Đôi khi, bên giữ khoá ký không thể (hoặc không nên) nhận chính thông báo: khoá nằm trong HSM hoặc KMS, hoặc thông báo lớn hơn giới hạn kích thước yêu cầu của người ký.

Các nguyên tắc cơ bản Prehash và SignPrehash giải quyết vấn đề này bằng cách chia quy trình ký thành hai bước. Bạn tính toán một giá trị tiền băm ngắn tại nơi có thông báo, chỉ sử dụng khoá công khai và gửi giá trị đó cho người ký. Người ký sẽ chuyển thông báo này thành chữ ký bằng khoá riêng tư mà không cần xem nội dung thông báo. Với ML-DSA ở chế độ External Mu (thuật toán mà Tink hỗ trợ ở đây), giá trị tiền băm là 69 byte, bất kể kích thước của thông báo.

Chữ ký mà bạn nhận được là một chữ ký thông thường trên thông báo ban đầu: trình xác minh sử dụng nguyên tắc cơ bản Chữ ký số thông thường và không cần biết rằng có hai bước liên quan.

Trước khi bắt đầu

Tạo một khoá ML-DSA có các khoá có yêu cầu về mã nhận dạng – biến thể TINK hoặc NO_PREFIX_WITH_PREHASH_ID nếu bạn không muốn có tiền tố đầu ra trên chữ ký kết quả. Cung cấp cho người ký bộ khoá riêng tư và cung cấp cho phía tiền băm bộ khoá công khai tương ứng. Trình ký phải có một khoá đã bật cho mọi mã khoá mà phía tiền băm có thể tạo ra; hãy xem Keysets.

Bước 1: Tính giá trị tiền băm

Chạy mã này ở bất cứ nơi nào có thông báo. Mã này chỉ cần keyset công khai.

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
}

Bước 2: Gửi giá trị tiền băm đến trình ký

Gửi giá trị tiền băm đến bất kỳ nơi nào lưu giữ khoá riêng tư. Khoá này không phải là khoá bí mật, nhưng bạn phải bảo vệ tính toàn vẹn của khoá trong quá trình truyền: kẻ tấn công có thể sửa đổi khoá trong quá trình truyền sẽ kiểm soát nội dung được ký.

Bước 3: Ký giá trị tiền băm

Chạy lệnh này ở bất cứ nơi nào có khoá riêng tư.

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
}

Bước 4: Xác minh chữ ký

Xác minh là quy trình Chữ ký số thông thường đối với thông báo ban đầu, chứ không phải đối với giá trị tiền băm.

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 và SignPrehash

Các nguyên tắc Prehash và SignPrehash chia quá trình tính toán chữ ký số thành hai bước:

  1. Prehash chỉ cần khoá công khai. Hàm này chuyển đổi một thông báo có độ dài bất kỳ thành một giá trị tiền băm ngắn, có kích thước cố định.
  2. SignPrehash cần khoá riêng tư. Thao tác này chuyển đổi giá trị tiền băm thành chữ ký.

Chữ ký nhận được là chữ ký thông thường trên thông báo gốc. Bạn xác minh chữ ký đó bằng nguyên tắc cơ bản Chữ ký số PublicKeyVerify thông thường và người xác minh không cần phải biết (hoặc quan tâm) đến việc chữ ký được tạo trong hai bước.

Coi giá trị tiền băm là các byte không rõ ràng. Khoá này có kích thước cố định và mang mã nhận dạng của khoá mà khoá được tính toán, nhưng bố cục của khoá là một phần của định dạng truyền dữ liệu của Tink và bạn không nên tự phân tích cú pháp hoặc tạo khoá. Nếu bạn đang chuyển Tink hoặc cần thông tin chi tiết ở cấp độ byte, hãy xem Định dạng truyền dữ liệu của Tink.

Hãy sử dụng cặp thành phần cơ bản này khi:

  • Khoá ký nằm ở một nơi khác, chẳng hạn như trong HSM, trong KMS hoặc sau ranh giới RPC và bạn không muốn gửi toàn bộ thông báo qua ranh giới đó.
  • Thông báo có kích thước lớn và trình ký từ xa áp dụng giới hạn kích thước yêu cầu.

Nếu không có trường hợp nào trong số này áp dụng, hãy sử dụng nguyên tắc cơ bản Chữ ký số đơn giản: nguyên tắc này đơn giản hơn và khó bị sử dụng sai mục đích hơn.

Bộ khoá

Hai nguyên hàm này chọn khoá theo cách khác nhau, giống như cách ký và xác minh đối với nguyên hàm Chữ ký số:

  • Prehash.Compute luôn sử dụng khoá chính của bộ khoá công khai và ghi lại mã nhận dạng của khoá đó trong giá trị tiền băm. Đây là bên chọn khoá mà chữ ký sẽ được tạo.
  • SignPrehash.Sign đọc mã khoá từ giá trị tiền băm và ký bằng khoá đã bật khớp của bộ khoá riêng tư. Đó là bên tuân theo lựa chọn mà người khác đã đưa ra. Nếu không có khoá nào được bật trong keyset có mã nhận dạng đó, thì lệnh gọi sẽ không thành công.

Mọi khoá trong một bộ khoá SignPrehash đều phải có yêu cầu về mã nhận dạng, nếu không, việc tạo nguyên thuỷ sẽ không thành công.

Đảm bảo an ninh tối thiểu

  • Chữ ký kết quả có các thuộc tính tương tự như chữ ký do nguyên tắc cơ bản Chữ ký số tạo ra với cùng loại khoá.
  • Tink thêm 5 byte vào giá trị tiền băm, trong đó có một giá trị đặc biệt được dành riêng và mã nhận dạng của khoá mà giá trị đó được tính toán. SignPrehash chỉ ký giá trị bằng khoá đó. Việc giá trị có được liên kết theo phương pháp mã hoá với khoá đó hay không phụ thuộc vào thuật toán; đối với External Mu ML-DSA, giá trị này được liên kết, hãy xem định dạng truyền dữ liệu Tink.
  • Tin nhắn có thể có độ dài tuỳ ý.

Những điều cần chú ý

  • Người ký không thể kiểm tra nội dung mà họ ký. Bất kỳ ai có thể gọi SignPrehash đều có thể nhận được một thông báo tuỳ ý đã ký và người ký không có cách nào áp dụng chính sách cho nội dung thông báo. Bảo vệ quyền truy cập vào SignPrehash giống như cách bạn bảo vệ quyền truy cập vào PublicKeySign.
  • Bảo vệ giá trị được băm trước trong quá trình truyền. Đây không phải là bí mật, nhưng kẻ tấn công có thể sửa đổi nó trong quá trình truyền dữ liệu sẽ kiểm soát nội dung được ký.

Chọn loại khoá

ML-DSA ở chế độ External Mu (Mu bên ngoài), như mô tả trong RFC 9881, là thuật toán duy nhất mà Tink hỗ trợ cho Prehash và SignPrehash. Bạn nên sử dụng khoá ML-DSA tiêu chuẩn với các thành phần cơ bản này.

Bạn nên dùng ML_DSA_65 cho hầu hết các trường hợp sử dụng.

Khoá phải có yêu cầu về mã nhận dạng, vì mọi giá trị tiền băm đều bắt đầu bằng một tiền tố chứa mã nhận dạng của khoá mà giá trị đó được tính toán. Các biến thể sau được chấp nhận:

  • TINK – chữ ký kết quả bắt đầu bằng tiền tố đầu ra 5 byte thông thường của Tink.
  • NO_PREFIX_WITH_PREHASH_ID – chữ ký kết quả không có tiền tố đầu ra, trong khi khoá vẫn có mã nhận dạng mà giá trị tiền xử lý cần.

Không được hỗ trợ các khoá sử dụng biến thể NO_PREFIX (thô) vì các khoá này không có mã khoá.