קידוד ופענוח Base64 ב- Java 8

Java 8 תיזכר בעיקר בזכות הצגת lambdas, זרמים, מודל תאריך / שעה חדש ומנוע ה- JavaScript של Nashorn ל- Java. יש שיזכרו גם את Java 8 על הצגת תכונות קטנות אך שימושיות שונות כגון ה- Base64 API. מהו Base64 וכיצד אני משתמש ב- API זה? פוסט זה עונה על שאלות אלו.

מה זה Base64?

Base64 היא ערכת קידוד בינארית לטקסט המייצגת נתונים בינאריים בפורמט מחרוזת ASCII להדפסה על ידי תרגוםם לייצוג radix-64. כל ספרת Base64 מייצגת בדיוק 6 סיביות של נתונים בינאריים.

בקשת Base64 למסמכי תגובה

Base64 תואר לראשונה (אך לא נקרא שם) ב- RFC 1421: שיפור פרטיות לדואר אלקטרוני באינטרנט: חלק א ': נהלי הצפנת הודעות ואימות. מאוחר יותר, הוא הוצג רשמית כ- Base64 ב- RFC 2045: הרחבות דואר אינטרנט רב תכליתיות (MIME), חלק ראשון: פורמט של גופי הודעות באינטרנט, ובעקבות זאת חזרו ל- RFC 4648: קידוד הנתונים Base16, Base32 ו- Base64.

Base64 משמש למניעת שינויים בנתונים בזמן מעבר דרך מערכות מידע, כגון דוא"ל, שאולי אינן נקיות של 8 סיביות (הן עשויות לבלבל ערכים של 8 סיביות). לדוגמא, אתה מצרף תמונה להודעת דואר אלקטרוני ורוצה שהתמונה תגיע לקצה השני מבלי להתבלבל. תוכנת הדוא"ל שלך Base64 מקודדת את התמונה ומכניסה את הטקסט המקביל להודעה, כפי שמוצג להלן:

Content-Disposition: inline; filename=IMG_0006.JPG Content-Transfer-Encoding: base64 /9j/4R/+RXhpZgAATU0AKgAAAAgACgEPAAIAAAAGAAAAhgEQAAIAAAAKAAAAjAESAAMAAAABAAYA AAEaAAUAAAABAAAAlgEbAAUAAAABAAAAngEoAAMAAAABAAIAAAExAAIAAAAHAAAApgEyAAIAAAAU AAAArgITAAMAAAABAAEAAIdpAAQAAAABAAAAwgAABCRBcHBsZQBpUGhvbmUgNnMAAAAASAAAAAEA ... NOMbnDUk2bGh26x2yiJcsoBIrvtPe3muBbTRGMdeufmH+Nct4chUXpwSPk/qK9GtJRMWWVFbZ0JH I4rf2dkZSbOjt7hhEzwcujA4I7Gust75pYVwAPpXn+kzNLOVYD7xFegWEKPkHsM/pU1F0NKbNS32 o24sSCOlaaFYLUhjky4x9PSsKL5bJsdWkAz3xirH2dZLy1DM2C44zx1FZqL2PTXY/9k=

האיור מראה שתמונה מקודדת זו מתחילה /ונגמרת ב =. ...מציין טקסט שאני לא הראיתי לשם קיצור. שים לב שהקידוד כולו עבור דוגמה זו או כל דוגמה אחרת גדול בכ- 33 אחוזים מהנתונים הבינאריים המקוריים.

תוכנת הדואר האלקטרוני של הנמען תפקד את Base64 בתמונת הטקסט המקודדת כדי לשחזר את התמונה הבינארית המקורית. לדוגמא זו, התמונה תוצג בשורה עם שאר ההודעה.

קידוד ופענוח Base64

Base64 מסתמך על אלגוריתמי קידוד ופענוח פשוטים. הם עובדים עם תת-קבוצה של 65 תווים של US-ASCII, כאשר כל אחת מ -64 התווים הראשונים ממפה לרצף בינארי שווה ערך של 6 סיביות. הנה האלף-בית:

Value Encoding Value Encoding Value Encoding Value Encoding 0 A 17 R 34 i 51 z 1 B 18 S 35 j 52 0 2 C 19 T 36 k 53 1 3 D 20 U 37 l 54 2 4 E 21 V 38 m 55 3 5 F 22 W 39 n 56 4 6 G 23 X 40 o 57 5 7 H 24 Y 41 p 58 6 8 I 25 Z 42 q 59 7 9 J 26 a 43 r 60 8 10 K 27 b 44 s 61 9 11 L 28 c 45 t 62 + 12 M 29 d 46 u 63 / 13 N 30 e 47 v 14 O 31 f 48 w (pad) = 15 P 32 g 49 x 16 Q 33 h 50 y

התו ה -65 ( =) משמש לריפוד טקסט מקודד Base64 לגודל אינטגרלי כמוסבר בקרוב.

מאפיין משנה

לקבוצת משנה זו יש את המאפיין החשוב שהיא מיוצגת באופן זהה בכל גרסאות ה- ISO 646, כולל US-ASCII, וכל התווים בקבוצת המשנה מיוצגים זהה בכל הגרסאות של EBCDIC.

אלגוריתם הקידוד מקבל זרם קלט של בתים של 8 סיביות. יש להניח כי זרם זה יוזמן עם הסיבית המשמעותית ביותר תחילה: הסיבית הראשונה היא סיב הסדר הגבוה בבייט הראשון, סיבית השמינית היא הסדר הנמוך בבייט זה, וכן הלאה.

משמאל לימין, בתים אלה מאורגנים בקבוצות של 24 סיביות. מתייחסים לכל קבוצה כארבע קבוצות משולבות של 6 סיביות. כל קבוצה של 6 סיביות מתווספת למערך של 64 התווים להדפסה; הדמות המתקבלת היא פלט.

כאשר פחות מ- 24 ביטים זמינים בסוף הנתונים המקודדים, אפס ביטים מתווספים (מימין) ליצירת מספר אינטגרלי של קבוצות של 6 סיביות. לאחר מכן, =תו פלט אחד או שניים יופיעו. יש לשקול שני מקרים:

  • בית אחד שנותר: ארבעה ביטים אפסים מתווספים לבית זה כדי ליצור שתי קבוצות של 6 סיביות. כל קבוצה מוסיפה את האינדקס למערך ותוצאת תו שנוצר. בעקבות שתי הדמויות הללו =מוצגות שתי תווי כרית.
  • שני בתים שנותרו: שני ביטים אפסים מתווספים לבית השני כדי ליצור שלוש קבוצות של 6 סיביות. כל קבוצה מוסיפה את האינדקס למערך ותוצאת תו שנוצר. בעקבות שלושת התווים הללו, יוצא =תו כרית אחד .

הבה נבחן שלוש דוגמאות כדי ללמוד כיצד פועל אלגוריתם הקידוד. ראשית, נניח שברצוננו לקודד @!*:

Source ASCII bit sequences with prepended 0 bits to form 8-bit bytes: @ ! * 01000000 00100001 00101010 Dividing this 24-bit group into four 6-bit groups yields the following: 010000 | 000010 | 000100 | 101010 These bit patterns equate to the following indexes: 16 2 4 42 Indexing into the Base64 alphabet shown earlier yields the following encoding: QCEq

נמשיך בקיצור רצף הקלט ל @!:

Source ASCII bit sequences with prepended 0 bits to form 8-bit bytes: @ ! 01000000 00100001 Two zero bits are appended to make three 6-bit groups: 010000 | 000010 | 000100 These bit patterns equate to the following indexes: 16 2 4 Indexing into the Base64 alphabet shown earlier yields the following encoding: QCE An = pad character is output, yielding the following final encoding: QCE=

הדוגמה האחרונה מקצרת את רצף הקלט ל @:

Source ASCII bit sequence with prepended 0 bits to form 8-bit byte: @ 01000000 Four zero bits are appended to make two 6-bit groups: 010000 | 000000 These bit patterns equate to the following indexes: 16 0 Indexing into the Base64 alphabet shown earlier yields the following encoding: QA Two = pad characters are output, yielding the following final encoding: QA==

אלגוריתם הפענוח הוא ההפוך מאלגוריתם הקידוד. עם זאת, זה חופשי לנקוט בפעולות מתאימות לאחר זיהוי של תו שאינו באלפבית Base64 או מספר שגוי של תווי כרית.

גרסאות Base64

כמה גרסאות Base64 הומצאו. גרסאות מסוימות דורשות לחלק את זרם הפלט המקודד למספר שורות באורך קבוע כאשר כל שורה לא חורגת ממגבלת אורך מסוימת (ומעט השורה האחרונה) מופרדת מהשורה הבאה באמצעות מפריד קו (החזרת כרכרה \rואחריה הזנת קו \n). אני מתאר את שלוש הגרסאות הנתמכות על ידי API Base64 של Java 8. עיין בערך Base64 של ויקיפדיה לרשימה מלאה של גרסאות.

בסיסי

RFC 4648 מתאר גרסת Base64 המכונה Basic . גרסה זו משתמשת באלפבית Base64 המוצג בטבלה 1 של RFC 4648 ו- RFC 2045 (ומוצג קודם בהודעה זו) לצורך קידוד ופענוח. המקודד מתייחס לזרם הפלט המקודד כשורה אחת; לא יוצא מפריד קו. המפענח דוחה קידוד המכיל תווים מחוץ לאלפבית Base64. שים לב שניתן לבטל תנאים אלה ואחרים.

לְחַקוֹת

RFC 2045 מתאר גרסת Base64 המכונה MIME . גרסה זו משתמשת באלפבית Base64 המוצג בטבלה 1 של RFC 2045 לצורך קידוד ופענוח. זרם הפלט המקודד מאורגן בשורות של לא יותר מ 76 תווים; כל שורה (למעט השורה האחרונה) מופרדת מהשורה הבאה באמצעות מפריד קו. כל מפרידי השורות או תווים אחרים שלא נמצאים באלפבית Base64 מתעלמים במהלך הפענוח.

כתובת אתר ושם קובץ בטוח

RFC 4648 מתאר גרסת Base64 המכונה URL ושם קובץ בטוח . גרסה זו משתמשת באלפבית Base64 המוצג בטבלה 2 של RFC 4648 לצורך קידוד ופענוח. האלפבית זהה האלפבית שראה קודם אלא -מחליפים +ו _מחליף /. לא יוצא מפריד קו. המפענח דוחה קידוד המכיל תווים מחוץ לאלפבית Base64.

קידוד Base64 שימושי בהקשר של נתונים בינאריים ארוכים ובקשות HTTP GET. הרעיון הוא לקודד נתונים אלה ואז להוסיף אותם לכתובת ה- HTTP GET. אם הגרסה הבסיסית או MIME שמשה, כל +או /תווי נתון המקודדים יצטרכו לקודד כתובות לתוך רצפים הקסדצימלי ( +הופך %2Bואת /הופך %2F). מחרוזת ה- URL המתקבלת תהיה ארוכה במקצת. על ידי החלפה +עם -ו /עם _, כתוב אתר ואת שם הקובץ בטוח מייתר את צורך כתובות אתררבים / מפענחים (וההשפעות שלהם על האורכים של ערכים מקודדים). כמו כן, גרסה זו שימושית כאשר יש להשתמש בנתונים המקודדים לשם קובץ מכיוון ששמות הקבצים של יוניקס ו- Windows אינם יכולים להכיל /.

עובד עם ה- Base64 API של Java

Java 8 introduced a Base64 API consisting of the java.util.Base64 class along with its Encoder and Decoder nested static classes. Base64 presents several static methods for obtaining encoders and decoders:

  • Base64.Encoder getEncoder(): Return an encoder for the Basic variant.
  • Base64.Decoder getDecoder(): Return a decoder for the Basic variant.
  • Base64.Encoder getMimeEncoder(): Return an encoder for the MIME variant.
  • Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator): Return an encoder for a modified MIME variant with the given lineLength (rounded down to the nearest multiple of 4 -- output not separated into lines when lineLength<= 0) and lineSeparator. It throws java.lang.IllegalArgumentException when lineSeparator includes any Base64 alphabet character presented in Table 1 of RFC 2045.

    RFC 2045's encoder, which is returned from the noargument getMimeEncoder() method, is rather rigid. For example, that encoder creates encoded text with fixed line lengths (except for the last line) of 76 characters. If you want an encoder to support RFC 1421, which dicates a fixed line length of 64 characters, you need to use getMimeEncoder(int lineLength, byte[] lineSeparator).

  • Base64.Decoder getMimeDecoder(): Return a decoder for the MIME variant.
  • Base64.Encoder getUrlEncoder(): Return an encoder for the URL and Filename Safe variant.
  • Base64.Decoder getUrlDecoder(): Return a decoder for the URL and Filename Safe variant.

Base64.Encoder presents several threadsafe instance methods for encoding byte sequences. Passing the null reference to one of the following methods results in java.lang.NullPointerException:

  • byte[] encode(byte[] src): Encode all bytes in src to a newly-allocated byte array, which this method returns.
  • int encode(byte[] src, byte[] dst): Encode all bytes in src to dst (starting at offset 0). If dst isn't big enough to hold the encoding, IllegalArgumentException is thrown. Otherwise, the number of bytes written to dst is returned.
  • ByteBuffer encode(ByteBuffer buffer): Encode all remaining bytes in buffer to a newly-allocated java.nio.ByteBuffer object. Upon return, buffer's position will be updated to its limit; its limit won't have been changed. The returned output buffer's position will be zero and its limit will be the number of resulting encoded bytes.
  • String encodeToString(byte[] src): Encode all bytes in src to a string, which is returned. Invoking this method is equivalent to executing new String(encode(src), StandardCharsets.ISO_8859_1).
  • Base64.Encoder withoutPadding(): Return an encoder that encodes equivalently to this encoder, but without adding any padding character at the end of the encoded byte data.
  • OutputStream wrap(OutputStream os): עטוף זרם פלט לקידוד נתוני בתים. מומלץ לסגור מייד את זרם הפלט שהוחזר לאחר השימוש, במהלכו הוא יזרוק את כל הבתים שנותרו אפשריים לזרם הפלט הבסיסי. סגירת זרם הפלט המוחזר תסגור את זרם הפלט הבסיסי.

Base64.Decoderמציג מספר שיטות מופע של threadsafe לפענוח רצפי בתים. העברת ההפניה לאפס לאחת מהשיטות הבאות מביאה NullPointerException: