栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Base64编解码原理并用Java手工实现Base64编解码

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Base64编解码原理并用Java手工实现Base64编解码

Base64编解码原理

目前Base64已经成为网络上常见的传输8比特字节代码的编码方式之一。在做支付系统时,系统之间的报文交互都需要使用Base64对明文进行转码,然后进行签名或加密,之后再次Base64编码传输。那么,Base64到底起到什么作用呢?

在参数传输的过程中经常遇到的一种情况:使用全英文的没问题,但一旦涉及到中文就会出现乱码情况。与此类似,网络上传输的字符并不全是可打印的字符,比如二进制文件、图片等。Base64的出现就是为了解决此问题,它是基于64个可打印的字符来表示二进制的数据的一种方法。

电子邮件刚问世的时候,只能传输英文,但后来随着用户的增加,中文、日文等文字的用户也有需求,但这些字符并不能被服务器或网关有效处理,因此Base64就登场了。随之,Base64在URL、Cookie、网页传输少量二进制文件中也有相应的使用。

Base64的原理比较简单,Base64定义了"A-Z、a-z、0-9、+、/"64个可打印字符,这是标准的Base64协议规定。在日常使用中我们还会看到=或==号出现在Base64的编码结果中,=在此是作为填充字符出现,待会会讲到。

Base64编码步骤:

  1. 将待转换的字符串按指定的字符集(如UTF-8、GBk等)编码成字节数组
  2. 每三个字节分为一组,每个字节占8比特,那么每组共有24个比特位
  3. 将上面的24个比特位每6个一组,共分为4组
  4. 在每组前面添加两个0,每组由6个变为8个比特位,总共32个比特位,即四个字节
  5. 按照Base64编码表(如下所示)将四个字节转化为四个对应的字符
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   

15 P  32 g   49 x

16 Q  33 h   50 y

由于Base64是按三个字节来分组,如果最后一组字节数不足三个,那么该如何处理?

  • 两个字节:两个字节共16个比特位,依旧按照规则进行分组。此时总共16个比特位,每6个一组,则第三组缺少2位,低位用0补齐,得到三个Base64编码,第四组完全没有数据则用=补齐
  • 一个字节:一个字节共8个比特位,依旧按照规则进行分组。此时共8个比特位,每6个一组,则第二组缺少4位,低位用0补齐,得到两个Base64编码,而后面两组没有对应数据,都用=补齐

从上面的步骤我们发现:

  • Base64将3个字节共24个比特分成4组,编码后每组对应一个字节,即3个字节编码后变成4个字节,因此Base64编码后要比原文大1/3
  • 为什么使用3个字节一组呢?因为6和8的最小公倍数为24,三个字节正好24个比特位,每6个比特位一组,恰好能够分为4组

注意:

  • 大多数编码都是由字符串转化成二进制,而Base64编码恰恰相反是从二进制转换为字符串
  • Base64编码主要用传输、存储、表示二进制领域,算不上加密,Base64是可以直接解码的
  • 由于Base64是对字节编码,因为同一字符串的不同编码(UTF-8、GBK等)对应的Base64编码不同
  • Base64用6个比特位表示字符,因此称为Base64((2的6次幂为64)。同理,Base32就是用5位,Base16就是用4位

知道了Base64如何编码,那么解码就很容易了,下面说下步骤。
Base64解码步骤:

  1. 按4个字符为一组(Base64编码出来的字符串的长度一定是4的倍数)
  2. 解析拿到每一组有效的24个比特串,如果最后一组有一个=号,则16个比特串有效,最后一组有两个=号,则8个比特串有效(至于是哪些比特串有效,不用再介绍了吧,前面编码步骤中已经介绍过了)
  3. 将有效的比特串转为字节数组,再将字节数组按指定的字符集(如UTF-8、GBk等)转为字符串

参考文章:一篇文章彻底弄懂Base64编码原理

Java实现Base64编解码(不使用任何API)

下面是纯手工实现的Base64编解码,没有用到任何API(为什么这么说呢?因为强大的Java已经有实现了即class java.util.Base64)

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

public class MyBase64 {
    
    public static String encode(String s,String charset) throws UnsupportedEncodingException {
        byte[] arr = s.getBytes(charset);
        int a = arr.length % 3;
        int b = arr.length / 3;
        StringBuilder base64Str = new StringBuilder();
        // 以3个字节为一组
        for (int i = 0; i < 3*b; i=i+3) {
            // 3个字节一共24位,每6位为一组,分为4组。由于是6位,一共有2^6=64个值(0~63),因为称为Base64
            String bits1 = byteToBinaryString(arr[i]).substring(0, 6);
            String bits2 = byteToBinaryString(arr[i]).substring(6, 8)+byteToBinaryString(arr[i+1]).substring(0, 4);
            String bits3 = byteToBinaryString(arr[i+1]).substring(4, 8)+byteToBinaryString(arr[i+2]).substring(0, 2);
            String bits4 = byteToBinaryString(arr[i+2]).substring(2, 8);
            // 计算这4组每一组的值
            int w=compute(bits1);
            int x=compute(bits2);
            int y=compute(bits3);
            int z=compute(bits4);
            // 将每一组的值转换为Base64编码,并拼接在一起
            base64Str.append(convert(w)).append(convert(x)).append(convert(y)).append(convert(z));
        }
        if(a == 1){
            // 分组后,多出来一个字节,共8个bit位,则只能分为两组,即6+2,第三组和第四组直接用=表示,第二组缺失的低四位补0
            String bits1 = byteToBinaryString(arr[3*b]).substring(0, 6);
            String bits2 = byteToBinaryString(arr[3*b]).substring(6, 8)+"0000";
            int w = compute(bits1);
            int x = compute(bits2);
            base64Str.append(convert(w)).append(convert(x)).append('=').append('=');
        }else if(a == 2){
            // 分组后,多出来二个字节,共16个bit位,则只能分为三组,即6+6+4,第四组直接用=表示,第二组缺失的低二位补0
            String bits1 = byteToBinaryString(arr[3*b]).substring(0, 6);
            String bits2 = byteToBinaryString(arr[3*b]).substring(6, 8)+byteToBinaryString(arr[3*b+1]).substring(0, 4);
            String bits3 = byteToBinaryString(arr[3*b+1]).substring(4, 8)+"00";
            int w = compute(bits1);
            int x = compute(bits2);
            int y = compute(bits3);
            base64Str.append(convert(w)).append(convert(x)).append(convert(y)).append('=');
        }
        return base64Str.toString();
    }

    
    public static String decode(String s,String charset) throws UnsupportedEncodingException {
        List bytes = new ArrayList<>();
        // 由于Base64编码是将三个字节作为一组变成4个字节,即4个字符,则解码时按4个字符为一组来进行解码
        for (int i = 0; i < s.length(); i=i+4) {
            if(s.charAt(i+2) == '='){
                // 最后一组有两个=的情况
                // 将字符转为Base64编码
                byte w = convert(s.charAt(i));
                byte x = convert(s.charAt(i+1));
                // 获取比特串
                String bits = byteToBinaryString(w).substring(2, 8) + byteToBinaryString(x).substring(2, 4);
                // 会得到一个字节
                for (int j = 0; j < bits.length(); j=j+8) {
                    bytes.add(binaryStringToByte(bits.substring(j,j+8)));
                }
            }else if(s.charAt(i+3) == '='){
                // 最后一组有一个=的情况
                // 将字符转为Base64编码
                byte w = convert(s.charAt(i));
                byte x = convert(s.charAt(i+1));
                byte y = convert(s.charAt(i+2));
                // 获取比特串
                String bits = byteToBinaryString(w).substring(2, 8) + byteToBinaryString(x).substring(2, 8)+byteToBinaryString(y).substring(2, 6);
                // 会得到两个字节
                for (int j = 0; j < bits.length(); j=j+8) {
                    bytes.add(binaryStringToByte(bits.substring(j,j+8)));
                }
            }else{
                // 无=的情况
                // 将字符转为Base64编码
                byte w = convert(s.charAt(i));
                byte x = convert(s.charAt(i+1));
                byte y = convert(s.charAt(i+2));
                byte z = convert(s.charAt(i+3));
                // 获取比特串
                String bits = byteToBinaryString(w).substring(2, 8) + byteToBinaryString(x).substring(2, 8)
                        + byteToBinaryString(y).substring(2, 8) + byteToBinaryString(z).substring(2, 8);
                // 会得到三个字节
                for (int j = 0; j < bits.length(); j=j+8) {
                    bytes.add(binaryStringToByte(bits.substring(j,j+8)));
                }
            }
        }
        byte[] arr = new byte[bytes.size()];
        for (int i = 0; i < bytes.size(); i++) {
            arr[i] = bytes.get(i);
        }
        // 按指定字符集将byte数组转为字符串
        return new String(arr,charset);
    }


    
    private static char convert(int w){
        if(w >=0 && w <= 25){
            return (char)(w+65);
        }else if(w >= 26 && w <= 51){
            return (char)(w+71);
        }else if(w >= 52 && w <= 61){
            return (char)(w-4);
        }else if(w == 62){
            return '+';
        }else if(w == 63){
            return '/';
        }
        throw new RuntimeException("convert error");
    }

    
    private static byte convert(char w){
        if(w >=65 && w <= 90){
            return (byte)(w-65);
        }else if(w >= 97 && w <= 122){
            return (byte)(w-71);
        }else if(w >= 48 && w <= 57){
            return (byte)(w+4);
        }else if(w == 43){
            return 62;
        }else if(w == 47){
            return 63;
        }
        throw new RuntimeException("convert error");
    }

    
    private static int compute(String bits){
        int sum = 0;
        for (int j = 0; j < bits.length(); j++) {
            if(bits.charAt(j) == '1'){
                sum+= (int)Math.pow(2,bits.length()-1-j);
            }
        }
        return sum;
    }

    
    private static String byteToBinaryString(byte b){
        // -128特殊处理
        if(b == -128){
            return "10000000";
        }
        byte tmp = b;
        if(tmp<0){
            // 将符号位从1转为0。如-127的二进制为10000001,只需要用乘积取余法得到1的比特串,再将高位0变成1就得到-127的比特串
            tmp = (byte)(tmp & 127);
        }
        // 存储8个比特位
        int index = 7;
        byte[] bits = new byte[8];
        // 开始转换
        byte x = tmp;
        do {
            byte y = (byte) (x%2);
            x = (byte) (x >> 1);
            bits[index--] = y;
        }while (x != 0);
        if(b < 0){
            // 如果是负数,则高位的0变为1
            bits[0] = 1;
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bits.length; i++) {
            sb.append(bits[i]);
        }
        return sb.toString();
    }

    
    private static byte binaryStringToByte(String bits){
        if(bits.length() != 8){
            throw new RuntimeException("binaryStringToByte error");
        }
        if(bits.charAt(0) == '0'){
            return (byte) compute(bits);
        }else if(bits.charAt(0) == '1'){
            if(bits.equals("10000000")){
                return -128;
            }else{
                // 计算负数补码的值
                byte v1 = (byte)compute(bits.substring(1));
                // 计算负数原码的值
                byte v2 = (byte)(~(v1-1));
                int v3 = compute(byteToBinaryString(v2).substring(1));
                return (byte)-v3;
            }
        }
        throw new RuntimeException("binaryStringToByte error");
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        System.out.println(encode("你不要过来啊?!ok","UTF-8"));
        System.out.println(decode("5L2g5LiN6KaB6L+H5p2l5ZWKPyFvaw==","UTF-8"));
    }
}

运行结果如下所示:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/888556.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号