Tiếng Việt

Mã hóa, thoát và băm

Base64 không phải là mã hóa, btoa không phải là UTF-8, EncodeURI không phải là EncodeURIComponent và SHA-256 không phải là hàm băm mật khẩu. Đây là những gì mỗi điều này thực sự làm và những sai lầm cụ thể xảy ra khi giả định ngược lại.

Mã hóa không phải là mã hóa và không phải là nén

Mã hóa thay đổi cách dữ liệu được ghi lại. Mã hóa thay đổi ai có thể đọc nó. Nén thay đổi bao nhiêu không gian cần thiết. Đây là ba công việc khác nhau và base64 chỉ thực hiện công việc đầu tiên - thật tệ, nếu bạn đang hy vọng vào một trong hai công việc còn lại.

Base64 lấy ba byte mỗi lần và viết lại chúng thành bốn ký tự được rút ra từ bảng chữ cái ký hiệu 64. Bốn ký tự mang ba byte có nghĩa là đầu ra luôn lớn hơn đầu vào khoảng 33%, cộng với phần đệm. Nó tồn tại vì rất nhiều cơ sở hạ tầng — tiêu đề email, tiêu đề HTTP, giá trị chuỗi JSON, URL, thuộc tính XML — được thiết kế cho văn bản và đọc sai hoặc từ chối các byte tùy ý. Base64 là bộ chuyển đổi cho phép bạn đẩy byte qua một ống có hình văn bản.

Bất cứ ai cũng có thể đảo ngược nó ngay lập tức mà không cần chìa khóa, vì không có chìa khóa. Nếu bạn đặt 64 mật khẩu, bạn đã xuất bản mật khẩu ở định dạng hơi bất tiện. Điều này quan trọng vì đầu ra base64 trông có vẻ lộn xộn đối với mắt người, đây chính xác là đặc tính khiến mọi người tin tưởng nó vì những việc nó không thể làm được.

Tại sao btoa() bị hỏng và có hai cách khác nhau để phá vỡ

Trình duyệt cung cấp cho bạn btoa() và atob() và chúng cũ hơn các API văn bản hiện đại. btoa được xác định trên "chuỗi nhị phân": các chuỗi trong đó mỗi đơn vị mã là một byte đơn, từ 0 đến 255. Văn bản không phải vậy.

Thất bại đầu tiên là lớn. Gọi btoa("世界") và bạn nhận được InvalidCharacterError, vì U+4E16 không vừa với một byte. Những thất bại lớn là điều tốt - bạn nhận thấy chúng ngay lập tức và tìm cách khắc phục.

Thất bại thứ hai diễn ra âm thầm, chính là thất bại đến tay sản xuất. Ký tự é là U+00E9, vừa với một byte. Vì vậy, btoa("café") vui vẻ trả về, mã hóa é dưới dạng byte đơn 0xE9. Nhưng é trong UTF-8 là hai byte, 0xC3 0xA9. Base64 mà bạn vừa tạo sẽ giải mã, trong mọi hệ thống khác trên trái đất, thành nội dung không phải là văn bản của bạn. Vài tuần sau, bạn sẽ biết khi nào một tên trong cơ sở dữ liệu đã chuyển thành ký tự thay thế.

Cách khắc phục là ngừng coi văn bản dưới dạng byte và chuyển đổi nó một cách rõ ràng. TextEncode tạo ra UTF-8 byte; mã hóa chúng. TextDecoding biến byte trở lại thành văn bản và xây dựng nó bằng { fatal: true } khiến nó đưa ra các chuỗi không hợp lệ thay vì lặng lẽ thay thế U+FFFD, do đó, một giải mã không thể đúng sẽ thất bại thay vì trả về những điều vô nghĩa trông có vẻ hợp lý. Đó chính là quy trình mà bộ công cụ này sử dụng, đó là lý do tại sao biểu tượng cảm xúc, kết hợp các dấu và chữ viết từ phải sang trái đều hoàn toàn chính xác.

  1. Chuyển đổi văn bản thành byte bằng TextEncode - không bao giờ lập chỉ mục thành chuỗi.
  2. Mã hóa byte thành base64.
  3. Để đảo ngược: giải mã base64 thành byte, sau đó giải mã byte thành UTF-8 với fatal: true.
  4. Nếu bước UTF-8 không thành công thì tải trọng là nhị phân chứ không phải văn bản. Hiển thị nó dưới dạng hex thay vì giả vờ.

base64 so với base64url và câu hỏi về phần đệm

Base64 tiêu chuẩn sử dụng + và / làm hai ký hiệu cuối cùng. Cả hai đều có ý nghĩa trong URL: + có thể được đọc dưới dạng khoảng trắng được mã hóa trong chuỗi truy vấn và / là dấu phân cách đường dẫn. Vì vậy RFC 4648 xác định bảng chữ cái thứ hai, base64url, thay thế - và _ thay thế. JWT sử dụng nó, cũng như hầu hết các định dạng mã thông báo và nhiều API.

Phần đệm là biến khác. Các miếng đệm base64 tiêu chuẩn có = nên độ dài đầu ra luôn là bội số của bốn. base64url thường bỏ phần đệm, vì bản thân = là một ký tự khó xử trong URL và độ dài có thể được phục hồi về mặt số học. Bộ giải mã yêu cầu đệm sẽ từ chối các phân đoạn JWT hoàn toàn hợp lệ.

Lời khuyên thực tế: bộ giải mã của bạn nên chấp nhận cả hai bảng chữ cái và chấp nhận phần đệm bị thiếu, vì bạn hiếm khi kiểm soát được những gì mình được giao. Bộ mã hóa của bạn phải nêu rõ nội dung nó phát ra vì người nhận có thể quan tâm. Tiện ích base64 ở đây thực hiện chính xác điều đó — nó chấp nhận mọi thứ hợp lý và cho phép bạn chọn chính xác những gì nó tạo ra.

EncodeURI và EncodeURIComponent: sự khác biệt trong một câu

Cả hai đều mã hóa phần trăm bằng cách sử dụng UTF-8. Chúng chỉ khác nhau ở những ký tự mà chúng để lại, và sự khác biệt đó là toàn bộ câu chuyện: EncodeURIComponent thoát khỏi các dấu phân cách dành riêng, còn EncodeURI thì không.

Các dấu phân cách dành riêng là các ký tự cung cấp cấu trúc URL của nó: : / ? # [ ] @ ! $ & ' ( ) * + , ; =. EncodeURI giả sử bạn đã cấp cho nó một URL vốn đã có cấu trúc chính xác và phải giữ nguyên như vậy nên nó sẽ giữ nguyên chúng — nó sẽ không biến https:// thành https%3A%2F%2F. EncodeURIComponent giả sử bạn đưa cho nó một mảnh sắp được thả vào một khe, để nó thoát khỏi chúng, đảm bảo mảnh đó không thể thoát ra khỏi khe của nó.

Lỗi này tạo ra hoàn toàn là lỗi cơ học. Lấy giá trị tìm kiếm của a&b=c. Mã hóa nó bằng EncodeURI và thêm nó dưới dạng ?q=a&b=c, và bạn đã âm thầm tạo hai tham số: q bây giờ chỉ là "a" và một b=c lạc đã xuất hiện. Mã hóa nó bằng EncodeURIComponent và bạn nhận được ?q=a%26b%3Dc, một tham số, giá trị đúng. Cùng một loại lỗi cho phép một giá trị được tạo thủ công đưa các tham số vào URL mã của bạn được xây dựng — đó là lý do tại sao "sử dụng biểu mẫu thành phần cho các giá trị" là quy tắc bảo mật chứ không chỉ là quy tắc chính xác.

Mã hóa biểu mẫu là quy tắc thứ ba trông giống như quy tắc thứ hai. application/x-www-form-urlencoded ghi dấu cách dưới dạng + thay vì %20. Nếu bạn giải mã nội dung biểu mẫu bằng decodeURIComponent đơn giản, mọi dấu cộng trong dữ liệu sẽ trở thành khoảng trắng. Mọi hộp tìm kiếm từng đọc sai "C++" thành "C" đều là lỗi này.

HTML thực thể và tại sao việc giải mã chúng bằng InternalHTML lại là một thói quen xấu

Thoát cho HTML là hẹp và được hiểu rõ: & trở thành &amp;, < trở thành &lt;, > trở thành &gt; và bên trong các giá trị thuộc tính " và ' cũng cần thoát. Năm ký tự. Thoát nhiều hơn thế — biến mọi chữ cái có dấu thành một thực thể được đặt tên — là một giải pháp cho thời kỳ mã hóa ký tự không chắc chắn và giờ đây là kiểu dáng tùy chọn thay vì an toàn.

Giải mã là nơi sinh sống của thói quen xấu. Thủ thuật một dòng xuất hiện trong mọi câu trả lời là gán chuỗi cho HTML bên trong của phần tử tách rời và đọc lại textContent của nó. Nó hoạt động và đó là một ý tưởng tồi. Bạn đã chuyển dữ liệu đầu vào không đáng tin cậy cho trình phân tích cú pháp HTML để xây dựng các nút DOM thực từ đó. <img src=x onerror=...> trong chuỗi đó trở thành một phần tử hình ảnh thực tế có đính kèm trình xử lý lỗi thực tế; nếu cây con đó được chèn vào tài liệu thì nó sẽ chạy. Nó cũng âm thầm phá hủy dữ liệu của bạn: các thẻ trong đầu vào biến mất thay vì ngắt vòng, vì trình phân tích cú pháp hiểu chúng là đánh dấu chứ không phải văn bản.

Các thực thể giải mã chính xác không cần đến trình phân tích cú pháp: khớp tham chiếu, tra cứu tên trong bảng hoặc thực hiện phép tính số học cho tham chiếu số. Đó là vài chục dòng, nó không thể thực hiện bất cứ điều gì và nó thực hiện các chuyến đi khứ hồi một cách trung thực. Bộ công cụ này thực hiện theo cách đó, đó là lý do tại sao việc dán thẻ tập lệnh vào bộ giải mã thực thể sẽ hiển thị cho bạn thẻ tập lệnh.

Chọn một hàm băm và ba câu hỏi quyết định nó

Hàm băm mật mã biến bất kỳ đầu vào nào thành một bản tóm tắt có độ dài cố định, do đó việc tìm hai đầu vào có cùng một bản tóm tắt là không thể thực hiện được. Thuộc tính đó là thứ cho phép thông báo thay thế cho dữ liệu - trong chữ ký, kiểm tra tính toàn vẹn hoặc địa chỉ nội dung.

Câu hỏi đầu tiên: bạn đang bảo vệ khỏi tai nạn hay chống lại kẻ thù? Tổng kiểm tra bảo vệ khỏi bản tải xuống bị hỏng chỉ cần bắt các lần lật ngẫu nhiên; CRC32 được thôi. Một thông báo mà kẻ tấn công có thể hưởng lợi từ việc va chạm cần một hàm băm vẫn đứng vững. Sự khác biệt đó là lý do tại sao SHA-1 không chỉ đơn giản là "cũ".

SHA-1 đã bị hỏng, cụ thể là vậy. Trong 2017 tác phẩm được SHAttered tạo ra hai tệp PDF khác nhau có cùng thông báo SHA-1. Trong 2020, "SHA-1 là một Shambles" thể hiện sự xung đột tiền tố được chọn — biến thể mạnh hơn và nguy hiểm hơn nhiều vì nó cho phép kẻ tấn công va chạm hai tài liệu khác nhau có ý nghĩa thay vì hai đốm màu được xây dựng cẩn thận. Nếu bảo mật của hệ thống dựa trên khả năng chống va chạm SHA-1 thì bảo mật đó sẽ không còn nữa. SHA-1 vẫn còn trong bộ công cụ này vì id đối tượng git và một đoạn dài chữ ký API kế thừa vẫn sử dụng nó và bạn cần có khả năng tái tạo các giá trị đó. Tái tạo một giá trị không giống như dựa vào nó.

Câu hỏi thứ hai: đầu vào có phải là mật khẩu không? Nếu vậy, không ai trong số này là câu trả lời. SHA-256 được thiết kế để nhanh và nhanh chính xác là sai đối với mật khẩu: điều đó có nghĩa là kẻ tấn công với cơ sở dữ liệu của bạn có thể thử hàng tỷ lần đoán mỗi giây. Mật khẩu cần có chức năng làm chậm bộ nhớ, làm chậm bộ nhớ với muối cho mỗi người dùng - Argon2id, scrypt hoặc bcrypt. Đây không phải là một sắc thái; sử dụng SHA-256 cho mật khẩu là lỗi băm nghiêm trọng phổ biến nhất.

Câu hỏi thứ ba: bạn có cần một bản tóm tắt có khóa không? Nếu bạn đang xác thực một thư thay vì lấy dấu vân tay của nó, bạn muốn HMAC chứ không phải hàm băm đơn thuần. Ghép nối một bí mật và băm nó là một mục tiêu phản lưới nhà cổ điển chống lại các cuộc tấn công kéo dài; HMAC tồn tại vì việc xây dựng đó khó thực hiện đúng hơn vẻ ngoài của nó.

Đối với mọi thứ khác — lấy dấu vân tay của một tệp, địa chỉ nội dung, thuộc tính toàn vẹn — SHA-256 là mặc định hợp lý và SHA-512 thường nhanh hơn trên phần cứng 64-bit trong khi cung cấp thông tin tổng hợp rộng hơn.

Tại sao các giá trị băm ở đây lại đến từ trình duyệt

Thông tin tóm tắt trong bộ công cụ này được tính toán bởi SubtleCrypto, triển khai Web Crypto của chính trình duyệt, chứ không phải bởi JavaScript được gửi từ trang web này. Đó là một lựa chọn có chủ ý: việc triển khai trình duyệt được kiểm tra, duy trì và thường chạy dưới dạng mã gốc được tối ưu hóa. SHA-256 viết tay trong gói trang sẽ có nhiều mã đáng tin cậy hơn mà không mang lại lợi ích gì.

Nó có một hậu quả rõ ràng. Web Crypto chỉ được hiển thị trong bối cảnh an toàn, nghĩa là https:// hoặc localhost. Mở trang này trên HTTP đơn giản trên địa chỉ LAN và crypto.subtle sẽ không được xác định, vì vậy tiện ích băm sẽ cho bạn biết một cách rõ ràng thay vì âm thầm thất bại hoặc thay thế thứ gì đó yếu hơn.

Lý do tương tự thúc đẩy trình tạo UUID. crypto.randomUUID() cũng chỉ dành cho ngữ cảnh bảo mật, do đó, nếu không có sẵn, bộ công cụ sẽ quay trở lại crypto.getRandomValues() — vẫn là nguồn bảo mật bằng mật mã tương tự — và tự thiết lập phiên bản cũng như các bit biến thể. Điều nó sẽ không bao giờ làm là quay trở lại Math.random(). Đó là PRNG không phải mật mã nhanh mà trạng thái bên trong của nó có thể được phục hồi sau một thời gian ngắn xuất ra và các mã định danh có thói quen đáng tiếc là được thăng cấp thành khóa phiên và liên kết đặt lại mật khẩu. Nếu không có nguồn an toàn nào tồn tại, công cụ này sẽ không tạo ra gì và cho biết lý do.

Điều gì xảy ra với những gì bạn dán

  • Mọi chuyển đổi, băm, giải mã và tìm khác biệt đều chạy trong tab trình duyệt của bạn. Không có đầu vào nào được tải lên, ghi lại hoặc lưu trữ trên máy chủ vì không có máy chủ nào liên quan sau khi trang được tải.
  • Băm đến từ việc triển khai Web Crypto của chính trình duyệt và UUID từ trình tạo ngẫu nhiên được bảo mật bằng mật mã của nó. Không liên quan đến cuộc gọi mạng.
  • Không có nội dung nào bạn nhập được ghi vào bộ nhớ cục bộ hoặc cookie. Tải lại trang sẽ loại bỏ nó; đóng tab sẽ loại bỏ nó.
  • Phân tích trên toàn trang web chỉ chạy trên máy chủ sản xuất chuẩn đã được định cấu hình và được tiết lộ trong Chính sách quyền riêng tư; máy chủ cục bộ và bản xem trước từ chối nó. Các giá trị, mã thông báo, URL và nội dung tệp đã dán sẽ bị loại trừ khỏi các sự kiện phân tích của chính ToolAcre. Quảng cáo bị vô hiệu hóa trong cấu hình hiện tại.
  • Điều đó cho biết: khóa JWT hoặc API là thông tin xác thực trực tiếp. Thói quen an toàn là không bao giờ dán nội dung này vào trang web mà bạn không viết, cho dù những tuyên bố của trang đó có đáng tin cậy đến đâu đi nữa - bao gồm cả trang này.

Câu hỏi

Base64 có phải là cách để ẩn dữ liệu không?

Không. Đó là cách trình bày văn bản có thể đảo ngược, không có khóa, có thể được giải mã bởi bất kỳ ai trong một phần của giây. Nó làm cho dữ liệu tồn tại trong các kênh chỉ có văn bản; nó không làm cho nó bí mật. Bất cứ điều gì thực sự nhạy cảm đều cần mã hóa và kết quả được mã hóa sau đó thường được mã hóa base64 để truyền tải - đây là nguồn gốc của sự nhầm lẫn.

Tại sao base64 của tôi dài hơn đầu vào?

Vì bốn ký tự đầu ra mang ba byte đầu vào nên kích thước đầu ra có kích thước khoảng 4/3, cộng với tối đa hai ký tự đệm. Đó là vốn có của định dạng. Nếu kích thước quan trọng, hãy nén trước khi mã hóa - không bao giờ nén sau vì đầu ra base64 nén kém.

Tôi nên sử dụng chức năng mã hóa URL nào?

Sử dụng EncodeURIComponent cho bất kỳ phần nào bạn đang chèn vào URL: giá trị truy vấn, đoạn đường dẫn, đoạn. Chỉ sử dụng mã hóaURI khi bạn có toàn bộ URL có cấu trúc sẵn chỉ chứa dấu cách hoặc không chứa ASCII. Nếu bạn đang xây dựng một chuỗi truy vấn, hãy ưu tiên URLSearchParams, quy tắc này áp dụng quy tắc chính xác cho bạn và xử lý sự khác biệt về khoảng trắng.

Tại sao bộ giải mã của tôi lại báo lỗi "URI không đúng định dạng"?

Bởi vì % trong đầu vào không được theo sau bởi hai chữ số thập lục phân. Thông thường, văn bản chứa ký hiệu phần trăm theo nghĩa đen — "50% off" — chưa bao giờ được mã hóa. Phần trăm bằng chữ phải được viết là %25. Tiện ích URL ở đây báo cáo vị trí chính xác của hành vi trốn thoát vi phạm thay vì chỉ từ chối.

Tôi có thể sử dụng SHA-256 để lưu trữ mật khẩu không?

Số SHA-256 được thiết kế nhanh, có nghĩa là kẻ tấn công đánh cắp cơ sở dữ liệu của bạn có thể kiểm tra hàng tỷ mật khẩu dự kiến ​​mỗi giây trên phần cứng thông thường. Mật khẩu cần chức năng chậm, khó nhớ, khó nhớ: Argon2id, scrypt hoặc bcrypt. Đây là sai lầm nghiêm trọng phổ biến nhất trong lĩnh vực này.

Tại sao SHA-1 vẫn ở đây nếu nó bị hỏng?

Bởi vì bạn vẫn cần tái tạo các giá trị SHA-1 đã tồn tại: id đối tượng git, dấu vân tay chứng chỉ TLS cũ, chữ ký yêu cầu API cũ. Việc có thể tính toán một giá trị cho khả năng tương tác khác với việc dựa vào nó để bảo mật. Mỗi vị trí SHA-1 xuất hiện trong bộ công cụ này đều được gắn nhãn tương ứng.

Tại sao hai công cụ lại đưa ra các giá trị băm khác nhau cho cùng một văn bản?

Hầu như luôn có sự khác biệt về byte chứ không phải về thuật toán. Thủ phạm thông thường là dòng mới ở cuối (tệp kết thúc bằng một; hộp văn bản có thể không), mã hóa văn bản khác hoặc CRLF so với kết thúc dòng LF. Công cụ này băm UTF-8 byte chính xác những gì bạn đã nhập và hiển thị cho bạn số byte, điều này thường làm cho sự khác biệt trở nên rõ ràng.

Hạn chế

  • Bảng thực thể HTML được đặt tên bao gồm tập hợp con thực tế — các ký tự quan trọng trong đánh dấu, kiểu chữ, tiền tệ, mũi tên, toán học, tiếng Hy Lạp và tiếng Latin-1 — không phải tất cả các tham chiếu có tên 2,231 HTML5. Những cái tên không được nhận dạng sẽ được báo cáo và để lại chính xác như được viết thay vì đoán.
  • Giải mã thực thể yêu cầu dấu chấm phẩy kết thúc. HTML5 chấp nhận một số ít tham chiếu kế thừa mà không có tham chiếu nào, nhưng việc giải mã chúng một cách chính xác tùy thuộc vào ngữ cảnh đánh dấu xung quanh mà một công cụ văn bản độc lập không có.
  • Việc băm và tạo UUID cần có bối cảnh an toàn (https:// hoặc localhost) vì Web Crypto không bị lộ. Công cụ này báo cáo điều này thay vì thay thế việc triển khai yếu hơn.
  • Chỉ SHA-1, SHA-256, SHA-384 và SHA-512 khả dụng vì đó là những gì SubtleCrypto triển khai. MD5 vắng mặt do lựa chọn cũng như do cần thiết.
  • Không có HMAC, không có dẫn xuất khóa và không có mã hóa ở đây. Những thứ đó cần quản lý khóa, đây không phải là điều mà một trang bạn tìm thấy trên internet sẽ xử lý.
  • Mọi thứ đều bị giới hạn bởi bộ nhớ thiết bị của bạn vì tất cả đều chạy trong một tab trình duyệt. Đầu vào bị giới hạn — vài megabyte cho mỗi tiện ích — và công cụ này từ chối công việc quá khổ thay vì đóng băng.