信息摘要技术 - Base64技术

37 篇文章 0 订阅
13 篇文章 0 订阅

Base64不是加密算法,不适合用在加密场景下,但是Base64的效率不错适合用在一些特殊场景下,Base64不是信息摘要算法,但是比较适合整理在信息摘要技术类中。

Base64的产生是解决电子邮件传输问题的,因为最早的电子邮件只允许ASCII码字符,这样如果传输非ASCII码内容,很可能发生乱码;

Base64是双向的,即可以解码;

算法定义

Base64算法是一种基于64个字符的编码,定义在RFC 2045文档中:

Base64内容传送编码是一种以任意8位字节序列组合的描述形式,这种描述不易被人直接识别;

经过Base64编码的内容会比原来数据略长,为原来的4/3倍;

经过Base64编码的字符串的字符数是以4为单位的整数倍;

=作为Base64的补位符,一般结尾会包含=号,即Base64编码;

Base64的结尾最多会有两个“=”号;

衍生

Base64还有很多衍生算法,如Base32、Base16,Http的Get传输时对字符进行Base64编码算法是一种衍生算法:Url Base64。

Url Base64:算法主要替换了Base64字符映射表中的“+”和“/”,将这两个字符替换为“-”和“_”;

补位符:Url Base64有两种补位,“.”和“~”,两种都有实现,Commons Codec完全杜绝补位符;

URL Base64

URL Base64是Base64的变种,目前应用也很广泛。URL Base64仅仅是将一些在URL中不允许或容易引起歧义的字符做了替换:

  • 不允许出现的字符:”+”和”/”,使用“-”和“_”代替;
  • 有歧义的字符:“=”,“=”是URL中的参数分割符号,容易歧义,需要被替换;
    • 使用”.”替换,但是在某些文件系统中,出现两个“.”则认为是错误的,有局限性;
    • 使用“~”代替,但是“~”与文件系统的路径冲突,有局限性;
    • 不适用替换付,即需要“=”的末尾不添加任何字符,这样就成了变长字符,也就是Base64的长度变为原字符串的 4/3倍不再适用。

关于“=”的替换符没有严格标准,规范仅仅做了建议,具体要看自己的使用场景做决定,这要是各个Base64工具的差异地方。

适用场景

Base64算法的大部分适用场景即解决乱码,不时候作为加密手段。

RFC 2045定义了Base64标准,但是有些实现是不严格遵循的,比如规范要求在编码的字符串末尾添加回车换行符;

很多时候我们不需要遵循标准,所以看自己的场景选择工具类!
Base64也普遍用在如下场景:

  • 网络数据传输:URL,Post的body中;
  • 密钥存储,将二进制的密钥转换为base64字符串后发送给对方;
  • 数字证书存储和传输;
  • 图片、视频等二进制数据的传输;

算法过程

整个过程有两个关键的地方,1是分组转换过程,2是编码映射表,分组转换过程很标准,编解码时双向可逆,但是字符编码映射表要保持一致,否则会解码出错。

  • 1.将输入的字符转换为以字符为单位的char数组;
  • 2.将char数组分组,每3个字符为一组,也就是24位为一组,这个很重要;
  • 3.将每个字符转换对应的字符编码,将字符编码转换为二进制码;
  • 4.这样每组就获得了3个8位二进制码,也就是24位;
  • 5.将这24位以6位为一个单元,转换为4个单元,也就是3个字符的二进制码被分为了4个单元;
  • 6.将获取的4个单元中,每个单元高位添加2个0,注意是高位补位,这是成为了4个8位二进制码;
  • 7.将4个8位二进制码转换为4各十进制码;
  • 8.按照字符映射表转换对应的字符;

具体例子

下表示Base64的映射表(规范定义,非假设):

十进制码字符十进制码字符十进制码字符十进制码字符
0A17R34i51z
1B18S35j520
2C19T36k531
3D20U37m542
4E21V38l553
5F22W39n564
6G23X40o575
7H24Y41p586
8I25Z42q587
9J26a43r608
10K27b44s619
11M28c45t62+
12L29d46u63/
13N30e47v(pad)=
14O31f48wurl base64+换为-
15P32g49xurl base64/换为_
16Q33h50yurl base64= 处理有不同

输入:abc

  • abc转换为字符[a,b,c];
  • [a,b,c]转换为ASCII码[97,98,99];
  • [97,98,99]转换为二进制码[01100001,01100010,01100011];
  • [01100001,01100010,01100011]转换为4-6进制分组:[011000,010110,001001,100011];
  • 对新的4-6进制分组补位:[00110000,00001110,00100010,00101100];
  • 转换为10进制:[24,22,9,35];
  • 跟进映射表找到4个十进制数对应的字符[Y,W,J,j];
  • Base64的结果:YWJj

Java示例代码

下面的代码简单演示了Base64的过程,代码仅仅是根据Base64的算法描述做的伪代码,不可用于实际生产。

package com.sailen.research.encry;

import java.util.HashMap;
import java.util.Map;

/**
 * @author : qiesai
 * @createTime : 2017/3/22
 */
public class Base64Test {

    private static Map<Integer, String> table = new HashMap<Integer, String>();
    static {

        char begin = 'A';
        char begin2 = 'a';
        int idx = 0;
        for (int i = 0; i < 26; i++, idx++) {
            char v = (char) (begin + i);
            table.put(idx, v + "");

            char v2 = (char) (begin2 + i);
            table.put(idx + 26, v2 + "");
        }
        idx = 52;
        for (int i = 0; i < 10; i++, idx++) {
            table.put(idx, "" + i);
        }
        idx++;
        table.put(idx, "+");
        idx++;
        table.put(idx, "/");
        // 特殊处理,不合规的:
        idx++;
        table.put(idx, "=");

        log(table.toString());
    }

    public static void main(String[] args) {
        // 对于非ASCII的编码是有问题的
        // String input = "Java加密与解密的艺术";
        String input = "A";

        String binaryStr = getBinaryString(input);
        String[][] group46 = convert2Group46(binaryStr);

        String rs = getReslut(group46);

        log("%s -> %s", input, rs);
    }

    /**
     * 将给定的字符串转换为二进制字符串:<br>
     * 1.将字符串转换为字符数组;<br>
     * 2.将字符数组转换为ASCII码,将ASCII码转换为二进制编码;<br>
     * 3.如果二进制码不足8位要补0<br>
     * 
     * @param input
     * @return
     */
    public static String getBinaryString(String input) {
        // 1. 将输入字符转换为字符数组
        char[] inputChars = input.toCharArray();
        StringBuilder binaryBuilder = new StringBuilder();
        for (char c : inputChars) {// 将字符转换为ascii编码,再转换为二进制码.
            Integer charASCII = Integer.valueOf(c);
            String charBinary = Integer.toBinaryString(charASCII);
            // 这里获得二进制码是7位,因为高位为0,所以添加个0,如果已经是8位不用添加0
            int x = 8 - (charBinary.length() % 8);
            for (int i = 0; i < x; i++) {
                binaryBuilder.append('0');
            }
            binaryBuilder.append(charBinary);
            log("%s -> %s -> %s", c, charASCII, charBinary);
        }

        // 不足6位,低位补0
        int m = 6 - (binaryBuilder.length() % 6);
        for (int i = 0; i < m; i++) {
            binaryBuilder.append('0');
        }

        String binaryStr = binaryBuilder.toString();
        log("binary string : %s ", binaryStr);
        return binaryStr;
    }

    /**
     * 将给定的字符串转换为4-6的分组:<br>
     * 1.二进制字符串长度一定是24的倍数<br>
     * 2.计算可以分多个3-8分组;<br>
     * 3.将3-8分组转换为4-6分组,并在高位补2个0;
     *
     * @param binaryStr
     * @return
     */
    public static String[][] convert2Group46(String binaryStr) {
        // 2.进行分组转换3-8的分组,转换为4-6分组
        int len = binaryStr.length();

        if (len % 6 != 0) {
            throw new RuntimeException("Error , Binary String Length Is Not Multiples 8, len : " + len);
        }
        // 计算能以3-8的分组,能分多少组,少于24位;
        int groupNum = len / (3 * 8);
        if (len % 24 != 0) {
            groupNum += 1;
        }
        String[][] group46 = new String[groupNum][4];

        int idx = 0;
        for (int gn = 0; gn < groupNum; gn++) {// 对每个3-8的分组进行转换

            for (int unitIdx = 0; unitIdx < 4; unitIdx++, idx += 6) {// 转换为4-6的分组,并对高位补2个0
                int end = idx + 6;
                group46[gn][unitIdx] = "00";
                if (end <= len) {
                    group46[gn][unitIdx] += binaryStr.substring(idx, idx + 6);// 每次读取6位
                } else {
                    group46[gn][unitIdx] += "01000001";// 每次读取6位
                }

                log("4-6Group[%s][%s] -> %s", gn, unitIdx, group46[gn][unitIdx]);
            }
        }

        log("Get 46Group Num : %s", group46.length);
        return group46;
    }

    public static String getReslut(String[][] group46) {
        int groupNum = group46.length;
        int[][] decimalGroup = new int[groupNum][4];

        // 将4-6分组补位后的二进制转换为10进制编码,并在映射表中寻找对应的字符
        StringBuilder rs = new StringBuilder();
        for (int gn = 0; gn < groupNum; gn++) {
            for (int unitIdx = 0; unitIdx < 4; unitIdx++) {// 转换为十进制编码
                decimalGroup[gn][unitIdx] = Integer.parseInt(group46[gn][unitIdx], 2);
                String val = table.get(decimalGroup[gn][unitIdx]);
                rs.append(val);
                log("4-6Group[%s][%s] -> %s -> %s", gn, unitIdx, group46[gn][unitIdx], val);
            }
        }
        log(rs.toString());
        return rs.toString();
    }

    private static void log(Object msg) {
        System.out.println(msg);
    }

    private static void log(String tmp, Object... params) {
        System.out.println(String.format(tmp, params));
    }
}

Java的Base64工具

下表列举了在实际项目中可以使用的Base64工具:

  • 一般项目建议使用Codec包,效率不错,而且规范;
  • 一般大型项目,如果已经绑定死某个框架,比如标配Spring,可以使用Spring的util;
  • 大型项目,自己建立Base64Util要注意效率和Url Base64的特点,可以从其他框架copy 一份(注意版权);
  • 要注意自己的项目是否需要严格遵循RFC 2045标准,根据这个选择工具类。
工具位置特点适用性
java.util.Base64JDK 1.8 加入到util包中适用JDK 1.8后可以使用
Commons CodecJava加解密领域很出名的包,可以选择是否使用RFC-2045的标准规范进行编解码很多框架在用,支持的算法很多,Base32等都支持
Spring等框架自己的JDK 1.8前Java不支持Base64,为了不和其他框架绑定,自己推出不建议用这些框架的,vendor lock-in
自己定义自己书写底层框架,尤其是大项目,即不绑定死JDK,也不绑定死框架要注意多测试,考虑Url Base64的特点

总结

  • Base64的产生是解决邮件传输的乱码问题的,现在使用场景大多也是为了防止乱码;
  • Base64不算是一种加密(除非自己打乱映射表),算法不复杂;
  • Base64编码后长度比原来数据长4/3,毕竟一个3个字符为一组转换为4个字符了;
  • Base64最后最多2个“=”,可以没有,但是不可能多余2个。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值