เหตุใดการเข้ารหัส base64 จึงจำเป็นต้องมีช่องว่างภายในถ้าความยาวอินพุตไม่หารด้วย 3


103

จุดประสงค์ของการขยายในการเข้ารหัส base64 คืออะไร ต่อไปนี้เป็นสารสกัดจากวิกิพีเดีย:

"มีการจัดสรรอักขระแพดเพิ่มเติมซึ่งอาจใช้เพื่อบังคับให้เอาต์พุตที่เข้ารหัสเป็นจำนวนเต็มจำนวนเต็มจำนวน 4 อักขระ (หรือเทียบเท่าเมื่อข้อความไบนารีที่ไม่ได้เข้ารหัสไม่ใช่จำนวน 3 ไบต์) จากนั้นอักขระช่องว่างเหล่านี้จะต้องถูกละทิ้งเมื่อถอดรหัส แต่ ยังคงอนุญาตให้คำนวณความยาวที่มีประสิทธิภาพของข้อความที่ไม่ได้เข้ารหัสเมื่อความยาวไบนารีที่ป้อนเข้าจะไม่ใช่ผลคูณของ 3 ไบต์ (โดยปกติอักขระที่ไม่ใช่แผ่นรองสุดท้ายจะถูกเข้ารหัสเพื่อให้บล็อก 6 บิตสุดท้ายที่แสดงถึงจะเป็นศูนย์ - วางบนบิตที่มีนัยสำคัญน้อยที่สุดอักขระแผ่นรองส่วนใหญ่อาจเกิดขึ้นที่ส่วนท้ายของสตรีมที่เข้ารหัส) "

ฉันเขียนโปรแกรมที่สามารถเข้ารหัส base64 สตริงใดก็ได้และถอดรหัสสตริงที่เข้ารหัส base64 ใด ๆ padding แก้ปัญหาอะไรได้บ้าง?

คำตอบ:


215

ข้อสรุปของคุณว่าการเพิ่มช่องว่างนั้นไม่จำเป็นนั้นถูกต้อง เป็นไปได้เสมอที่จะกำหนดความยาวของอินพุตโดยไม่คลุมเครือจากความยาวของลำดับที่เข้ารหัส

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

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

แก้ไข: ภาพประกอบ

สมมติว่าเรามีโปรแกรมที่เข้ารหัสคำ base64 เชื่อมต่อคำเหล่านั้นและส่งผ่านเครือข่าย มันเข้ารหัส "I", "AM" และ "TJM" ประกบผลลัพธ์เข้าด้วยกันโดยไม่ต้องเว้นช่องว่างและส่งข้อมูลเหล่านั้น

  • Iเข้ารหัสเป็นSQ( SQ==ด้วยช่องว่างภายใน)
  • AMเข้ารหัสเป็นQU0( QU0=ด้วยช่องว่างภายใน)
  • TJMเข้ารหัสเป็นVEpN( VEpNด้วยช่องว่างภายใน)

SQQU0VEpNดังนั้นข้อมูลที่ส่งเป็น รับ base64-ถอดรหัสนี้เช่นแทนที่จะตั้งใจI\x04\x14\xd1Q) IAMTJMผลลัพธ์ที่ได้เป็นเรื่องไร้สาระเนื่องจากผู้ส่งได้ทำลายข้อมูลเกี่ยวกับตำแหน่งที่แต่ละคำลงท้ายในลำดับที่เข้ารหัส ถ้าผู้ส่งได้ส่งSQ==QU0=VEpNแทนรับอาจมีการถอดรหัสนี้เป็นสามลำดับ base64 IAMTJMแยกต่างหากซึ่งจะเชื่อมเพื่อให้

ทำไมต้องรำคาญกับ Padding?

ทำไมไม่ออกแบบโปรโตคอลให้นำหน้าแต่ละคำด้วยความยาวจำนวนเต็ม จากนั้นเครื่องรับสามารถถอดรหัสสตรีมได้อย่างถูกต้องและไม่จำเป็นต้องมีช่องว่างภายใน

นั่นเป็นความคิดที่ดีตราบใดที่เราทราบความยาวของข้อมูลที่กำลังเข้ารหัสก่อนที่เราจะเริ่มเข้ารหัส แต่ถ้าแทนที่จะเป็นคำพูดเรากำลังเข้ารหัสวิดีโอจากกล้องถ่ายทอดสดล่ะ? เราอาจไม่ทราบความยาวของแต่ละชิ้นล่วงหน้า

หากโปรโตคอลใช้ช่องว่างภายในก็ไม่จำเป็นต้องส่งความยาวเลย ข้อมูลอาจถูกเข้ารหัสตามที่ส่งเข้ามาจากกล้องโดยแต่ละชิ้นจะสิ้นสุดด้วยช่องว่างภายในและผู้รับจะสามารถถอดรหัสสตรีมได้อย่างถูกต้อง

เห็นได้ชัดว่าเป็นตัวอย่างที่ได้รับการออกแบบมาอย่างดี แต่บางทีมันอาจแสดงให้เห็นว่าเหตุใดช่องว่างภายในจึงมีประโยชน์ในบางสถานการณ์


24
+1 คำตอบเดียวที่ให้คำตอบที่สมเหตุสมผลนอกจาก "เพราะเราชอบคำฟุ่มเฟือยและความซ้ำซ้อนด้วยเหตุผลที่อธิบายไม่ได้"
ไม่ถูกต้อง

1
ใช้งานได้ดีสำหรับชิ้นส่วนที่เข้ารหัสอย่างชัดเจน แต่คาดว่าจะเชื่อมต่อกันอย่างแยกไม่ออกหลังจากถอดรหัส หากคุณส่ง U0FNSQ == QU0 = คุณสามารถสร้างประโยคใหม่ได้ แต่คุณจะสูญเสียคำที่ประกอบเป็นประโยคไป ดีกว่าไม่มีอะไรฉันเดา โดยเฉพาะอย่างยิ่งโปรแกรม GNU base64 จะจัดการการเข้ารหัสแบบเรียงต่อกันโดยอัตโนมัติ
Marcelo Cantos

2
จะเกิดอะไรขึ้นถ้าความยาวของคำเป็นผลคูณของ 3? วิธีการเชื่อมต่อที่โง่เขลานี้ทำลายข้อมูล (คำลงท้าย) ไม่ใช่การลบช่องว่างภายใน
GreenScape

2
การเรียงต่อกันของ Base64 ช่วยให้ตัวเข้ารหัสประมวลผลชิ้นส่วนขนาดใหญ่แบบขนานโดยไม่ต้องมีภาระในการจัดแนวขนาดชิ้นให้เป็นผลคูณของสาม ในทำนองเดียวกันในรายละเอียดการใช้งานอาจมีตัวเข้ารหัสอยู่ที่นั่นซึ่งจำเป็นต้องล้างบัฟเฟอร์ข้อมูลภายในที่มีขนาดที่ไม่ใช่ผลคูณของสาม
Andre D

2
คำตอบนี้อาจทำให้คุณคิดว่าคุณสามารถถอดรหัสบางอย่างเช่น "SQ == QU0 = VEpN" ได้โดยเพียงแค่ส่งให้ตัวถอดรหัส ดูเหมือนว่าคุณทำไม่ได้เช่นการใช้งานในจาวาสคริปต์และ php ไม่รองรับสิ่งนี้ เริ่มต้นด้วยสตริงที่ต่อกันคุณจะต้องถอดรหัสครั้งละ 4 ไบต์หรือแยกสตริงหลังจากเติมอักขระ ดูเหมือนว่าการใช้งานเหล่านั้นจะเพิกเฉยต่อตัวอักษร padding แม้ว่าจะอยู่ตรงกลางของสตริงก็ตาม
โรมัน

39

ในบันทึกที่เกี่ยวข้องนี่คือตัวแปลงพื้นฐานสำหรับการแปลงฐานโดยพลการที่ฉันสร้างขึ้นสำหรับคุณ สนุก! https://convert.zamicol.com/

ตัวอักษร Padding คืออะไร?

อักขระเว้นวรรคช่วยตอบสนองความต้องการด้านความยาวและไม่มีความหมาย

ตัวอย่างทศนิยมของการ เว้นวรรค:เนื่องจากข้อกำหนดที่กำหนดเองสตริงทั้งหมดมีความยาว 8 อักขระจำนวน 640 สามารถตอบสนองความต้องการนี้โดยใช้ 0 นำหน้าเป็นอักขระช่องว่างภายในเนื่องจากไม่มีความหมาย "00000640"

การเข้ารหัสไบนารี

กระบวนทัศน์ไบต์:ไบต์เป็นหน่วยวัดมาตรฐานโดยพฤตินัยและรูปแบบการเข้ารหัสใด ๆ จะต้องสัมพันธ์กับไบต์

Base256เหมาะกับกระบวนทัศน์นี้ หนึ่งไบต์เท่ากับหนึ่งอักขระใน base256

Base16เลขฐานสิบหกหรือฐานสิบหกใช้ 4 บิตสำหรับแต่ละอักขระ หนึ่งไบต์สามารถแทนอักขระ base16 ได้สองตัว

Base64ไม่พอดีกับกระบวนทัศน์ไบต์ (หรือฐาน 32) ไม่เท่ากันซึ่งแตกต่างจาก base256 และ base16 อักขระ base64 ทั้งหมดสามารถแสดงเป็น 6 บิตสั้น 2 บิตของไบต์เต็ม

เราสามารถเป็นตัวแทนของ base64 เข้ารหัสเมื่อเทียบกับกระบวนทัศน์ไบต์เป็นเศษส่วน: 6 บิตต่อตัวละครกว่า 8 บิตต่อไบต์ เศษส่วนที่ลดลงคือ 3 ไบต์ส่วนอักขระ 4 ตัว

อัตราส่วนนี้ 3 ไบต์สำหรับทุกๆ 4 อักขระ base64 เป็นกฎที่เราต้องการปฏิบัติตามเมื่อเข้ารหัส base64 การเข้ารหัส Base64 สามารถรับประกันได้แม้กระทั่งการวัดด้วยบันเดิล 3 ไบต์ซึ่ง แตกต่างจาก base16 และ base256 ที่ทุกไบต์สามารถยืนได้ด้วยตัวเอง

ดังนั้นทำไมช่องว่างภายในได้รับการสนับสนุนแม้ว่าการเข้ารหัสจะทำงานได้ดีโดยไม่มีอักขระช่องว่างหรือไม่

หากไม่ทราบความยาวของสตรีมหรือหากทราบแน่ชัดว่าสตรีมข้อมูลสิ้นสุดลงเมื่อใดให้ใช้ช่องว่างภายใน อักขระช่องว่างภายในจะสื่อสารอย่างชัดเจนว่าจุดพิเศษเหล่านั้นควรว่างเปล่าและขจัดความคลุมเครือใด ๆ แม้ว่าจะไม่ทราบความยาวของช่องว่างคุณจะทราบว่าสตรีมข้อมูลของคุณสิ้นสุดที่ใด

ตามตัวอย่างตัวนับมาตรฐานบางอย่างเช่นJOSEไม่อนุญาตให้ใช้อักขระช่องว่างภายใน ในกรณีนี้หากมีบางอย่างขาดหายไปลายเซ็นการเข้ารหัสจะไม่ทำงานหรืออักขระอื่น ๆ ที่ไม่ใช่ base64 จะหายไป (เช่น ".") แม้ว่าจะไม่มีการตั้งสมมติฐานเกี่ยวกับความยาว แต่ก็ไม่จำเป็นต้องมีช่องว่างภายในเพราะหากมีสิ่งผิดปกติก็จะไม่ได้ผล

และนี่คือสิ่งที่base64 RFC พูด

ในบางสถานการณ์ไม่จำเป็นต้องใช้หรือใช้ padding ("=") ในข้อมูลที่เข้ารหัสพื้นฐาน ในกรณีทั่วไปเมื่อไม่สามารถตั้งสมมติฐานเกี่ยวกับขนาดของข้อมูลที่ขนส่งได้จำเป็นต้องมีช่องว่างภายในเพื่อให้ได้ข้อมูลที่ถอดรหัสถูกต้อง

[... ]

ขั้นตอนการเติมในฐาน 64 [... ] หากนำไปใช้ไม่ถูกต้องจะนำไปสู่การเปลี่ยนแปลงข้อมูลที่เข้ารหัสที่ไม่สำคัญ ตัวอย่างเช่นหากอินพุตเป็นเพียงหนึ่งอ็อกเต็ตสำหรับการเข้ารหัสฐาน 64 สัญลักษณ์แรกทั้งหกบิตจะถูกใช้ แต่จะใช้เพียงสองบิตแรกของสัญลักษณ์ถัดไปเท่านั้น บิตแพดเหล่านี้ต้องถูกตั้งค่าเป็นศูนย์โดยใช้ตัวเข้ารหัสซึ่งอธิบายไว้ในคำอธิบายเกี่ยวกับช่องว่างภายในด้านล่าง หากไม่มีคุณสมบัตินี้จะไม่มีการแสดงข้อมูลที่เข้ารหัสฐานตามมาตรฐานและสามารถถอดรหัสสตริงที่เข้ารหัสฐานหลายตัวให้เป็นข้อมูลไบนารีเดียวกันได้ หากคุณสมบัตินี้ (และอื่น ๆ ที่กล่าวถึงในเอกสารนี้) มีการรับรองการเข้ารหัสที่ยอมรับได้

Padding ช่วยให้เราสามารถถอดรหัสการเข้ารหัส base64 โดยสัญญาว่าจะไม่มีบิตสูญหาย หากไม่มีช่องว่างภายในจะไม่มีการยอมรับอย่างชัดเจนเกี่ยวกับการวัดในกลุ่มสามไบต์อีกต่อไป หากไม่มีช่องว่างภายในคุณอาจไม่สามารถรับประกันการสร้างการเข้ารหัสต้นฉบับได้อย่างถูกต้องโดยไม่มีข้อมูลเพิ่มเติมจากที่อื่นในสแต็กของคุณเช่น TCP เช็คซัมหรือวิธีการอื่น ๆ

ตัวอย่าง

นี่คือตัวอย่างแบบฟอร์ม RFC 4648 ( http://tools.ietf.org/html/rfc4648#section-8 )

อักขระแต่ละตัวในฟังก์ชัน "BASE64" ใช้หนึ่งไบต์ (base256) จากนั้นเราแปลเป็น 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.)

นี่คือตัวเข้ารหัสที่คุณสามารถเล่นได้: http://www.motobit.com/util/base64-decoder-encoder.asp


16
-1 เป็นโพสต์ที่ดีและละเอียดถี่ถ้วนเกี่ยวกับการทำงานของระบบตัวเลข แต่ไม่ได้อธิบายว่าเหตุใดจึงใช้ช่องว่างภายในเมื่อการเข้ารหัสทำงานได้อย่างสมบูรณ์โดยไม่ต้อง
Matti Virkkunen

2
คุณอ่านคำถามแล้วหรือยัง? คุณไม่จำเป็นต้องมีช่องว่างภายในเพื่อถอดรหัสอย่างถูกต้อง
นาวิน

3
ฉันคิดว่าในความเป็นจริงคำตอบนี้สามารถอธิบายเหตุผลได้ตามที่ระบุไว้ที่นี่: "เราไม่สามารถรับประกันการทำซ้ำการเข้ารหัสต้นฉบับได้อีกต่อไปโดยไม่มีข้อมูลเพิ่มเติม" มันง่ายมากที่ช่องว่างภายในทำให้เรารู้ว่าเราได้รับการเข้ารหัสที่สมบูรณ์ ทุกครั้งที่คุณมี 3 ไบต์คุณสามารถสรุปได้อย่างปลอดภัยว่าสามารถดำเนินการต่อและถอดรหัสได้อย่างปลอดภัยคุณไม่ต้องกังวลว่าฮึ่ม ...
Didier A.

@DidierA. คุณรู้ได้อย่างไรว่าไม่มีอีก 3 ไบต์ในสตริงย่อย base64 ในการถอดรหัส a char*คุณต้องมีขนาดของสตริงหรือเทอร์มิเนเตอร์ว่าง Padding ซ้ำซ้อน ดังนั้นคำถามของ OP
นาวิน

4
@Navin หากคุณกำลังสตรีมถอดรหัส base64 ไบต์คุณไม่ทราบความยาวด้วยช่องว่าง 3 ไบต์คุณจะรู้ว่าทุกครั้งที่คุณมี 3 ไบต์คุณสามารถประมวลผลอักขระ 4 ตัวได้จนกว่าจะถึงจุดสิ้นสุดของสตรีม หากไม่มีคุณอาจต้องย้อนรอยเนื่องจากไบต์ถัดไปอาจทำให้อักขระก่อนหน้าเปลี่ยนไปดังนั้นคุณจึงมั่นใจได้ว่าคุณถอดรหัสถูกต้องเมื่อคุณไปถึงจุดสิ้นสุดของสตรีมแล้วเท่านั้น ดังนั้นมันจึงไม่มีประโยชน์มากนัก แต่มันก็มีบางกรณีที่คุณอาจต้องการมัน
Didier A.

2

ไม่มีประโยชน์มากนักในยุคปัจจุบัน ลองดูนี่เป็นคำถามว่าจุดประสงค์ทางประวัติศาสตร์ดั้งเดิมอาจเป็นอย่างไร

Base64 เข้ารหัสทำให้ปรากฏตัวครั้งแรกในRFC 1421ลงวันที่ปี 1993 นี้จะเน้น RFC จริงในการเข้ารหัสอีเมลและ base64 อธิบายไว้ในส่วนเล็ก ๆ แห่งหนึ่ง 4.3.2.4

RFC นี้ไม่ได้อธิบายวัตถุประสงค์ของการขยาย สิ่งที่ใกล้เคียงที่สุดที่เราต้องพูดถึงจุดประสงค์ดั้งเดิมคือประโยคนี้:

ควอนตัมการเข้ารหัสแบบเต็มจะเสร็จสมบูรณ์ในตอนท้ายของข้อความเสมอ

ไม่แนะนำให้เชื่อมต่อกัน (คำตอบด้านบนที่นี่) หรือความสะดวกในการนำไปใช้เป็นจุดประสงค์ที่ชัดเจนสำหรับช่องว่างภายใน อย่างไรก็ตามเมื่อพิจารณาจากคำอธิบายทั้งหมดแล้วการสันนิษฐานว่าอาจมีจุดมุ่งหมายเพื่อช่วยให้ตัวถอดรหัสอ่านอินพุตในหน่วย 32 บิต ( "ควอนต้า" ) ไม่ได้โดยไม่มีเหตุผล นั่นไม่มีประโยชน์ในปัจจุบันอย่างไรก็ตามในปี 1993 รหัส C ที่ไม่ปลอดภัยจะมีแนวโน้มที่จะใช้ประโยชน์จากคุณสมบัตินี้อย่างแท้จริง


1
ในกรณีที่ไม่มีช่องว่างภายในความพยายามที่จะเชื่อมสองสตริงเมื่อความยาวของสตริงแรกไม่ใช่ผลคูณสามมักจะทำให้ได้สตริงที่ดูเหมือนถูกต้อง แต่เนื้อหาของสตริงที่สองจะถอดรหัสไม่ถูกต้อง การเพิ่มช่องว่างภายในช่วยให้มั่นใจได้ว่าจะไม่เกิดขึ้น
supercat

1
@supercat ถ้านั่นคือเป้าหมายมันจะไม่ง่ายกว่าไหมที่จะจบทุกสตริง base64 ด้วย "=" เดียว? ความยาวเฉลี่ยจะสั้นลงและยังป้องกันการต่อข้อมูลที่ผิดพลาด
Roman Starkov

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