การคำนวณความยาว Base64


155

หลังจากอ่าน base64 wiki ...

ฉันกำลังพยายามหาว่าสูตรทำงานอย่างไร:

รับสายอักขระที่มีความยาวความยาวnbase64 จะเป็นป้อนคำอธิบายรูปภาพที่นี่

ซึ่งเป็น : 4*Math.Ceiling(((double)s.Length/3)))

ฉันรู้แล้วว่าความยาว base64 จะต้อง%4==0ยอมให้ตัวถอดรหัสรู้ว่าความยาวข้อความต้นฉบับคืออะไร

จำนวนสูงสุดของการขยายลำดับสามารถหรือ===

wiki: จำนวนไบต์เอาต์พุตต่อไบต์อินพุตประมาณ 4/3 (โอเวอร์เฮด 33%)

คำถาม:

วิธีทำข้อมูลข้างต้นชำระกับระยะเวลาในการส่งออก ป้อนคำอธิบายรูปภาพที่นี่?

คำตอบ:


210

อักขระแต่ละตัวถูกใช้เพื่อแสดงถึง 6 บิต ( log2(64) = 6)

ดังนั้น 4 4 * 6 = 24 bits = 3 bytesตัวอักษรที่ใช้แทน

ดังนั้นคุณต้องใช้4*(n/3)ตัวอักษรเพื่อแทนnค่าไบต์และนี่จะต้องปัดเศษเป็นทวีคูณของ 4

จำนวนตัว padding ที่ไม่ได้ใช้ซึ่งเป็นผลมาจากการปัดเศษเป็นทวีคูณของ 4 จะเป็น 0, 1, 2 หรือ 3


ช่องว่างภายในอยู่ที่ไหน
Royi Namir

1
พิจารณาว่าคุณมีหนึ่งไบต์ของอินพุต ที่จะสร้างผลลัพธ์สี่ตัวอักษร แต่จำเป็นต้องมีอักขระเอาต์พุตเพียงสองตัวเพื่อเข้ารหัสอินพุต ดังนั้นตัวละครทั้งสองจะได้รับการแพ็ดดิ้ง
David Schwartz

2
ความยาวของเอาต์พุตจะถูกปัดขึ้นเป็นทวีคูณของ 4 ดังนั้น 1, 2 หรือ 3 ไบต์อินพุต => 4 ตัวอักษร; 4, 5 หรือ 6 ไบต์อินพุต => 8 ตัวอักษร; 7, 8 หรือ 9 input bytes => 12 ตัวอักษร
พอล R

5
ฉันอธิบายทั้งหมดนี้ในคำตอบข้างต้น: (i) ถ่านแต่ละตัวแทน 6 บิตของอินพุต, (ii) 4 ตัวอักษรเอาต์พุตดังนั้นแทน 4 * 6 = 24 บิต , (iii) 24 บิตคือ 3 ไบต์ , (iv) 3 ไบต์ของอินพุตดังนั้นผลลัพธ์ใน 4 charsของเอาต์พุต (v) อัตราส่วนของcharsเอาต์พุตต่อไบต์อินพุตดังนั้น 4/3
Paul R

2
@ techie_28: ฉันทำมัน 27308 ตัวอักษรสำหรับ 20 * 1024 ไบต์ แต่ฉันยังไม่ได้ดื่มกาแฟเมื่อเช้านี้
Paul R

60

4 * n / 3 ให้ความยาวเบาะ

และปัดเศษขึ้นเป็นพหุคูณที่ใกล้เคียงที่สุดของ 4 สำหรับการขยายและขณะที่ 4 คือกำลังของ 2 สามารถใช้การดำเนินการเชิงตรรกะแบบบิต

((4 * n / 3) + 3) & ~3

1
คุณพูดถูก! -> 4 * n / 3 ให้ความยาวที่ไม่ได้บุนวม! คำตอบข้างต้นไม่ถูกต้อง -> ((4 * n / 3) + 3) & ~ 3 ส่งคืนผลลัพธ์ที่ถูกต้อง
Cadburry

ไม่ทำงานเป็นอินพุตสำหรับ API CryptBinaryToStringA ของหน้าต่าง
TarmoPikaro

เพื่อสะกดมันออกมาสำหรับผู้ใช้เชลล์:$(( ((4 * n / 3) + 3) & ~3 ))
starfry

1
4 * n / 3ล้มเหลวไปแล้วn = 1หนึ่งไบต์ถูกเข้ารหัสโดยใช้อักขระสองตัวและผลลัพธ์คืออักขระหนึ่งตัว
Maarten Bodewes

1
@Crog ตามที่เขียนไว้หาก n = 1 คุณจะได้ 4/3 = 1 โดยใช้จำนวนเต็ม ตามที่คุณระบุผลลัพธ์ที่คาดหวังคือ 2 ไม่ใช่ 1
Maarten Bodewes

25

สำหรับการอ้างอิงสูตรความยาวของเครื่องเข้ารหัส Base64 มีดังนี้:

สูตรความยาวของตัวเข้ารหัส Base64

ดังที่คุณกล่าวไว้ตัวเข้ารหัส Base64 ที่กำหนดให้nไบต์ของข้อมูลจะสร้างสตริงของ4n/3อักขระ Base64 อีกวิธีหนึ่งข้อมูลทุก 3 ไบต์จะส่งผลให้มีตัวอักษร 4 Base64 แก้ไข : ความคิดเห็นอย่างถูกต้องชี้ให้เห็นว่ากราฟิกก่อนหน้าของฉันไม่ได้มีส่วนร่วมในการขยาย; Ceiling(4n/3)สูตรที่ถูกต้องคือ

บทความ Wikipedia แสดงให้เห็นอย่างชัดเจนว่าสตริง ASCII Man เข้ารหัสลงในสตริง Base64 อย่างไรTWFuในตัวอย่าง สายป้อนคือ 3 ไบต์หรือ 24 บิตในขนาดดังนั้นสูตรได้อย่างถูกต้องคาดการณ์การส่งออกจะมี 4 ไบต์ (หรือ 32 บิต) TWFuยาว: กระบวนการเข้ารหัสข้อมูลทุก 6 บิตให้เป็นหนึ่งใน 64 ตัวอักษร 64 Base64 ดังนั้นการป้อนข้อมูลแบบ 24 บิตหารด้วย 6 ผลลัพธ์จะเป็นตัวอักษร 4 Base64

คุณถามความคิดเห็นว่าขนาดของการเข้ารหัส123456จะเป็นเท่าไหร่ โปรดทราบว่าอักขระทุกตัวของสตริงนั้นคือ 1 ไบต์หรือ 8 บิตในขนาด (สมมติว่าการเข้ารหัส ASCII / UTF8) เรากำลังเข้ารหัสข้อมูล 6 ไบต์หรือ 48 บิต (6 bytes / 3 bytes) * 4 characters = 8 charactersตามสมการที่เราคาดว่าระยะเวลาที่จะออก

วาง123456เป็น Base64 เข้ารหัสสร้างMTIzNDU2ซึ่งเป็น 8 ตัวอักษรยาวเช่นเดียวกับที่เราคาดหวัง


5
ใช้สูตรนี้ระวังว่ามันไม่ได้ให้ความยาวของเบาะ ดังนั้นคุณสามารถมีความยาวได้อีกต่อไป
Spilarix

การคำนวณไบต์ถอดรหัสคาดว่าจากข้อความ base64 floor((3 * (length - padding)) / 4)ผมใช้สูตร ตรวจสอบต่อไปสรุปสาระสำคัญ
Kurt Vangraefschepe

13

จำนวนเต็ม

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

สำหรับเรื่องนี้เป็นความคิดที่ดีที่จะจำวิธีการแบ่งเพดาน: ceil(x / y)ในคู่สามารถเขียนเป็น(x + y - 1) / y(ในขณะที่หลีกเลี่ยงตัวเลขติดลบ แต่ระวังของล้น)

อ่านง่าย

หากคุณไปเพื่อความสามารถในการอ่านคุณสามารถโปรแกรมได้เช่นกัน (เช่นใน Java สำหรับ C คุณสามารถใช้มาโครได้แน่นอน):

public static int ceilDiv(int x, int y) {
    return (x + y - 1) / y;
}

public static int paddedBase64(int n) {
    int blocks = ceilDiv(n, 3);
    return blocks * 4;
}

public static int unpaddedBase64(int n) {
    int bits = 8 * n;
    return ceilDiv(bits, 6);
}

// test only
public static void main(String[] args) {
    for (int n = 0; n < 21; n++) {
        System.out.println("Base 64 padded: " + paddedBase64(n));
        System.out.println("Base 64 unpadded: " + unpaddedBase64(n));
    }
}

inlined

มีเบาะ

เรารู้ว่าเราต้องการบล็อก 4 ตัวอักษรในแต่ละครั้งสำหรับ 3 ไบต์ (หรือน้อยกว่า) ดังนั้นสูตรจะกลายเป็น (สำหรับ x = n และ y = 3):

blocks = (bytes + 3 - 1) / 3
chars = blocks * 4

หรือรวม:

chars = ((bytes + 3 - 1) / 3) * 4

คอมไพเลอร์ของคุณจะปรับให้เหมาะสม3 - 1ดังนั้นเพียงปล่อยไว้เช่นนี้เพื่อรักษาความสามารถในการอ่าน

unpadded

ที่พบได้น้อยกว่าคือตัวแปรที่ไม่เพิ่มจำนวนสำหรับเรื่องนี้เราจำได้ว่าเราแต่ละคนต้องการอักขระสำหรับแต่ละ 6 บิตโดยปัดเศษขึ้น:

bits = bytes * 8
chars = (bits + 6 - 1) / 6

หรือรวม:

chars = (bytes * 8 + 6 - 1) / 6

อย่างไรก็ตามเราสามารถยังคงหารด้วยสอง (ถ้าเราต้องการ):

chars = (bytes * 4 + 3 - 1) / 3

อ่านไม่ได้

ในกรณีที่คุณไม่เชื่อถือคอมไพเลอร์ของคุณเพื่อทำการปรับแต่งขั้นสุดท้ายสำหรับคุณ (หรือถ้าคุณต้องการสร้างความสับสนให้เพื่อนร่วมงานของคุณ):

มีเบาะ

((n + 2) / 3) << 2

unpadded

((n << 2) | 2) / 3

ดังนั้นเราจึงมีสองวิธีในการคำนวณแบบตรรกะและเราไม่ต้องการสาขาใด ๆ บิต -ops หรือ modulo ops - เว้นแต่ว่าเราต้องการ

หมายเหตุ:

  • เห็นได้ชัดว่าคุณอาจต้องเพิ่ม 1 ในการคำนวณเพื่อรวมไบต์การเลิกจ้างที่เป็นโมฆะ
  • สำหรับ Mime คุณอาจต้องดูแลตัวอักขระการสิ้นสุดบรรทัดที่เป็นไปได้และเช่นนั้น (มองหาคำตอบอื่นสำหรับสิ่งนั้น)

5

ฉันคิดว่าคำตอบที่ให้นั้นพลาดจุดคำถามเดิมซึ่งเป็นจำนวนพื้นที่ที่ต้องจัดสรรเพื่อให้เหมาะกับการเข้ารหัส base64 สำหรับสตริงไบนารี่ที่มีความยาว n ไบต์

คำตอบคือ (floor(n / 3) + 1) * 4 + 1

ซึ่งรวมถึงการแพ็ดดิ้งและการยกเลิกอักขระโมฆะ คุณอาจไม่จำเป็นต้องโทรฟลอร์ถ้าคุณกำลังทำเลขคณิตจำนวนเต็ม

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


5
สูตรของคุณผิด พิจารณา n = 3 ผลลัพธ์ที่คาดหวัง (โดยไม่มีการเว้นว่างว่าง) คือ 4 แต่สูตรของคุณจะคืนค่า 8
CodesInChaos

5
ฉันยังคิดว่าการรวม null terminator นั้นโง่โดยเฉพาะเมื่อเรากำลังพูดถึง. net ที่นี่
CodesInChaos

ทำงานอย่างถูกต้องใน windows โดยใช้ CryptBinaryToStringA คะแนนของฉันสำหรับสิ่งนี้
TarmoPikaro

5

นี่คือฟังก์ชั่นในการคำนวณขนาดดั้งเดิมของไฟล์ Base 64 ที่เข้ารหัสเป็น String ในหน่วย KB:

private Double calcBase64SizeInKBytes(String base64String) {
    Double result = -1.0;
    if(StringUtils.isNotEmpty(base64String)) {
        Integer padding = 0;
        if(base64String.endsWith("==")) {
            padding = 2;
        }
        else {
            if (base64String.endsWith("=")) padding = 1;
        }
        result = (Math.ceil(base64String.length() / 4) * 3 ) - padding;
    }
    return result / 1000;
}

3

ในขณะที่คนอื่นกำลังโต้เถียงสูตรเกี่ยวกับพีชคณิตฉันก็แค่ใช้ BASE64 เองเพื่อบอกฉัน:

$ echo "Including padding, a base64 string requires four bytes for every three-byte chunk of the original string, including any partial chunks. One or two bytes extra at the end of the string will still get converted to four bytes in the base64 string when padding is added. Unless you have a very specific use, it is best to add the padding, usually an equals character. I added an extra byte for a null character in C, because ASCII strings without this are a little dangerous and you'd need to carry the string length separately."| wc -c

525

$ echo "Including padding, a base64 string requires four bytes for every three-byte chunk of the original string, including any partial chunks. One or two bytes extra at the end of the string will still get converted to four bytes in the base64 string when padding is added. Unless you have a very specific use, it is best to add the padding, usually an equals character. I added an extra byte for a null character in C, because ASCII strings without this are a little dangerous and you'd need to carry the string length separately." | base64 | wc -c

710

ดังนั้นดูเหมือนว่าสูตร 3 ไบต์จะถูกแทนด้วย 4 เบส 64 ตัวอักษรดูเหมือนว่าถูกต้อง


1
ฉันมีบางอย่างกับการคำนวณที่ต้องใช้หน่วยความจำและเวลา CPU มากในขณะที่การคำนวณสามารถทำได้ใน 1 ns และหนึ่งหรือสองการลงทะเบียน
Maarten Bodewes

ดังนั้นเมื่อคุณพยายามจัดการกับข้อมูลเลขฐานสองที่ไม่รู้จัก - วิธีนี้จะช่วยได้อย่างไร?
UKMonkey

คำถามคือทั้งหมดที่เกี่ยวกับสูตรซึ่งช่วยในการคำนวณขนาดผลลัพธ์โดยไม่ต้องทำ base64 เอง ในขณะที่คำตอบนี้มีประโยชน์ในบางสถานการณ์ แต่ก็ไม่ได้ช่วยอะไรกับคำถามนี้
Alejandro

3

(ในความพยายามที่จะให้รวบรัด แต่สมบูรณ์มา)

ทุกไบต์อินพุตมี 8 บิตดังนั้นสำหรับnอินพุตไบต์ที่เราได้รับ:

บิตอินพุตn × 8

ทุกๆ 6 บิตเป็นไบต์เอาต์พุตดังนั้น:

ceil ( n × 8/6 ) =  ไบต์เอาต์พุตceil ( n × 4/3 )

นี่คือไม่มีการขยาย

ด้วยการแพ็ดดิ้งเราจะปัดเศษเอาต์พุตเป็นจำนวนมากถึงสี่ในสี่ไบต์:

ceil ( ceil ( n × 4/3 ) / 4) × 4 =  ceil ( n × 4/3/4 ) × 4 =  ceil ( n / 3) × 4 ไบต์เอาท์พุท

ดูหน่วยงานซ้อนกัน (Wikipedia) สำหรับความเท่าเทียมแรก

การใช้ arithmetics จำนวนเต็มceil ( n / m )สามารถคำนวณได้เป็น( n + m - 1) div mดังนั้นเราจะได้รับ:

( n * 4 + 2) div 3 โดยไม่มีช่องว่างภายใน

( n + 2) div 3 * 4 พร้อมช่องว่างภายใน

สำหรับภาพประกอบ:

 n   with padding    (n + 2) div 3 * 4    without padding   (n * 4 + 2) div 3 
------------------------------------------------------------------------------
 0                           0                                      0
 1   AA==                    4            AA                        2
 2   AAA=                    4            AAA                       3
 3   AAAA                    4            AAAA                      4
 4   AAAAAA==                8            AAAAAA                    6
 5   AAAAAAA=                8            AAAAAAA                   7
 6   AAAAAAAA                8            AAAAAAAA                  8
 7   AAAAAAAAAA==           12            AAAAAAAAAA               10
 8   AAAAAAAAAAA=           12            AAAAAAAAAAA              11
 9   AAAAAAAAAAAA           12            AAAAAAAAAAAA             12
10   AAAAAAAAAAAAAA==       16            AAAAAAAAAAAAAA           14
11   AAAAAAAAAAAAAAA=       16            AAAAAAAAAAAAAAA          15
12   AAAAAAAAAAAAAAAA       16            AAAAAAAAAAAAAAAA         16

ในที่สุดในกรณีของการเข้ารหัส MIME Base64 จำเป็นต้องใช้สองไบต์เพิ่มเติม (CR LF) ต่อทุก ๆ เอาต์พุต 76 ไบต์ปัดขึ้นหรือลงขึ้นอยู่กับว่าต้องการการขึ้นบรรทัดใหม่หรือไม่


ขอบคุณสำหรับการวิเคราะห์อย่างละเอียด
P Satish Patro

2

ดูเหมือนว่าสูตรที่เหมาะสมควรเป็น:

n64 = 4 * (n / 3) + (n % 3 != 0 ? 4 : 0)

Ascii zero fill ไม่ได้ถูกนำมาพิจารณา - ไม่ทำงานใน Windows (CryptBinaryToStringA)
TarmoPikaro

1

ฉันเชื่อว่าคำตอบนี้เป็นคำตอบที่ถูกต้องหาก n% 3 ไม่เป็นศูนย์ใช่หรือไม่

    (n + 3-n%3)
4 * ---------
       3

รุ่น Mathematica:

SizeB64[n_] := If[Mod[n, 3] == 0, 4 n/3, 4 (n + 3 - Mod[n, 3])/3]

มีความสุข

GI


1

implementantion ง่าย ๆ ใน javascript

function sizeOfBase64String(base64String) {
    if (!base64String) return 0;
    const padding = (base64String.match(/(=*)$/) || [])[1].length;
    return 4 * Math.ceil((base64String.length / 3)) - padding;
}

1

สำหรับทุกคนที่พูด C ลองดูมาโครสองตัวนี้:

// calculate the size of 'output' buffer required for a 'input' buffer of length x during Base64 encoding operation
#define B64ENCODE_OUT_SAFESIZE(x) ((((x) + 3 - 1)/3) * 4 + 1) 

// calculate the size of 'output' buffer required for a 'input' buffer of length x during Base64 decoding operation
#define B64DECODE_OUT_SAFESIZE(x) (((x)*3)/4) 

ที่นำมาจากที่นี่


1

ฉันไม่เห็นสูตรแบบง่ายในการตอบกลับอื่น ๆ ตรรกะครอบคลุมอยู่ แต่ฉันต้องการรูปแบบพื้นฐานที่สุดสำหรับการใช้งานแบบฝังของฉัน:

  Unpadded = ((4 * n) + 2) / 3

  Padded = 4 * ((n + 2) / 3)

หมายเหตุ: เมื่อคำนวณจำนวนที่ไม่เพิ่มเราจะปัดเศษจำนวนเต็มเช่นเพิ่ม Divisor-1 ซึ่งคือ +2 ในกรณีนี้


0

ใน windows - ฉันต้องการประมาณขนาดของบัฟเฟอร์ขนาด mime64 แต่สูตรการคำนวณที่แม่นยำทั้งหมดไม่ได้ผลสำหรับฉัน - ในที่สุดฉันก็จบลงด้วยสูตรโดยประมาณดังนี้:

ขนาดการจัดสรรสตริง Mine64 (โดยประมาณ) = (((4 * ((ขนาดบัฟเฟอร์ไบนารี) + 1)) / 3) + 1)

ดังนั้น +1 ล่าสุด - ใช้สำหรับ ascii-zero - อักขระสุดท้ายต้องจัดสรรเพื่อจัดเก็บการสิ้นสุดศูนย์ - แต่ทำไม "ขนาดบัฟเฟอร์ของไบนารี" คือ + 1 - ฉันสงสัยว่ามีอักขระการสิ้นสุด mime64 บางตัว หรืออาจเป็นปัญหาการจัดตำแหน่งบางอย่าง


0

หากมีคนสนใจที่จะประสบความสำเร็จในการแก้ปัญหา @Pedro Silva ใน JS ฉันเพียงแค่นำเสนอโซลูชันเดียวกันนี้:

const getBase64Size = (base64) => {
  let padding = base64.length
    ? getBase64Padding(base64)
    : 0
  return ((Math.ceil(base64.length / 4) * 3 ) - padding) / 1000
}

const getBase64Padding = (base64) => {
  return endsWith(base64, '==')
    ? 2
    : 1
}

const endsWith = (str, end) => {
  let charsFromEnd = end.length
  let extractedEnd = str.slice(-charsFromEnd)
  return extractedEnd === end
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.