API của password.es đã phản hồi: dùng thế nào

Đăng ngày bởi David Carrero

Blog này đã nhiều tháng lặp lại một điều: mật khẩu của bạn không nên rời khỏi trình duyệt. Trình tạo mật khẩu lấy tính ngẫu nhiên từ crypto.getRandomValues() ngay trên máy của bạn, còn trình kiểm tra phân tích những gì bạn gõ mà không gửi đi đâu cả.

Và hôm nay chúng tôi công bố một API tạo mật khẩu trên máy chủ.

Mâu thuẫn là rõ ràng và chúng tôi sẽ không che đậy: mỗi phản hồi của API đều mang một lời cảnh báo nói đúng điều đó. Nhưng có những chỗ không có trình duyệt — một script tạo một trăm tài khoản, một máy chủ sinh thông tin đăng nhập tạm thời, một trợ lý bị ai đó hỏi xin mật khẩu và tự bịa ra — và ở đó, lựa chọn thay thế không phải là «trình duyệt»: đó là một random() chọn sai hoặc một chuỗi gõ bằng tay. API này sinh ra cho việc đó.

Toàn bộ lệnh gọi

Không khóa, không đăng ký, không tiêu đề. Chỉ có vậy:

curl -X POST https://api.password.es/v1/generate

Nó trả về một mật khẩu 16 ký tự cùng phần phân tích. Chạy trong lúc viết đoạn này: x*7$9,BUy9PPvOI?, lấy từ bảng chữ cái 89 ký tự, 103,6 bit entropy và thời gian bẻ khóa 2,5 × 10¹¹ năm. Mức 4 trên 4.

Không con số nào trong đó là mới. Chúng đến từ đúng bộ máy vẽ thanh đo ở trang chủ: cùng công thức bit, cùng mô hình tấn công — 10¹² lần đoán mỗi giây, ngoại tuyến, hàm băm nhanh — và cùng thang mức độ với trình kiểm tra. Nếu trang web và API cho ra những con số khác nhau cho cùng một mật khẩu thì một trong hai đang nói dối.

Nó trả về gì, từng trường một

Phản hồi mang passwords — luôn là một mảng, kể cả khi bạn chỉ xin một — và một khối analysis gồm length, pool, bits, log10_guesses, crack_time_log10_seconds, crack_time, level, level_scaleceiling.

Hai trong số đó đáng một đoạn riêng. level_scale cho biết mức độ đến từ đâu (hiện luôn là "time"), để nếu sau này trang web và API lệch nhau thì điều đó hiện ra ngay ở trường dữ liệu thay vì phải suy ra bằng cách so sánh các con số. Còn ceiling cho biết con số là chính xác hay chỉ là mức trần: trong generate nó luôn là false, vì mật khẩu do máy chủ tạo ra và nó biết dùng bảng chữ cái nào; ở trình kiểm tra trên web thì không phải lúc nào cũng vậy, vì ở đó mật khẩu là do bạn mang tới.

Sau đó là notice, lời cảnh báo, và một khối _meta với gói dịch vụ, ngôn ngữ đã áp dụng, các giới hạn và một liên kết về tài liệu.

Các tùy chọn

Tất cả đều không bắt buộc, và tên để nguyên tiếng Anh vì người tích hợp một API sẽ gõ y hệt tên trường: length (4–64, mặc định 16), count (1–20), lower, upper, digits, symbols, exclude_ambiguous, no_repeatslang.

curl -X POST https://api.password.es/v1/generate -H 'content-type: application/json' -d '{"length":20,"exclude_ambiguous":true}'

Hai chi tiết mà tài liệu có nói và nên biết trước khi tích hợp. exclude_ambiguous bỏ sáu ký tự chứ không phải bảy: nó loại 0 O 1 I l | o, nhưng dấu gạch đứng vốn không có trong bộ ký hiệu ngay từ đầu, nên bảng chữ cái giảm từ 89 xuống 83 chứ không phải 82. Vì vậy phản hồi ghi pool là 83. Và no_repeats không phải là bảo đảm tuyệt đối: nó thử lại mười lần, y như trình tạo trên web, và ở 64 ký tự thì một cặp lặp liền kề vẫn lọt qua khoảng 0,1 % số lần. Nói ra như vậy hữu ích hơn là hứa điều ngược lại.

Ngôn ngữ

Mặc định nó trả lời bằng tiếng Anh, đúng như người tích hợp mà không nói gì sẽ mong đợi. Đổi bằng ?lang= trên URL, bằng "lang" trong thân yêu cầu hoặc bằng tiêu đề Accept-Language, và nếu mâu thuẫn thì cái đầu tiên thắng. Một ngôn ngữ không tồn tại không phải là lỗi: nó rơi về tiếng Anh.

Ở đây có một sự bất đối xứng không thể đoán ra, và vì thế API tự khai báo: thông điệp có tiếng Anh và tiếng Tây Ban Nha; còn liên kết thì có đủ mười tám ngôn ngữ của trang. Xin tiếng Đức sẽ cho bạn liên kết tiếng Đức và thông điệp vẫn bằng tiếng Anh. Không cần phải đoán: _meta.lang mang { "messages", "links" } cho biết mỗi nửa đã được áp dụng gì.

Máy chủ MCP

MCP là giao thức để một trợ lý — Claude, ChatGPT — dùng công cụ bên ngoài. https://api.password.es/mcp là một máy chủ MCP không đăng ký và không khóa, và nó công bố đúng một công cụ: generate_password, với các tham số như trên.

Đây là phần chúng tôi quan tâm nhất, và không phải vì lý do kỹ thuật: một trợ lý không có chỗ nào để lấy mật khẩu sẽ tự bịa ra, và thứ ra đời từ đó không hề ngẫu nhiên, nó là thứ mà mô hình cho là trông giống mật khẩu. Khi nối máy chủ này, trợ lý thôi ứng biến và trả về một mật khẩu tạo bằng crypto.getRandomValues(), kèm phân tích và kèm lời cảnh báo nằm trong mô tả công cụ lẫn trong từng kết quả. Chính lời cảnh báo đó là thứ trợ lý cuối cùng đọc lại cho người đã hỏi xin mật khẩu, và đó là lý do nó có mặt.

Nếu bạn đến từ các máy chủ MCP khác, một điều thôi: máy chủ này không có trạng thái. POST được trả lời bằng JSON và không mở luồng nào, Mcp-Session-Id không được phát ra cũng không được chờ đợi, một thông báo nhận về 202 không có thân, và GET trả về 405. Đặc tả 2025-06-18 cho phép điều đó một cách rõ ràng, nhưng ai chờ phiên làm việc và SSE sẽ gỡ lỗi trong mù mờ nếu không ai nói.

Những gì nó không làm

/v1/check trả về 501. Đó không phải sơ suất hay một endpoint làm dở: nó cố ý như vậy, và chính phản hồi giải thích vì sao. Trả về đúng những con số của trình kiểm tra đòi hỏi đúng bộ máy nhận dạng mẫu đang chạy trên web, và bộ máy đó tốn từ 11 ms đến 3,6 giây CPU mỗi yêu cầu tùy vào thứ được gửi lên. Với một dịch vụ không đăng ký và không khóa, khoảng chênh đó là một quyết định sản phẩm — đặt trần độ dài ở đâu — và vẫn chưa được chốt. Trong lúc chờ, mã 501 mang theo checker_url trỏ tới trình kiểm tra trên web bằng đúng ngôn ngữ bạn yêu cầu, nơi làm đúng việc này mà không gửi gì đi.

Tài khoản cũng chưa có, khóa cũng không, gói cước cũng không. Và vì chúng không tồn tại, trong toàn bộ API không có lấy một liên kết đăng ký nào: kể cả trong lỗi vượt giới hạn, chỗ mà ai cũng đặt một cái.

Các giới hạn

Một cái, và đó là cái thực sự được áp dụng: 60 yêu cầu mỗi phút cho mỗi IP. Không cần tin trang này: con số đi kèm trong từng phản hồi, ở _meta.limits.burst. Vượt quá thì nhận 429 kèm Retry-After và các tiêu đề RateLimit-*.

Trong _meta bạn còn thấy khối quota với các giá trị null. Đó là chỗ dành sẵn cho khi nào có tài khoản, và nó để trống một cách có chủ ý: một giới hạn được công bố mà không được thi hành còn tệ hơn là không công bố gì, vì người tích hợp sẽ tôn trọng nó và lập trình theo một con số chẳng ai đếm.

Và dù vậy, hãy cân nhắc hai lần

Một API tạo mật khẩu, xét cho cùng, là một phản mẫu: mật khẩu đi qua mạng và đi qua một cái máy không phải của bạn. Việc chúng tôi không lưu gì không làm thay đổi hình dạng của vấn đề, chỉ thay đổi phần của chúng tôi trong đó — và bạn đã biết một lời hứa không kiểm chứng được thì đáng giá bao nhiêu.

Vì thế lời cảnh báo không chỉ sống trên trang này: nó đi theo từng phản hồi, bằng ngôn ngữ bạn yêu cầu. Và vì thế đoạn văn này nằm ở đây chứ không giấu ở cuối tài liệu. Với một mật khẩu bạn sẽ tự dùng, trình tạo của trang này chạy hoàn toàn trong trình duyệt của bạn và không gửi gì đi. API là cho việc khác: cho những gì diễn ra khi không có ai ngồi trước màn hình.

Chi tiết nằm ở hai trang. API là gì kể nó dành cho ai, hôm nay cái gì phản hồi và cái gì không. Tài liệu tham khảo có đủ chín tham số, mọi trường được giải thích từng cái một, các mã lỗi và các giới hạn; đó là trang bạn mở cạnh trình soạn thảo. Còn nếu bạn muốn máy đọc nó, đã có openapi.json.


Nguồn: chính API đó, đã kiểm chứng trên môi trường thật ngày 31 tháng 8 năm 2026 — POST /v1/generate, POST /v1/check (501), POST /mcpGET /openapi.json · các con số phân tích đến từ cùng bộ máy với trình tạo và trình kiểm tra của password.es · mô hình tấn công là 10¹² lần đoán mỗi giây, ngoại tuyến và với hàm băm nhanh, giống phần còn lại của trang · máy chủ MCP triển khai đặc tả 2025-06-18 với truyền tải Streamable HTTP không trạng thái.

Ảnh của Stanislav Kondratiev · Pexels

← Quay lại blog