ทำไมฉันต้องใช้ 'b' เพื่อเข้ารหัสสตริงด้วย Base64


258

ต่อจากตัวอย่างไพธ อนฉันเข้ารหัสสตริงเป็น Base64 ด้วย:

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

แต่ถ้าฉันออกจากผู้นำb:

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

ฉันได้รับข้อผิดพลาดต่อไปนี้:

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

ทำไมนี้


37
จริง ๆ แล้วคำถามทั้งหมดที่ส่งคืน "TypeError: ไบต์ที่คาดไว้ไม่ใช่ str" มีคำตอบเดียวกัน
Lennart Regebro

คำตอบ:


273

base64 เข้ารหัสใช้เวลา 8 บิตข้อมูลไบต์ไบนารีและถอดรหัสมันใช้เพียงตัวละครA-Z, a-z, 0-9, +, /* เพื่อที่จะสามารถส่งผ่านช่องทางที่ไม่รักษาทั้ง 8 บิตของข้อมูลเช่นอีเมล

ดังนั้นจึงต้องการสตริงไบต์ 8 บิต คุณสร้างสิ่งเหล่านั้นใน Python 3 ด้วยb''ไวยากรณ์

หากคุณลบbมันจะกลายเป็นสตริง สตริงคือลำดับของอักขระ Unicode base64 ไม่รู้ว่าจะทำอย่างไรกับข้อมูล Unicode ไม่ใช่ 8 บิต จริงๆแล้วมันไม่ใช่บิตเลย :-)

ในตัวอย่างที่สองของคุณ:

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

ตัวละครทุกตัวมีความเรียบร้อยในชุดอักขระ ASCII และการเข้ารหัส base64 จึงไม่มีประโยชน์จริง ๆ คุณสามารถแปลงเป็น ascii แทนได้ด้วย

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

หรือง่ายกว่า:

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

ซึ่งจะเป็นสิ่งเดียวกันในกรณีนี้


* รสชาติพื้นฐานส่วนใหญ่ 64 รายการอาจรวม=อยู่ในท้ายที่สุดว่าเป็นการเติมเต็ม นอกจากนี้บางสายพันธุ์ base64 อาจจะใช้ตัวละครอื่น ๆ กว่าและ+ /ดูตารางสรุป Variantsที่ Wikipedia เพื่อดูภาพรวม


174

คำตอบสั้น ๆ

คุณจำเป็นต้องผลักดันbytes-likeวัตถุ ( bytes, bytearrayฯลฯ ) กับbase64.b64encode()วิธีการ นี่คือสองวิธี:

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

หรือด้วยตัวแปร:

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

ทำไม?

ใน Python 3 strวัตถุไม่ใช่อาร์เรย์ของอักขระแบบ C (ดังนั้นจึงไม่ใช่อาร์เรย์ไบต์) แต่เป็นโครงสร้างข้อมูลที่ไม่มีการเข้ารหัสโดยธรรมชาติ คุณสามารถเข้ารหัสสตริงนั้น (หรือตีความมัน) ได้หลายวิธี (และค่าเริ่มต้นใน Python 3) ที่พบมากที่สุดคือ utf-8 โดยเฉพาะอย่างยิ่งเนื่องจากเข้ากันได้กับ ASCII (แม้ว่าจะเป็นการเข้ารหัสที่ใช้กันอย่างแพร่หลายที่สุด) นั่นคือสิ่งที่เกิดขึ้นเมื่อคุณใช้ a stringและเรียกใช้.encode()เมธอดดังกล่าว: Python ตีความสตริงเป็น utf-8 (การเข้ารหัสเริ่มต้น) และให้อาร์เรย์ของไบต์ที่สอดคล้องกับคุณ

การเข้ารหัส Base-64 ใน Python 3

แต่เดิมชื่อคำถามที่ถามเกี่ยวกับการเข้ารหัส Base-64 อ่านต่อสำหรับ Base-64 stuff

base64การเข้ารหัสต้องใช้ไบนารีจำนวน 6 บิตและเข้ารหัสโดยใช้อักขระ AZ, az, 0-9, '+', '/', และ '=' (การเข้ารหัสบางตัวใช้อักขระที่แตกต่างกันแทน '+' และ '/') . นี่คือการเข้ารหัสตัวอักษรที่ยึดตามโครงสร้างทางคณิตศาสตร์ของระบบเลขฐาน 64 หรือฐาน 64 แต่แตกต่างกันมาก Base-64 ในคณิตศาสตร์คือระบบตัวเลขเช่นเลขฐานสองหรือฐานสิบและคุณทำการเปลี่ยนค่าฐานในจำนวนเต็มทั้งหมดหรือ (ถ้าฐานที่คุณแปลงจากนั้นคือกำลัง 2 น้อยกว่า 64) ในหน่วยจากขวาไปเป็น ซ้าย.

ในการbase64เข้ารหัสการแปลเสร็จจากซ้ายไปขวา ผู้ 64 ตัวแรกที่มีเหตุผลที่เรียกว่าการเข้ารหัสbase64 สัญลักษณ์ 65th '=' ใช้สำหรับแพ็ดดิ้งเนื่องจากการเข้ารหัสดึงชิ้น 6 บิต แต่ข้อมูลมักจะหมายถึงการเข้ารหัสเป็นไบต์ 8 บิตดังนั้นบางครั้งมีเพียงสองหรือ 4 บิตในก้อนสุดท้าย

ตัวอย่าง:

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

หากคุณตีความว่าข้อมูลไบนารีนั้นเป็นจำนวนเต็มเดียวนี่คือวิธีที่คุณจะแปลงเป็นฐาน 10 และฐาน 64 ( ตารางสำหรับฐาน 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 อย่างไรก็ตามการเข้ารหัสจะจัดกลุ่มข้อมูลนี้ใหม่:

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

ดังนั้น 'B0ZXN0' จึงเป็นเวอร์ชั่นฐาน 64 ของไบนารีของเรา อย่างไรก็ตามการbase64 เข้ารหัสจะต้องทำการเข้ารหัสในทิศทางตรงกันข้าม (ดังนั้นข้อมูลดิบจะถูกแปลงเป็น 'dGVzdA') และยังมีกฎที่จะบอกแอปพลิเคชันอื่น ๆ ว่ามีพื้นที่เหลือเท่าใดในตอนท้าย สิ่งนี้ทำได้โดยการเติมจุดสิ้นสุดด้วยสัญลักษณ์ '=' ดังนั้นการbase64เข้ารหัสของข้อมูลนี้คือ 'dGVzdA ==' พร้อมด้วยสัญลักษณ์สอง '=' เพื่อแสดงว่าบิตสองคู่จะต้องถูกลบออกจากจุดสิ้นสุดเมื่อข้อมูลนี้ได้รับการถอดรหัสเพื่อให้ตรงกับข้อมูลต้นฉบับ

ลองทดสอบสิ่งนี้ดูว่าฉันไม่ซื่อสัตย์หรือไม่:

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

ทำไมต้องใช้การbase64เข้ารหัส

สมมติว่าฉันต้องส่งข้อมูลให้ใครบางคนทางอีเมลเช่นข้อมูลนี้:

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

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

มีปัญหาสองข้อที่ฉันปลูก:

  1. ถ้าฉันพยายามส่งอีเมลนั้นใน Unix อีเมลจะส่งทันทีที่\x04อ่านตัวอักษรเพราะนั่นคือ ASCII สำหรับEND-OF-TRANSMISSION(Ctrl-D) ดังนั้นข้อมูลที่เหลือจะถูกส่งออกไป
  2. นอกจากนี้ในขณะที่ Python ฉลาดพอที่จะหลบหนีอักขระควบคุมความชั่วร้ายทั้งหมดของฉันเมื่อฉันพิมพ์ข้อมูลโดยตรงเมื่อสตริงนั้นถอดรหัสเป็น ASCII คุณจะเห็นว่า 'msg' ไม่อยู่ที่นั่น นั่นเป็นเพราะฉันใช้BACKSPACEตัวละครสามตัวและSPACEตัวละครสามตัวเพื่อลบ 'msg' ดังนั้นแม้ว่าฉันจะไม่มีEOFตัวอักษรอยู่ก็ตามผู้ใช้จะไม่สามารถแปลจากข้อความบนหน้าจอเป็นข้อมูลดิบที่แท้จริงได้

นี่เป็นเพียงตัวอย่างเพื่อแสดงให้คุณเห็นว่าการส่งข้อมูลดิบนั้นยากเพียงใด การเข้ารหัสข้อมูลในรูปแบบ base64 ให้ข้อมูลที่แน่นอนเหมือนกัน แต่ในรูปแบบที่ทำให้แน่ใจว่าปลอดภัยสำหรับการส่งผ่านสื่ออิเล็กทรอนิกส์เช่นอีเมล


6
base64.b64encode(s.encode()).decode()ไม่ pythonic มากเมื่อทั้งหมดที่คุณต้องการคือการแปลงสตริงเป็นสตริง base64.encode(s)ควรเพียงพออย่างน้อยใน python3 ขอบคุณสำหรับคำอธิบายที่ดีมากเกี่ยวกับสตริงและไบต์ในไพ
ธ อน

2
@MortenB ใช่มันแปลก แต่กลับหัวกลับหางชัดเจนมากสิ่งที่เกิดขึ้นตราบใดที่วิศวกรตระหนักถึงความแตกต่างระหว่างอาร์เรย์ของไบต์และสตริงเนื่องจากไม่มีการแมปเดียว (เข้ารหัส) ระหว่างพวกเขาเป็นภาษาอื่น ๆ สมมติ.
Greg Schmit

3
@MortenB อย่างไรก็ตามมันbase64.encode(s)จะไม่ทำงานใน Python3 คุณกำลังพูดว่าควรจะมีบางอย่างเช่นนี้หรือไม่ ฉันคิดว่าเหตุผลที่ทำให้สับสนคือขึ้นอยู่กับการเข้ารหัสและเนื้อหาของสตริงsอาจไม่มี 1 การแสดงที่ไม่ซ้ำกันเป็นอาร์เรย์ของไบต์
Greg Schmit

Schmitt: มันเป็นเพียงตัวอย่างของความเรียบง่าย usecases ที่พบบ่อยที่สุดควรเป็นเช่นนั้น
MortenB

1
@MortenB แต่ b64 ไม่ได้มีไว้สำหรับข้อความเท่านั้นเนื้อหาไบนารีใด ๆ ที่สามารถเข้ารหัสได้เป็น b64 (เสียงภาพ ฯลฯ ) การทำให้มันทำงานตามที่คุณเสนอในความคิดของฉันได้ซ่อนความแตกต่างระหว่างข้อความและอาร์เรย์อาร์เรย์มากขึ้นทำให้การดีบักยากขึ้น มันแค่เคลื่อนย้ายความยากลำบากไปที่อื่น
Michael Ekoka

32

หากข้อมูลที่จะเข้ารหัสมีอักขระ "แปลกใหม่" ฉันคิดว่าคุณต้องเข้ารหัสใน "UTF-8"

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

24

หากสตริงเป็น Unicode วิธีที่ง่ายที่สุดคือ:

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: ñáéíóúÑ

ไม่ใช่วิธีที่ง่ายที่สุด แต่เป็นหนึ่งในวิธีที่ชัดเจนที่สุดเมื่อมีความสำคัญที่การเข้ารหัสจะใช้สำหรับการส่งสตริงซึ่งเป็นส่วนหนึ่งของ "โปรโตคอล" ของการส่งข้อมูลผ่าน base64
xuiqzy

12

มีทุกสิ่งที่คุณต้องการ:

expected bytes, not str

ชั้นนำbทำให้สตริงไบนารีของคุณ

คุณใช้ Python เวอร์ชันใด 2.x หรือ 3.x

แก้ไข:ดูhttp://docs.python.org/release/3.0.1/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bitสำหรับรายละเอียดของสายใน Python 3.x


ขอบคุณฉันใช้ 3.x ทำไมไพ ธ อนต้องการแปลงให้เป็นไบนารี่อย่างชัดเจน สิ่งเดียวกันใน Ruby จะเป็น ... ต้องการ> "base64" และจากนั้น> Base64.encode64 ('data to be encoded')
dublintech

2
@dublintech เพราะข้อความ (ยูนิโค้ด) แตกต่างจากข้อมูลดิบ หากคุณต้องการเข้ารหัสสตริงข้อความใน Base64 ขั้นแรกคุณต้องพิจารณาการเข้ารหัสอักขระ (เช่น UTF-8) จากนั้นคุณมีไบต์เป็นอักขระแทนที่จะเป็นตัวอักษรซึ่งคุณสามารถเข้ารหัสในรูปแบบข้อความ ASCII-safe
fortran

2
สิ่งนี้ไม่ตอบคำถาม เขารู้ว่ามันทำงานกับวัตถุไบต์ แต่ไม่ใช่วัตถุสตริง คำถามคือทำไม
Lennart Regebro

@fortran การเข้ารหัสสตริง Python3 ที่เป็นค่าเริ่มต้นเป็น UTF ไม่รู้ว่าทำไมจึงต้องตั้งค่าอย่างชัดเจน
xmedeko

0

นั่นหมายความว่าคุณกำลังรับอินพุตเป็นอาร์เรย์ไบต์หรือไบต์ไม่ใช่สตริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.