
CX
29/9/2026
Tóm tắt
blog.share_blog
@cogover/sdk: Mã hoá và chữ kýTrang này thuộc API reference của @cogover/sdk.
crypto cung cấp hash, message authentication, số ngẫu nhiên, mã hoá AES và RSA,
cùng chữ ký RSA và ECDSA qua implementation do Cogover quản lý. Import từ
@cogover/sdk hoặc đọc từ context.crypto; cả hai là cùng một object đã freeze.
const crypto: CryptoApi;
type BinaryEncoding = "base64" | "base64url" | "hex";
type DecryptOutput = "utf8" | "bytes" | BinaryEncoding;
interface SecretKeyReference {
readonly secret: string;
}
type AesKey =
| string
| Uint8Array
| (SecretKeyReference & { readonly encoding?: "utf8" | "base64" | "hex" });
type AsymmetricKey = string | SecretKeyReference;
interface CryptoApi {
sha256(data: string | Uint8Array, encoding?: "hex" | "base64"): Promise<string>;
hmacSha256(
key: string | Uint8Array | { readonly secret: string },
data: string | Uint8Array,
encoding?: "hex" | "base64",
): Promise<string>;
randomBytes(length: number): Promise<Uint8Array>;
randomUUID(): Promise<string>;
timingSafeEqual(a: string | Uint8Array, b: string | Uint8Array): boolean;
aesEncrypt(key: AesKey, data: string | Uint8Array, options?: AesEncryptOptions): Promise<AesEncryptResult>;
aesDecrypt(key: AesKey, ciphertext: string | Uint8Array, options: AesDecryptOptions): Promise<string>;
aesDecrypt(
key: AesKey,
ciphertext: string | Uint8Array,
options: AesDecryptOptions & { readonly output: "bytes" },
): Promise<Uint8Array>;
rsaEncrypt(publicKey: AsymmetricKey, data: string | Uint8Array, options?: RsaEncryptOptions): Promise<string>;
rsaDecrypt(privateKey: AsymmetricKey, ciphertext: string | Uint8Array, options?: RsaDecryptOptions): Promise<string>;
rsaDecrypt(
privateKey: AsymmetricKey,
ciphertext: string | Uint8Array,
options: RsaDecryptOptions & { readonly output: "bytes" },
): Promise<Uint8Array>;
sign(
algorithm: SignatureAlgorithm,
privateKey: AsymmetricKey,
data: string | Uint8Array,
options?: SignatureOptions,
): Promise<string>;
verify(
algorithm: SignatureAlgorithm,
publicKey: AsymmetricKey,
data: string | Uint8Array,
signature: string | Uint8Array,
options?: SignatureOptions,
): Promise<boolean>;
}
aesDecrypt và rsaDecrypt trả về chuỗi, trừ khi output là "bytes"; khi
output chỉ được biết là một DecryptOutput, kiểu kết quả là
string | Uint8Array.
import { crypto } from "@cogover/sdk";
const digest = await crypto.sha256(JSON.stringify(payload));
const signature = await crypto.hmacSha256({ secret: "webhook_secret" }, request.rawBody ?? "");
if (!crypto.timingSafeEqual(signature, request.headers["x-signature"] ?? "")) {
return response.empty({ status: 401 });
}
const token = await crypto.randomUUID();sha256(data, encoding?) trả về digest SHA-256 của data. Chuỗi được hash dưới
dạng UTF-8; Uint8Array được hash nguyên trạng. encoding là "hex" (mặc định)
hoặc "base64".
hmacSha256(key, data, encoding?) trả về HMAC-SHA256 của data. Key là chuỗi
không rỗng (UTF-8), Uint8Array không rỗng, hoặc { secret: name } để dùng một
secret của project mà không đọc giá trị.
randomBytes(length) trả về length byte ngẫu nhiên an toàn cho mật mã;
length là số nguyên từ 1 đến 1024.
randomUUID() trả về UUID version 4 ngẫu nhiên như
"3f9c2d6e-8b1a-4c7d-9e2f-1a2b3c4d5e6f". Byte ngẫu nhiên được lấy mỗi lần
1.024 byte, nên một capability call đủ cho 64 UUID.
timingSafeEqual(a, b) so sánh hai chuỗi hoặc hai mảng byte trong thời gian
hằng và trả về false khi độ dài khác nhau. Chuỗi được so sánh dưới dạng byte
UTF-8. Mỗi giá trị giới hạn 256 KiB, chuỗi tính theo số byte UTF-8; giá trị lớn
hơn ném ValidationError. Dùng hàm này cho mọi phép so sánh chữ ký, token hoặc
key.
Mọi key có thể truyền trực tiếp trong lời gọi hoặc dưới dạng { secret: name }.
Key từ secret do Cogover resolve và giá trị không bao giờ tới code của project;
secret được đặt tên phải đọc được bằng secrets.get trong cùng context và được
tính là một lần đọc secret. Hãy lưu mọi private key và mọi AES key thành secret của
project; key viết trong source code hiển thị với mọi người đọc được project.
Uint8Array được dùng nguyên trạng. Với secret,
encoding cho biết giá trị secret chứa key ra sao: "utf8" (mặc định) dùng chính
văn bản đó, còn "base64" hoặc "hex" giải mã trước, bỏ qua khoảng trắng ở hai
đầu.
AsymmetricKey) là văn bản PEM, hoặc phần thân base64 của
PEM không có các dòng -----BEGIN (DER dạng PUBLIC KEY cho public key và dạng
PRIVATE KEY cho private key). Văn bản đứng trước khối PEM và khối
EC PARAMETERS được bỏ qua.
| Key | Loại PEM được chấp nhận |
|---|---|
Public key (rsaEncrypt, verify) |
PUBLIC KEY (SubjectPublicKeyInfo), RSA PUBLIC KEY (PKCS #1), CERTIFICATE (X.509; chỉ dùng public key của nó) |
Private key (rsaDecrypt, sign) |
PRIVATE KEY (PKCS #8), RSA PRIVATE KEY (PKCS #1), EC PRIVATE KEY (SEC1) |
RSA key phải dài 2048 đến 4096 bit. EC key phải dùng curve P-256, P-384 hoặc P-521.
Private key đã mã hoá (ENCRYPTED PRIVATE KEY, hoặc PEM có header Proc-Type) và
key OpenSSH không được hỗ trợ; hãy lưu private key PKCS #8 chưa mã hoá thành secret.
Văn bản key giới hạn 16.384 ký tự. Certificate không được kiểm tra hạn dùng, thu hồi
hay bên phát hành: nó chỉ là vật chứa public key.
crypto.aesEncrypt(key, data, options?): Promise<AesEncryptResult>type AesMode = "GCM" | "CBC";
interface AesEncryptOptions {
readonly mode?: AesMode; // Mặc định "GCM"
readonly iv?: string | Uint8Array; // Mặc định: ngẫu nhiên
readonly aad?: string | Uint8Array; // Chỉ GCM
readonly separateTag?: boolean; // Chỉ GCM, mặc định false
readonly encoding?: BinaryEncoding; // Mặc định "base64"
}
interface AesEncryptResult {
readonly ciphertext: string;
readonly iv: string;
readonly tag?: string; // Chỉ khi có separateTag
}
Mã hoá data (chuỗi được mã hoá dạng UTF-8) và trả về ciphertext cùng IV theo
encoding.
"GCM" (mặc định) là mã hoá có xác thực: khi giải mã sẽ phát hiện mọi thay đổi
của ciphertext, IV, tag hoặc aad. ciphertext là dữ liệu đã mã hoá, nối tiếp
bởi authentication tag 16 byte, đúng dạng mà Java, .NET và Web Crypto dùng. Với
separateTag: true, tag được trả riêng trong tag, như các hệ thống xây trên
Node.js hoặc OpenSSL thường yêu cầu.
"CBC" dùng padding PKCS #7. Mode này không phát hiện thay đổi của ciphertext;
chỉ dùng khi hệ thống bên ngoài bắt buộc, và xác thực ciphertext bằng cách khác,
ví dụ hmacSha256.
iv, một IV ngẫu nhiên được tạo: 12 byte cho GCM và 16 byte cho
CBC. iv truyền vào phải dài 12 đến 16 byte với GCM và đúng 16 byte với CBC;
iv dạng chuỗi được giải mã theo encoding. Không bao giờ dùng lại cùng một IV
với cùng một key GCM: việc đó phá vỡ cả tính bảo mật lẫn khả năng xác thực.
Chỉ truyền IV khi hệ thống bên ngoài quy định.
aad là dữ liệu bổ sung được xác thực nhưng không mã hoá, ví dụ ID của record;
chuỗi được mã hoá dạng UTF-8. Khi giải mã phải truyền đúng aad đó.
crypto.aesDecrypt(key, ciphertext, options): Promise<string | Uint8Array>interface AesDecryptOptions {
readonly mode?: AesMode; // Mặc định "GCM"
readonly iv: string | Uint8Array;
readonly tag?: string | Uint8Array; // Chỉ GCM, khi tag không nối vào ciphertext
readonly aad?: string | Uint8Array; // Chỉ GCM
readonly encoding?: BinaryEncoding; // Của ciphertext, iv và tag dạng chuỗi; mặc định "base64"
readonly output?: DecryptOutput; // Mặc định "utf8"
}
Giải mã một ciphertext AES. iv là bắt buộc. Ở mode GCM, 16 byte cuối của
ciphertext là tag, trừ khi truyền tag. output chọn dạng kết quả: "utf8"
(mặc định) giải mã plaintext thành văn bản UTF-8 và ném ValidationError nếu nó
không phải UTF-8 hợp lệ, "bytes" trả về Uint8Array, còn "base64",
"base64url" hoặc "hex" trả về byte của plaintext theo encoding đó.
Sai key, IV, tag hoặc aad, ciphertext bị sửa, hoặc padding CBC không hợp lệ đều
ném CogoverApiError với code: "DECRYPTION_FAILED". Lỗi giống nhau cho mọi
nguyên nhân để kết quả không làm lộ thông tin về key hay plaintext.
import { crypto, CogoverApiError, defineScript } from "@cogover/sdk";
export default defineScript(async ({ request }) => {
const { data, iv, tag } = request.body as { data: string; iv: string; tag: string };
try {
const json = await crypto.aesDecrypt({ secret: "partner_aes_key", encoding: "base64" }, data, { iv, tag });
return { payment: JSON.parse(json) };
} catch (error) {
if (error instanceof CogoverApiError && error.code === "DECRYPTION_FAILED") {
return { r: 1001, msg: "The payload could not be decrypted." };
}
throw error;
}
});crypto.rsaEncrypt(publicKey, data, options?) và crypto.rsaDecrypt(privateKey, ciphertext, options?)type RsaPadding = "OAEP-SHA256" | "OAEP-SHA1" | "PKCS1";
interface RsaEncryptOptions {
readonly padding?: RsaPadding; // Mặc định "OAEP-SHA256"
readonly encoding?: BinaryEncoding; // Của ciphertext; mặc định "base64"
}
interface RsaDecryptOptions {
readonly padding?: RsaPadding; // Mặc định "OAEP-SHA256"
readonly encoding?: BinaryEncoding; // Của ciphertext dạng chuỗi; mặc định "base64"
readonly output?: DecryptOutput; // Mặc định "utf8"
}
rsaEncrypt mã hoá data (chuỗi được mã hoá dạng UTF-8) bằng RSA public key và
trả về ciphertext theo encoding. rsaDecrypt giải mã bằng private key tương ứng
và trả về plaintext theo output, giống aesDecrypt.
"OAEP-SHA256" (mặc định) là RSA-OAEP với SHA-256, và hàm sinh mask MGF1 cũng
dùng SHA-256, như Web Crypto, Node.js và OpenSSL. "OAEP-SHA1" dùng SHA-1 cho
cả hai. Một số code Java viết OAEPWithSHA-256AndMGF1Padding vẫn để MGF1 dùng
SHA-1 và không tương thích với cả hai lựa chọn; hãy hỏi hệ thống bên kia MGF1 của
họ dùng digest nào.
"PKCS1" là padding PKCS #1 v1.5. Chỉ dùng khi hệ thống bên ngoài bắt buộc."OAEP-SHA256", trừ 42 với "OAEP-SHA1" và trừ 11 với
"PKCS1" — tức 190, 214 và 245 byte với key 2048 bit. Dữ liệu lớn hơn ném
ValidationError. Để gửi nhiều hơn, mã hoá dữ liệu bằng aesEncrypt với một key
ngẫu nhiên từ randomBytes(32) và chỉ mã hoá key đó bằng rsaEncrypt.
CogoverApiError với
code: "DECRYPTION_FAILED".
const cardToken = await crypto.rsaEncrypt({ secret: "bank_public_key" }, "4111111111111111");
const plain = await crypto.rsaDecrypt({ secret: "our_private_key" }, request.body.data as string);crypto.sign(algorithm, privateKey, data, options?) và crypto.verify(algorithm, publicKey, data, signature, options?)type SignatureAlgorithm =
| "RSA-SHA1" | "RSA-SHA256" | "RSA-SHA384" | "RSA-SHA512"
| "RSA-PSS-SHA256" | "RSA-PSS-SHA384" | "RSA-PSS-SHA512"
| "ECDSA-SHA256" | "ECDSA-SHA384" | "ECDSA-SHA512";
interface SignatureOptions {
readonly encoding?: BinaryEncoding; // Của chữ ký; mặc định "base64"
readonly signatureFormat?: "der" | "ieee-p1363"; // Chỉ ECDSA; mặc định "der"
}
sign ký data (chuỗi được mã hoá dạng UTF-8) bằng private key và trả về chữ ký
theo encoding. verify trả về true khi signature là chữ ký hợp lệ của data
với public key, và false trong mọi trường hợp khác, kể cả khi chữ ký không giải
mã được hoặc sai độ dài; chữ ký thường đến từ chính bên cần xác minh nên chữ ký sai
định dạng không phải là lỗi. Chữ ký dài hơn 2.048 ký tự, hoặc 1.024 byte khi là
Uint8Array, được coi là không hợp lệ mà không được gửi đi. Key hoặc thuật toán không
hợp lệ vẫn ném ValidationError, bất kể chữ ký.
RSA-SHA* là RSASSA-PKCS1-v1_5 và cần RSA key. RSA-SHA1 chỉ để xác minh chữ ký
của các hệ thống còn dùng nó; không dùng cho chữ ký mới.
RSA-PSS-SHA* là RSASSA-PSS với MGF1 cùng digest và salt dài bằng digest (32, 48
hoặc 64 byte), và cần RSA key.
ECDSA-SHA* cần EC key. signatureFormat là "der" (ASN.1, dùng bởi OpenSSL và
Java) hoặc "ieee-p1363" (dạng r || s độ dài cố định, dùng bởi JWT và Web
Crypto); tuỳ chọn này bị từ chối với thuật toán RSA.
JWT alg |
algorithm |
Options |
|---|---|---|
RS256, RS384, RS512 |
RSA-SHA256, RSA-SHA384, RSA-SHA512 |
{ encoding: "base64url" } |
PS256, PS384, PS512 |
RSA-PSS-SHA256, RSA-PSS-SHA384, RSA-PSS-SHA512 |
{ encoding: "base64url" } |
ES256, ES384, ES512 |
ECDSA-SHA256, ECDSA-SHA384, ECDSA-SHA512 |
{ encoding: "base64url", signatureFormat: "ieee-p1363" } |
// Xác minh JWT ES256 do đối tác phát hành. Kiểm tra thêm claim (exp, iss, aud) trước khi tin cậy.
const [header, payload, signature] = token.split(".");
const valid = await crypto.verify("ECDSA-SHA256", { secret: "partner_jwt_public_key" },
`${header}.${payload}`, signature ?? "", { encoding: "base64url", signatureFormat: "ieee-p1363" });
if (!valid) return response.empty({ status: 401 });
// Ký request gửi ngân hàng yêu cầu SHA256withRSA dạng base64.
const body = JSON.stringify(order);
const bankSignature = await crypto.sign("RSA-SHA256", { secret: "bank_signing_key" }, body);
data, aad và HMAC key tường minh giới hạn 256 KiB, ciphertext giới hạn 256 KiB
cộng tag; giá trị lớn hơn và đối số không hợp lệ ném ValidationError trước khi
gọi. Toàn bộ lời gọi cũng phải vừa một capability request, mặc định 262.144 byte (xem
Giới hạn của một lần thực thi): mọi đối số
được gửi trong một request đã mã hoá JSON, và Uint8Array được gửi dạng base64, lớn
hơn một phần ba. Vì vậy dữ liệu nhị phân lớn hơn khoảng 190 KiB, hoặc dữ liệu text gần
256 KiB cùng các đối số khác, sẽ ném ValidationError trước khi gọi dù từng giá trị vẫn
trong giới hạn riêng của nó. Các lỗi về key, thuật toán và tuỳ chọn do Cogover phát hiện, như key sai kích
thước hoặc sai loại, cũng ném ValidationError. Mọi thao tác trừ timingSafeEqual
là một lời gọi cục bộ và được tính vào ngân sách lời gọi cục bộ của invocation, không
tính vào capability call (xem Giới hạn của một lần thực thi);
randomUUID gọi một lần cho mỗi 64 UUID.
Các thao tác hoạt động trong mọi context, kể cả trigger before-change, khi key được
truyền trực tiếp trong lời gọi. Key { secret } tuân theo quy tắc của
secrets.get: trigger before-change bị từ chối bằng PermissionDeniedError
(details.reason === "TRIGGER_READ_ONLY"), còn phiên phát triển local không có
quyền đọc secret bị từ chối với details.reason === "SECRETS_NOT_ALLOWED".

blog.blogs_related
Bắt đầu đổi mới phương thức vận hành và tự chủ hệ thống quản trị công việc của bạn
CÔNG TY CỔ PHẦN STRINGEE
Văn phòng