В этой статье изначально планировалось написать две части, первая — запись недавно возникших ошибок, связанных с Base64, а вторая — объяснение принципа кодирования Base64. В итоге половину написал и нашел, а? Почему так долго говорить о том, что не сложно? Не способствует чтению и пониманию (на самом деле мне немного лень сегодня идти на досуг и развлечение), поэтому подробное объяснение принципа кодирования Base64 будет приведено в следующей статье, так что следите за обновлениями.
0x01 Обнаружен феномен
A предоставляет интерфейс для B, а параметры интерфейса кодируются и передаются в формате Base64.
Но когда A декодирует параметры, переданные B, в Base64, возникает ошибка:
Illegal base64 character a
0x02 Анализ причины
После поиска я обнаружил, что это яма, на которую наступили многие пользователи сети.Короче говоря, существуют разные реализации кодировщика/декодера Base64, и некоторые из них несовместимы друг с другом.
Например, явление, с которым я столкнулся выше, можно полностью смоделировать и воспроизвести с помощью следующего кода:
package org.mazhuang.base64test;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.util.Base64Utils;
import sun.misc.BASE64Encoder;
@SpringBootApplication
public class Base64testApplication implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
byte[] content = "It takes a strong man to save himself, and a great man to save another.".getBytes();
String encrypted = new BASE64Encoder().encode(content);
byte[] decrypted = Base64Utils.decodeFromString(encrypted);
System.out.println(new String(decrypted));
}
public static void main(String[] args) {
SpringApplication.run(Base64testApplication.class, args);
}
}
Приведенное выше выполнение кода сообщит об исключении:
Caused by: java.lang.IllegalArgumentException: Illegal base64 character a
at java.util.Base64$Decoder.decode0(Base64.java:714) ~[na:1.8.0_202-release]
at java.util.Base64$Decoder.decode(Base64.java:526) ~[na:1.8.0_202-release]
Примечание:Если строка в тестовом коде очень короткая, например «Hello, World», ее можно нормально декодировать.
То есть кодирование с помощью sun.misc.BASE64Encoder и декодирование с помощью org.springframework.util.Base64Utils проблематично Мы можем использовать их для кодирования приведенных выше строк по отдельности, а затем вывести, чтобы увидеть разницу. Тестовый код:
byte[] content = "It takes a strong man to save himself, and a great man to save another.".getBytes();
System.out.println(new BASE64Encoder().encode(content));
System.out.println("--- 华丽的分隔线 ---");
System.out.println(Base64Utils.encodeToString(content));
вывод:
SXQgdGFrZXMgYSBzdHJvbmcgbWFuIHRvIHNhdmUgaGltc2VsZiwgYW5kIGEgZ3JlYXQgbWFuIHRv
IHNhdmUgYW5vdGhlci4=
--- 华丽的分隔线 ---
SXQgdGFrZXMgYSBzdHJvbmcgbWFuIHRvIHNhdmUgaGltc2VsZiwgYW5kIGEgZ3JlYXQgbWFuIHRvIHNhdmUgYW5vdGhlci4=
Видно, что содержимое, закодированное с помощью sun.misc.BASE64Encoder, упаковано, а ASCII-кодировка новой строки точно равна 0x0a, так что это кажется логичным. Давайте проследим его дальше, чтобы найти источник этого несоответствия.
0x03 идет еще дальше
Удерживая нажатой клавишу CTRL или COMMAND, щелкните имя метода в IDEA, чтобы перейти к его реализации.
3.1 sun.misc.BASE64Encoder.encode
Этот метод записи в основном включает два класса, BASE64Encoder и CharacterEncoder в пакете sun.misc, из которых последний является родительским классом для первого.
Метод кодирования, который действительно работает, находится в файле CharacterEncoder, аннотированная версия выглядит следующим образом:
public void encode(InputStream inStream, OutputStream outStream)
throws IOException {
int j;
int numBytes;
// bytesPerLine 在 BASE64Encoder 里实现,返回 57
byte tmpbuffer[] = new byte[bytesPerLine()];
// 用 outStream 构造一个 PrintStream
encodeBufferPrefix(outStream);
while (true) {
// 读取最多 57 个 bytes
numBytes = readFully(inStream, tmpbuffer);
if (numBytes == 0) {
break;
}
// 啥也没干
encodeLinePrefix(outStream, numBytes);
// 每次处理 3 bytes,编码成 4 bytes,不足位的补 0 位和 '='
for (j = 0; j < numBytes; j += bytesPerAtom()) {
// ...
}
if (numBytes < bytesPerLine()) {
break;
} else {
// 换行
encodeLineSuffix(outStream);
}
}
// 啥也没干
encodeBufferSuffix(outStream);
}
Затем в комментариях класса CharacterEncoder мы можем увидеть закодированный формат:
[Buffer Prefix]
[Line Prefix][encoded data atoms][Line Suffix]
[Buffer Suffix]
В сочетании с классом реализации BASE64Encoder префикс буфера, суффикс буфера и префикс строки пусты, а суффикс строки\n
.
Пока что мы нашли часть переноса строки в реализации — в этой реализации кодировщика 57 байт читаются как строка для кодирования (76 байт после кодирования).
3.2 org.springframework.util.Base64Utils.encodeToString
Этот метод написания в основном включает два класса: org.springframework.util.Base64Utils и java.util.Base64, Видно, что первый в основном является инкапсуляцией второго.
Наконец, метод записи Base64Utils.encodeToString использует кодировщик Base64.Encoder.RFC4648:
// isURL = false,newline = null,linemax = -1,doPadding = true
static final Encoder RFC4648 = new Encoder(false, null, -1, true);
Обратите внимание на значения newline и linemax.
Затем посмотрите на метод Base64.encode0, где реализовано фактическое кодирование:
private int encode0(byte[] src, int off, int end, byte[] dst) {
// ...
while (sp < sl) {
// ...
// 这个条件不会满足,不会加换行
if (dlen == linemax && sp < end) {
for (byte b : newline){
dst[dp++] = b;
}
}
}
// ...
return dp;
}
Итак... в этой реализации нет новой строки.
0x04 Сводка
После приведенного выше анализа правда стала ясна, то есть реализации двух энкодеров разные.Обратим внимание на использование совпадающего кодека в процессе разработки.Это нормально, то есть энкодер, под которым находится пакет Java используется для кодирования, используйте тот же кодек. Соответствующий декодер в пакете декодирует.
Насчет того, почему разные реализации, какие есть подноготные, обиды и ненависть между ними, подробные принципы Base64 и т.д., у меня толстое лицо, и приглашаю всех послушать очередную декомпозицию! :-П
Если вам интересны мои статьи, вы можете в любое время подписаться на мой общедоступный аккаунт WeChat «Men Sao Programmer», чтобы узнать больше.