Tại sao mã hóa base64 yêu cầu đệm nếu độ dài đầu vào không chia hết cho 3?


101

Mục đích của padding trong mã hóa base64 là gì. Sau đây là phần trích từ wikipedia:

"Một ký tự đệm bổ sung được phân bổ có thể được sử dụng để buộc đầu ra được mã hóa thành bội số nguyên của 4 ký tự (hoặc tương đương khi văn bản nhị phân không được mã hóa không phải là bội số của 3 byte); các ký tự đệm này sau đó phải được loại bỏ khi giải mã nhưng vẫn cho phép tính toán độ dài hiệu dụng của văn bản chưa được mã hóa, khi độ dài nhị phân đầu vào của nó không phải là bội số của 3 byte (ký tự không phải dấu đệm cuối cùng thường được mã hóa để khối 6 bit cuối cùng mà nó đại diện sẽ bằng không -được đệm trên các bit ít quan trọng nhất của nó, nhiều nhất hai ký tự đệm có thể xuất hiện ở cuối luồng được mã hóa). "

Tôi đã viết một chương trình có thể mã hóa base64 bất kỳ chuỗi nào và giải mã bất kỳ chuỗi mã hóa base64 nào. Padding giải quyết vấn đề gì?

Câu trả lời:


210

Kết luận của bạn rằng đệm là không cần thiết là đúng. Luôn có thể xác định độ dài của đầu vào một cách rõ ràng từ độ dài của trình tự được mã hóa.

Tuy nhiên, padding rất hữu ích trong các tình huống mà các chuỗi mã hóa base64 được nối với nhau theo cách mà độ dài của các chuỗi riêng lẻ bị mất, ví dụ như có thể xảy ra trong một giao thức mạng rất đơn giản.

Nếu các chuỗi chưa được đệm được nối với nhau, thì không thể khôi phục dữ liệu ban đầu vì thông tin về số byte lẻ ở cuối mỗi chuỗi riêng lẻ bị mất. Tuy nhiên, nếu các trình tự đệm được sử dụng, không có sự mơ hồ và toàn bộ trình tự có thể được giải mã một cách chính xác.

Chỉnh sửa: Một hình minh họa

Giả sử chúng ta có một chương trình mã hóa base64 các từ, nối chúng và gửi chúng qua mạng. Nó mã hóa "I", "AM" và "TJM", kẹp các kết quả lại với nhau mà không cần đệm và truyền chúng.

  • Imã hóa thành SQ( SQ==có đệm)
  • AMmã hóa thành QU0( QU0=có đệm)
  • TJMmã hóa thành VEpN( VEpNcó đệm)

Vì vậy, dữ liệu được truyền đi là SQQU0VEpN. Bộ thu base64-giải mã điều này I\x04\x14\xd1Q)thay vì dự định IAMTJM. Kết quả là vô nghĩa vì người gửi đã phá hủy thông tin về vị trí kết thúc của mỗi từ trong chuỗi mã hóa. Nếu người gửi đã gửi SQ==QU0=VEpNthay thế, người nhận có thể đã giải mã điều này thành ba chuỗi base64 riêng biệt sẽ nối với nhau để cung cấp IAMTJM.

Tại sao lại làm phiền với Padding?

Tại sao không chỉ thiết kế giao thức để tiền tố mỗi từ có độ dài số nguyên? Sau đó, người nhận có thể giải mã luồng một cách chính xác và không cần đệm.

Đó là một ý tưởng tuyệt vời, miễn là chúng ta biết độ dài của dữ liệu chúng ta đang mã hóa trước khi bắt đầu mã hóa nó. Nhưng điều gì sẽ xảy ra nếu thay vì lời nói, chúng tôi mã hóa các đoạn video từ một camera trực tiếp? Chúng ta có thể không biết trước độ dài của từng đoạn.

Nếu giao thức sử dụng đệm, sẽ không cần phải truyền một độ dài nào cả. Dữ liệu có thể được mã hóa khi nó đến từ máy ảnh, mỗi đoạn được kết thúc bằng đệm và người nhận sẽ có thể giải mã luồng một cách chính xác.

Rõ ràng đó là một ví dụ rất giả tạo, nhưng có lẽ nó minh họa tại sao padding có thể hữu ích trong một số trường hợp.


22
+1 Câu trả lời duy nhất thực sự cung cấp một câu trả lời hợp lý bên cạnh "bởi vì chúng tôi thích sự dài dòng và dư thừa vì một số lý do không thể giải thích được".
không hợp lệ

1
Điều này hoạt động tốt đối với các khối được mã hóa riêng biệt, nhưng dự kiến ​​sẽ được nối không thể phân biệt sau khi giải mã. Nếu bạn gửi U0FNSQ == QU0 =, bạn có thể xây dựng lại câu, nhưng bạn sẽ mất các từ tạo nên câu. Tôi đoán còn hơn không. Đáng chú ý, chương trình GNU base64 tự động xử lý các bảng mã nối.
Marcelo Cantos

2
Điều gì sẽ xảy ra nếu độ dài của các từ là bội số của 3? Cách nối ngu ngốc này phá hủy thông tin (phần cuối của từ), không loại bỏ phần đệm.
GreenScape

2
Việc ghép nối Base64 cho phép các bộ mã hóa xử lý các khối lớn song song mà không cần phải căn chỉnh các kích thước đoạn thành bội số của ba. Tương tự, với tư cách là chi tiết triển khai, có thể có một bộ mã hóa ở đó cần tạo bộ đệm dữ liệu nội bộ có kích thước không phải là bội số của ba.
Andre D

1
Câu trả lời này có thể khiến bạn nghĩ rằng bạn có thể giải mã một thứ gì đó như "SQ == QU0 = VEpN" bằng cách đưa nó cho bộ giải mã. Trên thực tế, có vẻ như bạn không thể, ví dụ như triển khai trong javascript và php không hỗ trợ điều này. Bắt đầu với một chuỗi được nối, bạn phải giải mã 4 byte cùng một lúc hoặc chia chuỗi sau các ký tự đệm. Có vẻ như những triển khai đó chỉ bỏ qua các ký tự đệm, ngay cả khi chúng ở giữa một chuỗi.
Roman

38

Trên một lưu ý liên quan, đây là một công cụ chuyển đổi cơ sở để chuyển đổi cơ sở tùy ý mà tôi đã tạo cho bạn. Thưởng thức! https://convert.zamicol.com/

Ký tự đệm là gì?

Các ký tự đệm giúp đáp ứng các yêu cầu về độ dài và không mang ý nghĩa.

Ví dụ thập phân về phần đệm: Với yêu cầu tùy ý tất cả các chuỗi đều có độ dài 8 ký tự, số 640 có thể đáp ứng yêu cầu này bằng cách sử dụng các ký tự đệm đứng trước số 0 vì chúng không có nghĩa, "00000640".

Mã hóa nhị phân

Mô hình Byte: Byte là đơn vị đo lường tiêu chuẩn trên thực tế và bất kỳ lược đồ mã hóa nào cũng phải liên quan trở lại đến byte.

Base256 phù hợp chính xác với mô hình này. Một byte tương đương với một ký tự trong base256.

Base16 , hệ thập lục phân hoặc hệ thập lục phân, sử dụng 4 bit cho mỗi ký tự. Một byte có thể đại diện cho hai ký tự base16.

Base64 không phù hợp đồng đều với mô hình byte (cũng như base32), không giống như base256 và base16. Tất cả các ký tự base64 có thể được biểu diễn bằng 6 bit, ngắn 2 bit của một byte đầy đủ.

Chúng ta có thể biểu diễn mã hóa base64 so với mô hình byte dưới dạng một phân số: 6 bit trên mỗi ký tự trên 8 bit trên mỗi byte . Giảm phân số này là 3 byte trên 4 ký tự.

Tỷ lệ này, 3 byte cho mỗi 4 ký tự base64, là quy tắc chúng tôi muốn tuân theo khi mã hóa base64. Mã hóa Base64 chỉ có thể hứa hẹn thậm chí đo với gói 3 byte, không giống như base16 và base256, nơi mỗi byte có thể tự đứng.

Vậy tại sao phần đệm lại được khuyến khích mặc dù việc mã hóa có thể hoạt động tốt mà không có ký tự đệm?

Nếu độ dài của một luồng không xác định hoặc nếu biết chính xác thời điểm luồng dữ liệu kết thúc, hãy sử dụng phần đệm. Các ký tự đệm thông báo rõ ràng rằng những điểm bổ sung đó phải trống và loại trừ mọi sự mơ hồ. Ngay cả khi độ dài không xác định với phần đệm, bạn sẽ biết nơi kết thúc luồng dữ liệu của mình.

Ví dụ về bộ đếm, một số tiêu chuẩn như JOSE không cho phép các ký tự đệm. Trong trường hợp này, nếu thiếu thứ gì đó, chữ ký mật mã sẽ không hoạt động hoặc các ký tự không phải base64 khác sẽ bị thiếu (như "."). Mặc dù giả định về độ dài không được đưa ra, nhưng không cần đệm bởi vì nếu có gì đó sai, nó sẽ không hoạt động.

Và đây chính xác là những gì RFC base64 nói,

Trong một số trường hợp, việc sử dụng padding ("=") trong dữ liệu được mã hóa cơ sở là không bắt buộc hoặc không được sử dụng. Trong trường hợp chung, khi không thể thực hiện các giả định về kích thước của dữ liệu được vận chuyển, thì cần có phần đệm để mang lại dữ liệu được giải mã chính xác.

[...]

Bước đệm trong cơ sở 64 [...] nếu được triển khai không đúng cách, sẽ dẫn đến những thay đổi không đáng kể đối với dữ liệu được mã hóa. Ví dụ: nếu đầu vào chỉ là một octet cho mã hóa cơ sở 64, thì tất cả sáu bit của ký hiệu đầu tiên được sử dụng, nhưng chỉ hai bit đầu tiên của ký hiệu tiếp theo được sử dụng. Các bit đệm này PHẢI được đặt thành 0 bằng các bộ mã hóa phù hợp, được mô tả trong phần mô tả về đệm bên dưới. Nếu thuộc tính này không được giữ, sẽ không có biểu diễn chính tắc của dữ liệu được mã hóa cơ sở và nhiều chuỗi được mã hóa cơ sở có thể được giải mã thành cùng một dữ liệu nhị phân. Nếu thuộc tính này (và các thuộc tính khác được thảo luận trong tài liệu này) giữ nguyên, thì mã hóa chuẩn được đảm bảo.

Padding cho phép chúng tôi giải mã mã hóa base64 với lời hứa không có bit bị mất. Không có phần đệm thì không còn xác nhận rõ ràng về việc đo lường trong gói ba byte. Nếu không có phần đệm, bạn có thể không đảm bảo sao chép chính xác mã hóa gốc mà không có thông tin bổ sung thường từ một nơi khác trong ngăn xếp của bạn, như TCP, tổng kiểm tra hoặc các phương pháp khác.

Ví dụ

Đây là mẫu ví dụ RFC 4648 ( http://tools.ietf.org/html/rfc4648#section-8 )

Mỗi ký tự bên trong hàm "BASE64" sử dụng một byte (base256). Sau đó, chúng tôi dịch nó sang base64.

BASE64("")       = ""           (No bytes used. 0%3=0.)
BASE64("f")      = "Zg=="       (One byte used. 1%3=1.)
BASE64("fo")     = "Zm8="       (Two bytes. 2%3=2.)
BASE64("foo")    = "Zm9v"       (Three bytes. 3%3=0.)
BASE64("foob")   = "Zm9vYg=="   (Four bytes. 4%3=1.)
BASE64("fooba")  = "Zm9vYmE="   (Five bytes. 5%3=2.)
BASE64("foobar") = "Zm9vYmFy"   (Six bytes. 6%3=0.)

Đây là một bộ mã hóa mà bạn có thể sử dụng: http://www.motobit.com/util/base64-decoder-encoder.asp


16
-1 Đây là một bài đăng hay và kỹ lưỡng về cách thức hoạt động của các hệ thống số, nhưng nó không giải thích tại sao đệm được sử dụng khi mã hóa sẽ hoạt động hoàn hảo mà không có.
Matti Virkkunen

2
Bạn thậm chí đã đọc câu hỏi? Bạn không cần đệm để giải mã chính xác.
Navin

3
Tôi nghĩ rằng câu trả lời này trên thực tế đã giải thích lý do như đã nêu ở đây: "chúng tôi không còn có thể đảm bảo sao chép chính xác mã hóa gốc mà không có thông tin bổ sung". Nó thực sự đơn giản, phần đệm cho chúng tôi biết rằng chúng tôi đã nhận được mã hóa hoàn chỉnh. Mỗi khi bạn có 3 byte, bạn có thể yên tâm cho rằng việc tiếp tục và giải mã nó là ổn, bạn đừng lo lắng điều đó, hum ... có thể một byte nữa sẽ đến có thể thay đổi mã hóa.
Didier A.

@DidierA. Làm thế nào để bạn biết rằng không có thêm 3 byte trong chuỗi con base64? Để giải mã a char*, bạn cần kích thước của chuỗi hoặc ký tự kết thúc null. Đệm là thừa. Do đó, câu hỏi của OP.
Navin

4
@Navin Nếu bạn đang phát trực tuyến giải mã base64 byte, bạn không biết độ dài, với phần đệm 3 byte, bạn biết rằng mỗi khi bạn có 3 byte, bạn có thể xử lý 4 ký tự cho đến khi bạn đến cuối luồng. Nếu không có nó, bạn có thể cần phải quay lại, bởi vì byte tiếp theo có thể khiến ký tự trước đó thay đổi, do đó, bạn chỉ có thể chắc chắn rằng mình đã giải mã nó đúng cách khi bạn đã đến cuối luồng. Vì vậy, nó không hữu ích lắm, nhưng nó có một số trường hợp hữu ích mà bạn có thể muốn sử dụng.
Didier A.

1

Nó không có nhiều lợi ích trong thời hiện đại. Vì vậy, hãy xem đây là một câu hỏi về mục đích lịch sử ban đầu có thể là gì.

Mã hóa Base64 xuất hiện lần đầu tiên trong RFC 1421 ngày 1993. RFC này thực sự tập trung vào việc mã hóa email và base64 được mô tả trong một phần nhỏ 4.3.2.4 .

RFC này không giải thích mục đích của phần đệm. Gần nhất chúng ta phải đề cập đến mục đích ban đầu là câu này:

Một lượng tử mã hóa đầy đủ luôn được hoàn thành ở cuối thư.

Nó không đề xuất nối (câu trả lời ở đây), cũng không dễ thực hiện như một mục đích rõ ràng cho phần đệm. Tuy nhiên, xem xét toàn bộ mô tả, không phải không có lý khi cho rằng điều này có thể nhằm giúp bộ giải mã đọc đầu vào theo đơn vị 32-bit ( "quanta" ). Điều đó không có lợi cho ngày nay, tuy nhiên vào năm 1993, mã C không an toàn sẽ rất có thể thực sự bị lợi dụng đặc tính này.


1
Trong trường hợp không có phần đệm, nỗ lực nối hai chuỗi khi độ dài của chuỗi đầu tiên không phải là bội số của ba thường sẽ mang lại một chuỗi có vẻ hợp lệ, nhưng nội dung của chuỗi thứ hai sẽ giải mã không chính xác. Thêm phần đệm đảm bảo điều đó không xảy ra.
supercat

1
@supercat Nếu đó là mục tiêu, việc kết thúc mọi chuỗi base64 chỉ bằng một dấu "=" có dễ dàng hơn không? Độ dài trung bình sẽ ngắn hơn, và nó vẫn sẽ ngăn chặn các nối sai.
Roman Starkov

2
Chiều dài trung bình của b'Zm9vYmFyZm9vYg==' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy' b'Zm9vYmFyZm9vYmFyZg==' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v' là giống như của b'Zm9vYmFyZm9vYg=' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy=' b'Zm9vYmFyZm9vYmFyZg=' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v='
Scott
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.