Tại sao tôi cần 'b' để mã hóa chuỗi bằng Base64?


258

Theo ví dụ python này , tôi mã hóa một chuỗi là Base64 với:

>>> import base64
>>> encoded = base64.b64encode(b'data to be encoded')
>>> encoded
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Nhưng, nếu tôi rời khỏi hàng đầu b:

>>> encoded = base64.b64encode('data to be encoded')

Tôi nhận được lỗi sau đây:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python32\lib\base64.py", line 56, in b64encode
   raise TypeError("expected bytes, not %s" % s.__class__.__name__)
   TypeError: expected bytes, not str

Tại sao lại thế này?


37
Trên thực tế, tất cả các câu hỏi trả về "TypeError: byte dự kiến, không phải str" đều có cùng một câu trả lời.
Lennart Regebro

Câu trả lời:


273

base64 mã hóa mất 8-bit nhị phân dữ liệu byte và mã hóa nó sử dụng những chữ số A-Z, a-z, 0-9, +, /* để nó có thể được truyền qua kênh mà không giữ gìn tất cả 8-bit dữ liệu, chẳng hạn như email.

Do đó, nó muốn một chuỗi các byte 8 bit. Bạn tạo những cái đó trong Python 3 với b''cú pháp.

Nếu bạn loại bỏ b, nó trở thành một chuỗi. Một chuỗi là một chuỗi các ký tự Unicode. Base64 không biết phải làm gì với dữ liệu Unicode, đó không phải là 8 bit. Thực tế nó không phải là bất kỳ bit nào. :-)

Trong ví dụ thứ hai của bạn:

>>> encoded = base64.b64encode('data to be encoded')

Tất cả các ký tự nằm gọn trong bộ ký tự ASCII và mã hóa base64 thực sự hơi vô nghĩa. Bạn có thể chuyển đổi nó thành ascii thay vào đó, với

>>> encoded = 'data to be encoded'.encode('ascii')

Hoặc đơn giản hơn:

>>> encoded = b'data to be encoded'

Đó sẽ là điều tương tự trong trường hợp này.


* Hầu hết các hương vị base64 cũng có thể bao gồm một =ở cuối là đệm. Ngoài ra, một số biến thể Base64 có thể sử dụng các ký tự khác +/. Xem bảng tóm tắt các biến thể tại Wikipedia để biết tổng quan.


174

Câu trả lời ngắn

Bạn cần phải đẩy một bytes-likeđối tượng ( bytes, bytearray, vv) để các base64.b64encode()phương pháp. Đây là hai cách:

>>> data = base64.b64encode(b'data to be encoded')
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Hoặc với một biến:

>>> string = 'data to be encoded'
>>> data = base64.b64encode(string.encode())
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Tại sao?

Trong Python 3, strcác đối tượng không phải là mảng ký tự kiểu C (vì vậy chúng không phải là mảng byte), mà đúng hơn, chúng là các cấu trúc dữ liệu không có bất kỳ mã hóa vốn có nào. Bạn có thể mã hóa chuỗi đó (hoặc giải thích nó) theo nhiều cách khác nhau. Phổ biến nhất (và mặc định trong Python 3) là utf-8, đặc biệt vì nó tương thích ngược với ASCII (mặc dù, như các mã hóa được sử dụng rộng rãi nhất). Đó là những gì đang xảy ra khi bạn thực hiện stringvà gọi .encode()phương thức trên nó: Python đang diễn giải chuỗi trong utf-8 (mã hóa mặc định) và cung cấp cho bạn mảng byte tương ứng.

Mã hóa Base-64 trong Python 3

Ban đầu tiêu đề câu hỏi hỏi về mã hóa Base-64. Đọc về công cụ Base-64.

base64mã hóa lấy các đoạn nhị phân 6 bit và mã hóa chúng bằng các ký tự AZ, az, 0-9, '+', '/' và '=' (một số mã hóa sử dụng các ký tự khác nhau thay cho '+' và '/') . Đây là một mã hóa ký tự dựa trên cấu trúc toán học của hệ thống số radix-64 hoặc base-64, nhưng chúng rất khác nhau. Base-64 trong toán học là một hệ thống số như nhị phân hoặc thập phân và bạn thực hiện thay đổi cơ số này trên toàn bộ số hoặc (nếu cơ số bạn chuyển đổi từ có công suất bằng 2 nhỏ hơn 64) trong các khối từ phải sang trái.

Trong base64mã hóa, bản dịch được thực hiện từ trái sang phải; 64 ký tự đầu tiên là lý do tại sao nó được gọi là base64 mã hóa . Biểu tượng '=' thứ 65 được sử dụng để đệm, vì mã hóa kéo các đoạn 6 bit nhưng dữ liệu thường được mã hóa là các byte 8 bit, do đó, đôi khi chỉ có hai hoặc 4 bit ở đoạn cuối.

Thí dụ:

>>> data = b'test'
>>> for byte in data:
...     print(format(byte, '08b'), end=" ")
...
01110100 01100101 01110011 01110100
>>>

Nếu bạn diễn giải dữ liệu nhị phân đó dưới dạng một số nguyên, thì đây là cách bạn sẽ chuyển đổi nó thành cơ sở 10 và cơ sở 64 ( bảng cho cơ sở 64 ):

base-2:  01 110100 011001 010111 001101 110100 (base-64 grouping shown)
base-10:                            1952805748
base-64:  B      0      Z      X      N      0

base64 mã hóa , tuy nhiên, sẽ nhóm lại dữ liệu này:

base-2:  011101  000110  010101 110011 011101 00(0000) <- pad w/zeros to make a clean 6-bit chunk
base-10:     29       6      21     51     29      0
base-64:      d       G       V      z      d      A

Vì vậy, 'B0ZXN0' là phiên bản cơ sở 64 của hệ nhị phân của chúng tôi, nói một cách toán học. Tuy nhiên, base64 mã hóa phải thực hiện mã hóa theo hướng ngược lại (vì vậy dữ liệu thô được chuyển đổi thành 'dGVzdA') và cũng có một quy tắc để cho các ứng dụng khác biết cuối cùng còn bao nhiêu dung lượng. Điều này được thực hiện bằng cách đệm phần cuối bằng ký hiệu '='. Vì vậy, base64mã hóa của dữ liệu này là 'dGVzdA ==', với hai ký hiệu '=' để biểu thị hai cặp bit sẽ cần được xóa khỏi cuối khi dữ liệu này được giải mã để làm cho nó khớp với dữ liệu gốc.

Hãy thử kiểm tra xem tôi có thiếu trung thực không:

>>> encoded = base64.b64encode(data)
>>> print(encoded)
b'dGVzdA=='

Tại sao nên sử dụng base64mã hóa?

Giả sử tôi phải gửi một số dữ liệu cho ai đó qua email, như dữ liệu này:

>>> data = b'\x04\x6d\x73\x67\x08\x08\x08\x20\x20\x20'
>>> print(data.decode())

>>> print(data)
b'\x04msg\x08\x08\x08   '
>>>

Có hai vấn đề tôi đã trồng:

  1. Nếu tôi cố gửi email đó trong Unix, email sẽ gửi ngay khi \x04đọc ký tự, vì đó là ASCII cho END-OF-TRANSMISSION(Ctrl-D), vì vậy dữ liệu còn lại sẽ bị loại khỏi đường truyền.
  2. Ngoài ra, trong khi Python đủ thông minh để thoát khỏi tất cả các ký tự điều khiển xấu xa của tôi khi tôi in trực tiếp dữ liệu, khi chuỗi đó được giải mã là ASCII, bạn có thể thấy rằng 'thông điệp' không có ở đó. Đó là bởi vì tôi đã sử dụng ba BACKSPACEký tự và ba SPACEký tự để xóa 'thông điệp'. Do đó, ngay cả khi tôi không có EOFký tự ở đó, người dùng cuối sẽ không thể dịch từ văn bản trên màn hình sang dữ liệu thực, thô.

Đây chỉ là một bản demo để cho bạn thấy việc gửi dữ liệu thô khó đến mức nào. Mã hóa dữ liệu sang định dạng base64 cung cấp cho bạn cùng một dữ liệu chính xác nhưng ở định dạng đảm bảo an toàn cho việc gửi qua phương tiện điện tử như email.


6
base64.b64encode(s.encode()).decode()không phải là rất pythonic khi tất cả những gì bạn muốn là chuyển đổi chuỗi thành chuỗi. base64.encode(s)nên là đủ ít nhất trong python3. Cảm ơn bạn đã giải thích rất tốt về chuỗi và byte trong python
MortenB

2
@MortenB Vâng, thật kỳ lạ, nhưng về mặt rõ ràng là những gì đang xảy ra miễn là kỹ sư nhận thức được sự khác biệt giữa các mảng byte và chuỗi, vì không có một ánh xạ (mã hóa) nào giữa chúng, như các ngôn ngữ khác giả định.
Greg Schmit

3
@MortenB Nhân tiện, base64.encode(s)sẽ không hoạt động trong Python3; bạn đang nói rằng một cái gì đó như thế nên có sẵn? Tôi nghĩ lý do có thể gây nhầm lẫn là, tùy thuộc vào mã hóa và nội dung của chuỗi, scó thể không có 1 biểu diễn duy nhất dưới dạng một mảng byte.
Greg Schmit

Schmitt: đó chỉ là một ví dụ về việc nó nên đơn giản như thế nào. các usecase phổ biến nhất nên như thế.
MortenB

1
@MortenB nhưng b64 không chỉ có ý nghĩa đối với văn bản, bất kỳ nội dung nhị phân nào cũng có thể được mã hóa b64 (âm thanh, hình ảnh, v.v.). Làm cho nó hoạt động như bạn đề xuất theo ý kiến ​​của tôi che giấu sự khác biệt giữa văn bản và mảng byte thậm chí nhiều hơn, làm cho việc gỡ lỗi khó khăn hơn. Nó chỉ đơn giản là di chuyển khó khăn ở một nơi khác.
Michael Ekoka

32

Nếu dữ liệu được mã hóa chứa các ký tự "kỳ lạ", tôi nghĩ bạn phải mã hóa trong "UTF-8"

encoded = base64.b64encode (bytes('data to be encoded', "utf-8"))

24

Nếu chuỗi là Unicode, cách dễ nhất là:

import base64                                                        

a = base64.b64encode(bytes(u'complex string: ñáéíóúÑ', "utf-8"))

# a: b'Y29tcGxleCBzdHJpbmc6IMOxw6HDqcOtw7PDusOR'

b = base64.b64decode(a).decode("utf-8", "ignore")                    

print(b)
# b :complex string: ñáéíóúÑ

Thực sự không phải là cách dễ nhất, nhưng là một trong những cách rõ ràng nhất, khi điều quan trọng là mã hóa được sử dụng để truyền chuỗi, là một phần của "giao thức" truyền dữ liệu qua cơ sở64.
xuiqzy

12

Có tất cả những gì bạn cần:

expected bytes, not str

Việc dẫn đầu blàm cho chuỗi nhị phân của bạn.

Bạn sử dụng phiên bản Python nào? 2.x hay 3.x?

Chỉnh sửa: Xem http://docs.python.org/release/3.0.1/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit để biết chi tiết chính xác của chuỗi trong Python 3.x


Cảm ơn tôi đang sử dụng, 3.x. Tại sao Python muốn chuyển đổi nó thành nhị phân. Điều tương tự trong Ruby sẽ là ... yêu cầu> "base64" và sau đó> Base64.encode64 ('dữ liệu được mã hóa')
dublintech

2
@dublintech Vì văn bản (unicode) khác với dữ liệu thô. Nếu bạn muốn mã hóa một chuỗi văn bản trong Base64, trước tiên, bạn cần xác định mã hóa ký tự (như UTF-8) và sau đó bạn có byte chứ không phải ký tự, để bạn có thể mã hóa ở dạng an toàn dưới dạng văn bản.
fortran

2
Điều này không trả lời câu hỏi. Anh ta biết nó hoạt động với một đối tượng byte, nhưng không phải là một đối tượng chuỗi. Câu hỏi là tại sao .
Lennart Regebro

@fortran Mã hóa chuỗi Python3 mặc định là UTF, không biết tại sao nó phải được đặt rõ ràng.
xmedeko

0

Điều đó đơn giản có nghĩa là bạn đang lấy đầu vào dưới dạng một mảng byte hoặc byte không phải là một chuỗi.

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.