'一些有關base64的坑,不要再犯這些錯了'

Java 百度 莫非技術棧 2019-08-25
"
"
一些有關base64的坑,不要再犯這些錯了

前言

最近在和百度對接中,需要通過base64加密的方式對數據加密並校驗。對方環境是jdk1.8的,我們的環境是jdk1.7的,在對接中遇到一些問題,所以總結一下。

概述

Base64是一種字符串編碼格式,採用了A-Z,a-z,0-9,“+”和“/”這64個字符來編碼原始字符(還有墊字符“=”)。一個字符本身是一個字節,也就是8位,而base64編碼後的一個字符只能表示6位的信息。也就是原始字符串中的3字節的信息編碼會變成4字節的信息。Base64的主要作用是滿足MIME的傳輸需求。

在Java8中Base64編碼已經成為Java類庫的標準,且內置了Base64編碼的編碼器和解碼器。

64字符碼錶(還有墊字符“=”)

"
一些有關base64的坑,不要再犯這些錯了

前言

最近在和百度對接中,需要通過base64加密的方式對數據加密並校驗。對方環境是jdk1.8的,我們的環境是jdk1.7的,在對接中遇到一些問題,所以總結一下。

概述

Base64是一種字符串編碼格式,採用了A-Z,a-z,0-9,“+”和“/”這64個字符來編碼原始字符(還有墊字符“=”)。一個字符本身是一個字節,也就是8位,而base64編碼後的一個字符只能表示6位的信息。也就是原始字符串中的3字節的信息編碼會變成4字節的信息。Base64的主要作用是滿足MIME的傳輸需求。

在Java8中Base64編碼已經成為Java類庫的標準,且內置了Base64編碼的編碼器和解碼器。

64字符碼錶(還有墊字符“=”)

一些有關base64的坑,不要再犯這些錯了

一、base64介紹

java7中base64加密的時候,一行不超過76個字符,超過了就會添加回車換行。

在網上看到一個一說法:java8的base64加密是不換行的,同理java8也不能解碼java7環境加密的base64。我想用事實說話。

public static void main(String[] args) throws IOException {
String aa = "abc123456789ababc123456789abc123456789abc123456789ababc123456789abc123456789abc123456789";
//jdk1.8的base64編碼getMimeEncoder
String encode = Base64.getEncoder().encodeToString(aa.getBytes("utf-8"));
System.out.println("~~~jdk1.8的base64編碼encode~~~"+encode);
//jdk1.8的base64編碼getMimeEncoder
String mimeEncode = Base64.getMimeEncoder().encodeToString(aa.getBytes("utf-8"));
System.out.println("~~~jdk1.8的base64編碼mimeEncode~~~"+mimeEncode);
//jdk1.7的base64編碼
sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
String aaEncode = encoder.encode(aa.getBytes("utf-8"));
System.out.println("~~~jdk1.7的base64編碼encode~~~:"+aaEncode);
//jdk1.7的base64解碼
sun.misc.BASE64Decoder aaDecoder = new sun.misc.BASE64Decoder();
byte[] aaByte = aaDecoder.decodeBuffer(aaEncode);
System.out.println("~~~jdk1.7的base64解碼decodeBuffer~~~~"+new String(aaByte,"utf-8"));
//jdk1.8的base64解碼
String mimeDecode = new String(Base64.getMimeDecoder().decode(aaEncode), "utf-8");
System.out.println("~~~jdk1.8的base64編碼mimeDecode~~~~:"+mimeDecode);
//jdk1.8的base64解碼
String decode = new String(Base64.getDecoder().decode(aaEncode), "utf-8");
System.out.println("~~~jdk1.8的base64編碼decode~~~~:"+decode);
}

結果


~~~jdk1.8的base64編碼encode~~~YWJjMTIzNDU2Nzg5YWJhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OQ==
~~~jdk1.8的base64編碼mimeEncode~~~YWJjMTIzNDU2Nzg5YWJhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmFiYzEy
MzQ1Njc4OWFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OQ==
~~~jdk1.7的base64編碼encode~~~:YWJjMTIzNDU2Nzg5YWJhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmFiYzEy
MzQ1Njc4OWFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OQ==
~~~jdk1.7的base64解碼decodeBuffer~~~~abc123456789ababc123456789abc123456789abc123456789ababc123456789abc123456789abc123456789
~~~jdk1.8的base64編碼mimeDecode~~~~:abc123456789ababc123456789abc123456789abc123456789ababc123456789abc123456789abc123456789
Exception in thread "main" java.lang.IllegalArgumentException: Illegal base64 character d
at java.util.Base64$Decoder.decode0(Unknown Source)
at java.util.Base64$Decoder.decode(Unknown Source)
at java.util.Base64$Decoder.decode(Unknown Source)
at com.bootdo.system.controller.LoginController.main(LoginController.java:123)

從上面的運行結果分析,我們可以得到幾個結論

1、java8中通過Encoder對象進行base64編碼不換行,通過MimeEncoder對象編碼換行。

2、java8中通過MimeDecoder可以正常解碼java7中base64編碼,Decoder會報異常。

一、碰到的問題

對方是java8環境,並且接口中用的Encoder和decode進行編碼和解碼,在對方不修改接口的前提下,我們是java1.7的環境,應該什麼處理呢?

測試代碼


 public static void main(String[] args) throws IOException {
String aa = "abc123456789ababc123456789abc123456789abc123456789ababc123456789abc123456789abc123456789";

//jdk1.7的base64編碼
sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
String aaEncode = encoder.encode(aa.getBytes("utf-8"));
System.out.println("~~~jdk1.7的base64編碼encode~~~:"+aaEncode);

//jdk1.8的base64解碼
String decode = new String(Base64.getDecoder().decode(aaEncode), "utf-8");
System.out.println("~~~jdk1.8的base64編碼decode~~~~:"+decode);
}

結果


~~~jdk1.7的base64編碼~~~~:YWJjMTIzNDU2Nzg5YWJhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmFiYzEy
Exception in thread "main" java.lang.IllegalArgumentException: Illegal base64 character d
at java.util.Base64$Decoder.decode0(Unknown Source)
at java.util.Base64$Decoder.decode(Unknown Source)
at java.util.Base64$Decoder.decode(Unknown Source)
at com.bootdo.system.controller.LoginController.main(LoginController.java:103)

從上面的運行結果可以看出來,java7中使用的base64已經換行,java8解密的時候回報錯,無效的base64編碼。

解決辦法一

假如我們去掉java7中“回車換行符”呢,是不是就可以解決問題了呢?

修改代碼


 //jdk1.7的base64編碼 
sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
String aaEncode = encoder.encode(aa.getBytes("utf-8")).replaceAll("[\\\\s*\\t\\n\\r]", "");
System.out.println("~~~jdk1.7的base64編碼~~~:"+aaEncode);

運行結果


~~~jdk1.8的base64編碼~~~YWJjMTIzNDU2Nzg5YWJhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OQ==
~~~jdk1.7的base64編碼~~~:YWJjMTIzNDU2Nzg5YWJhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OQ==
~~~jdk1.7的base64解碼~~~~abc123456789ababc123456789abc123456789abc123456789ababc123456789abc123456789abc123456789
~~~jdk1.8的base64編碼~~~~:abc123456789ababc123456789abc123456789abc123456789ababc123456789abc123456789abc123456789

可見去掉回車換行符對base64的解密並沒有影響,完美解決問題。

\\s*表示任意數量的空白,\\t製表符,\\n回車,\\t換行

但是java7就沒有不換行的base64加密嗎?

解決辦法二

經過自己測試,發現java7環境其實還是有不少第三方類庫可以解決上面的問題

測試代碼


 public static void main(String[] args) throws IOException {
String aa = "abc123456789ababc123456789abc123456789abc123456789ababc123456789abc123456789abc123456789";
sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
String aaEncode01 = encoder.encode(aa.getBytes("utf-8"));
System.out.println("~~~~編碼後aaEncode01~~~~:"+aaEncode01);
String aaEncode02 = org.apache.catalina.util.Base64.encode(aa.getBytes("utf-8"));
System.out.println("~~~~編碼後aaEncode02~~~~:"+aaEncode02);
String aaEncode03 = org.apache.axiom.util.base64.Base64Utils.encode(aa.getBytes("utf-8"));
System.out.println("~~~~編碼後aaEncode03~~~~:"+aaEncode03);
}

運行結果


~~~~編碼後aaEncode01~~~~:YWJjMTIzNDU2Nzg5YWJhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmFiYzEy
MzQ1Njc4OWFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OQ==
~~~~編碼後aaEncode02~~~~:YWJjMTIzNDU2Nzg5YWJhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OQ==
~~~~編碼後aaEncode03~~~~:YWJjMTIzNDU2Nzg5YWJhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmMxMjM0NTY3ODlhYmFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OWFiYzEyMzQ1Njc4OQ==

總結:

1、java7可以正常解碼java8的編碼

2、替換“回車換行符”,或者使用Base64Utils等類庫,java8可以正常解碼

二、base64在通過http請求傳輸中遇到的問題

假如base64編碼的結果中有+,通過http傳輸的時候,發現對方接受到的數據不能正確解碼,這是為什麼呢?

測試代碼


 public static void main(String[] args) throws Exception {

BASE64Encoder encoder = new BASE64Encoder();
String encode = encoder.encode("測試數據?哈哈哈".getBytes("utf-8"));
System.out.println("對字節數組Base64編碼encode~~~:"+encode);
sendPost("http://10.11.110.46:8080/rh-ryb-int/bhChildAnnuity/getImg", "imgBase="+encode);
}
//結果帶有加號
對字節數組Base64編碼encode~~~:5rWL6K+V5pWw5o2u77yf5ZOI5ZOI5ZOI

//接收
@RequestMapping("getImg")
public void getImg(String imgBase) {
System.out.println(imgBase);
}
//結果
5rWL6K V5pWw5o2u77yf5ZOI5ZOI5ZOI

我們可以發現接收的數據+號沒有了。base64編碼傳輸的時候,+號會被替換成空格,這樣解碼會有異常。

解決一

把請求數據進行URL編碼傳輸


 public static void main(String[] args) throws Exception {

BASE64Encoder encoder = new BASE64Encoder();
String encode = encoder.encode("測試數據?哈哈哈aa".getBytes("utf-8"));
System.out.println("對字節數組Base64編碼encode~~~:"+encode);
sendPost("http://10.11.110.46:8080/rh-ryb-int/bhChildAnnuity/getImg", "imgBase="+URLEncoder.encode(encode));
}
//結果
對字節數組Base64編碼encode~~~:5rWL6K+V5pWw5o2u77yf5ZOI5ZOI5ZOIYWE=
//獲取到的請求數據
@RequestMapping("getImg")
public void getImg(String imgBase) {
System.out.println(imgBase);
//不可以用url解碼URLDecoder.decode(imgBase)
System.out.println(URLDecoder.decode(imgBase));
}
//獲取的結果
5rWL6K+V5pWw5o2u77yf5ZOI5ZOI5ZOIYWE=
5rWL6K V5pWw5o2u77yf5ZOI5ZOI5ZOIYWE=

在接收數據的時候不可以用url解碼URLDecoder.decode(imgBase),這樣的話會把+號變為空格,

解決二

就是把空格替換成+


 @RequestMapping("getImg")
public void getImg(String imgBase) {
System.out.println(imgBase.replaceAll(" ", "+"));
}

解決三

在java8中base64會把+轉換-,把/轉換為_,通過java8自己的UrlDecoder對象進行解碼就可以了


 public static void main(String[] args) throws Exception {

String encodeToString = Base64.getUrlEncoder().encodeToString("測試數據+哈/哈哈aa".getBytes("utf-8"));
System.out.println("對字節數組Base64編碼UrlEncoder~~~:"+encodeToString);
byte[] decode = Base64.getUrlDecoder().decode(encodeToString);
System.out.println("對字節數組Base64編碼decode~~~:"+new String(decode));

}
//結果
對字節數組Base64編碼UrlEncoder~~~:5rWL6K-V5pWw5o2uK-WTiC_lk4jlk4hhYQ==
對字節數組Base64編碼decode~~~:測試數據+哈/哈哈aa

我看有些接口是通過base64編碼後進行數據傳輸的,因為base64可以反編譯,其實是不安全的,不建議使用。


"

相關推薦

推薦中...