jdk1.8 base64注意事项
由于jdk1.7和jdk1.8內(nèi)置的Base64遵守的RFC協(xié)議不一致,jdk1.7按照照RFC1521實現(xiàn)的,jdk1.8是按照rfc4648和rfc2045兩個協(xié)議來實現(xiàn)的。具體可以從類注釋中查詢到。由于協(xié)議的不同可能導(dǎo)致jdk1.8的解碼jdk1.7編碼的數(shù)據(jù)時拋出java.lang.IllegalArgumentException: Illegal base64 character a異常.因此需要特別注意保持解碼編碼的一致性。
jdk7的編碼結(jié)果包含換行;
jdk8的編碼結(jié)果不包含換行;
jdk8無法解碼包含換行的編碼結(jié)果;
既然得知上述異常產(chǎn)生的由于,故找到解決方案也很簡單。
-----------------------------------------------------------------------------------------
概述
Base64是一種字符串編碼格式,采用了A-Z,a-z,0-9,“+”和“/”這64個字符來編碼原始字符(還有墊字符“=”)。一個字符本身是一個字節(jié),也就是8位,而base64編碼后的一個字符只能表示6位的信息。也就是原始字符串中的3字節(jié)的信息編碼會變成4字節(jié)的信息。Base64的主要作用是滿足MIME的傳輸需求。?
在Java8中Base64編碼已經(jīng)成為Java類庫的標(biāo)準(zhǔn),且內(nèi)置了Base64編碼的編碼器和解碼器。
問題
偶然發(fā)現(xiàn)使用jdk8內(nèi)置的Base64解碼器進行解析的時候,會拋出java.lang.IllegalArgumentException: Illegal base64 character a異常。?
這非常奇怪,因為原文是使用jdk7里面的編碼器進行編碼的,理論上不至于發(fā)生這種不兼容的狀況。
測試程序
還是來寫程序測試一下問題到底在哪里。
| 測試程序使用了一個比較長的原文,主要是這個問題在原文較長的時候才會出現(xiàn),如果原文較短(字節(jié)長度不超過57),那么不會有這個問題。 |
1?使用jdk7進行編碼:
import sun.misc.BASE64Encoder; public class TestBase64JDK7 {private static final String TEST_STRING = "0123456789,0123456789,0123456789,0123456789,0123456789,0123456789,0123456789";public static void main(String[] args) {BASE64Encoder base64Encoder = new BASE64Encoder();String base64Result = base64Encoder.encode(TEST_STRING.getBytes());System.out.println(base64Result);} }2?jdk7編碼結(jié)果:
MDEyMzQ1Njc4Oe+8jDAxMjM0NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4Oe+8jDAxMjM0 NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4OQ==3?使用jdk8對上面的編碼結(jié)果進行解碼:
import java.util.Base64; public class TestBase64JDK8 {public static void main(String[] args) {String base64Result = "MDEyMzQ1Njc4Oe+8jDAxMjM0NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4Oe+8jDAxMjM0\n" +"NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4OQ==";Base64.getDecoder().decode(base64Result);} }4?結(jié)果就如最開始描述的那樣,會拋出異常:
Exception in thread "main" java.lang.IllegalArgumentException: Illegal base64 character aat java.util.Base64$Decoder.decode0(Base64.java:714)at java.util.Base64$Decoder.decode(Base64.java:526)at java.util.Base64$Decoder.decode(Base64.java:549)at com.francis.TestBase64JDK8.main(TestBase64JDK8.java:14)難道說jdk7和jdk8在base64的處理上有什么不一樣???
5?繼續(xù)來看一下jdk8對原文的編碼:
import java.util.Base64; public class TestBase64JDK8 {private static final String TEST_STRING = "0123456789,0123456789,0123456789,0123456789,0123456789,0123456789,0123456789";public static void main(String[] args) {String base64Result = Base64.getEncoder().encodeToString(TEST_STRING.getBytes());System.out.println(base64Result);} }6?jdk8編碼結(jié)果:
MDEyMzQ1Njc4Oe+8jDAxMjM0NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4Oe+8jDAxMjM0NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4OQ==至此針對比較長的原文進行base64編碼可以得到如下結(jié)論:
- jdk7的編碼結(jié)果包含換行;
- jdk8的編碼結(jié)果不包含換行;
- jdk8無法解碼包含換行的編碼結(jié)果;
| jdk8的編碼結(jié)果使用jdk7進行解碼,沒有任何問題,不再演示。 |
現(xiàn)在問題原因基本清楚了,是由于jdk7的編碼結(jié)果包含換行,導(dǎo)致jdk8解碼的時候拋出異常。?
但是為什么會有這種差異呢?難道使用的base64的標(biāo)準(zhǔn)還不一樣?
問題排查
繼續(xù)排查問題,先從類注釋入手,看看是不是理解有誤。
1 先來看看jdk8中的Base64類注釋,這里只列出一些關(guān)鍵內(nèi)容:
/*** This class consists exclusively of static methods for obtaining* encoders and decoders for the Base64 encoding scheme. The* implementation of this class supports the following types of Base64* as specified in* <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and* <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.** <ul>* <li><a name="basic"><b>Basic</b></a>* <p> Uses "The Base64 Alphabet" as specified in Table 1 of* RFC 4648 and RFC 2045 for encoding and decoding operation.* The encoder does not add any line feed (line separator)* character. The decoder rejects data that contains characters* outside the base64 alphabet.</p></li>...* @author Xueming Shen* @since 1.8*/大意是說:
這個類包含了base64編碼格式的編碼方法和解碼方法,而且實現(xiàn)是按照rfc4648和rfc2045兩個協(xié)議來實現(xiàn)的。 編碼和解碼操作是照著兩個協(xié)議中的'Table 1'中指定的'The Base64 Alphabet'來的。編碼器不會添加任何換行符,解碼器只會處理'The Base64 Alphabet'范圍內(nèi)的數(shù)據(jù),如果不在這個范圍內(nèi),解碼器會拒絕處理。看到這里就可以理解為什么jdk8的編碼結(jié)果不包含換行了。?
| 另外,基本上可以猜到為什么jdk8無法解碼jdk7的編碼結(jié)果了(換行符應(yīng)該不在The base64 alphabet當(dāng)中)。 |
2 先來看一眼兩個標(biāo)準(zhǔn)中的the base64 alphabet(兩個標(biāo)準(zhǔn)中的這個表是一樣的):
Table 1: The Base 64 AlphabetValue Encoding Value Encoding Value Encoding Value Encoding0 A 17 R 34 i 51 z1 B 18 S 35 j 52 02 C 19 T 36 k 53 13 D 20 U 37 l 54 24 E 21 V 38 m 55 35 F 22 W 39 n 56 46 G 23 X 40 o 57 57 H 24 Y 41 p 58 68 I 25 Z 42 q 59 79 J 26 a 43 r 60 810 K 27 b 44 s 61 911 L 28 c 45 t 62 +12 M 29 d 46 u 63 /13 N 30 e 47 v14 O 31 f 48 w (pad) =15 P 32 g 49 x16 Q 33 h 50 y并不包含換行符,這就可以解釋為什么jdk8無法解碼包含換行的編碼結(jié)果。
3 再來看一下jdk7中sun.misc.BASE64Encoder的類注釋:
This class implements a BASE64 Character encoder as specified in RFC1521. This RFC is part of the MIME specification as published by the Internet Engineering Task Force (IETF). Unlike some other encoding schemes there is nothing in this encoding that indicates where a buffer starts or ends. This means that the encoded text will simply start with the first line of encoded text and end with the last line of encoded text.這個實現(xiàn)是按照RFC1521來的,類注釋中并沒有關(guān)于編碼或者解碼約束的說明。
4 那繼續(xù)看一下rfc1521的關(guān)鍵部分(鏈接:https://tools.ietf.org/html/rfc1521)。
在5.2. Base64 Content-Transfer-Encoding章節(jié)有如下內(nèi)容:
The output stream (encoded bytes) must be represented in lines of nomore than 76 characters each. All line breaks or other charactersnot found in Table 1 must be ignored by decoding software. In base64data, characters other than those in Table 1, line breaks, and otherwhite space probably indicate a transmission error, about which awarning message or even a message rejection might be appropriateunder some circumstances.這里明確規(guī)定了:
編碼結(jié)果的每一行不能超過76個字符;
解碼的字符必須在:Tbale 1(也就是之前提到的the base64 alphabet)、換行符和空白符這個范圍內(nèi);
這就是為什么jdk7的編碼結(jié)果包含換行。?
這樣根據(jù)類注釋和rfc協(xié)議內(nèi)容,就可以解釋上面通過測試代碼得到的結(jié)論,也就可以理解為什么會產(chǎn)生這個問題。
?
| ‘sun’開頭的包并不屬于java規(guī)范,是sun公司的實現(xiàn),所以jdk7中的這種base64編碼方式并不是java的規(guī)范。 |
解決辦法
那么,怎么解決這個問題呢:?
1. 使用apache common包中的org.apache.commons.codec.binary.Base64類進行編碼和解碼;?
2. 編碼之后或解碼之前去除換行符;?
3. 編碼和解碼使用相同的jdk版本;
其他Base64庫
看看其他類庫是怎么處理base64的。?
1. Apache Common
Apache Common中的org.apache.commons.codec.binary.Base64類是基于rfc2045實現(xiàn)的,根據(jù)類注釋可以了解到此實現(xiàn)解碼時忽略了所有不在the base64 alphabet范圍內(nèi)的字符,所以該實現(xiàn)可以處理包含換行符的base64編碼結(jié)果。?
同時該類的編碼方法提供了參數(shù),用于指定編碼結(jié)果長度在超過76個字符的時候是否添加換行,默認(rèn)不換行。
Spring Core
Spring Core提供了Base64Utils類,該類只是一個工具類,并沒有實現(xiàn)任何協(xié)議。
優(yōu)先使用java8中的java.util.Base64類進行編碼和解碼;
如果java.util.Base64不存在,則會使用org.apache.commons.codec.binary.Base64;
如果都不存在,則會報錯
協(xié)議簡述
通過上面的排查步驟可以看到,rfc1521、rfc2045和rfc4648中關(guān)于base64的部分似乎不太一樣,接下來分別簡單看一下這三個協(xié)議是如何規(guī)范base64編碼的換行的。
rfc1521(鏈接:https://tools.ietf.org/html/rfc1521)?
該協(xié)議是關(guān)于MIME的,Base64是MIME支持的一種編碼類型。關(guān)鍵內(nèi)容5.2. Base64 Content-Transfer-Encoding章節(jié)已經(jīng)在上文中簡單闡述過了,主要是規(guī)定了:編碼結(jié)果每行長度和解碼字符的范圍。?
該協(xié)議已經(jīng)被淘汰。?
jdk7基于該協(xié)議實現(xiàn)base64,所以編碼結(jié)果會包含換行符。
?
| MIME:Multipurpose Internet Mail Extensions,多用途互聯(lián)網(wǎng)郵件擴展類型。是一個互聯(lián)網(wǎng)標(biāo)準(zhǔn),最早用于電子郵件系統(tǒng),后來被應(yīng)用到瀏覽器。服務(wù)器會將它們發(fā)送的多媒體數(shù)據(jù)的類型告訴瀏覽器,而通知手段就是說明該多媒體數(shù)據(jù)的MIME類型。 |
2.rfc2045(鏈接:https://tools.ietf.org/html/rfc2045)
該協(xié)議同樣是關(guān)于MIME的,是rfc1521的更新版本,關(guān)鍵內(nèi)容6.8. Base64 Content-Transfer-Encoding章節(jié),其中關(guān)于編碼結(jié)果長度和解碼字符范圍的規(guī)定與rfc1521并沒有什么差別。
3.rfc4648
該協(xié)議是關(guān)于base16、base32和base64編碼的。關(guān)于編碼結(jié)果每行長度的說明在3.1. Line Feeds in Encoded Data章節(jié):
?
大意是:
MIME協(xié)議通常作為base64協(xié)議的引用。但是MIME協(xié)議并沒有定義'base64',而是定義了'base64 內(nèi)容傳輸編碼'。因此MIME將base64編碼的數(shù)據(jù)的長度限制為76個字符。...MIME和PEM關(guān)于長度的限制都是用于SMTP的。該協(xié)議的實現(xiàn)在編碼結(jié)果中不能添加換行符,除非引用了該文檔的實現(xiàn)中,明確說明在特定長度之后添加換行符。jdk8的Base64類是基于rfc2045和rfc4648實現(xiàn)的,根據(jù)上文列出的協(xié)議內(nèi)容可以確定,該類的編碼結(jié)果不會包含換行符,而且在類的注釋中明確說明了不會添加換行符。
以上!
總結(jié)
以上是生活随笔為你收集整理的jdk1.8 base64注意事项的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HttpClient 指南思维导图笔记
- 下一篇: 掌控谈话~确保执行