增强安全意识:Base64

内容简介:先简单介绍Base64,然后对其算法进行分析。

前言

Base64 是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64 就是一种基于64个可打印字符来表示二进制数据的方法。一般 Base64 编码后的数据具有不可读性,需要解码后才能阅读原数据。但这种编解码算法是公开的(部分公司会实现自己的变种算法),也相对简单,所以 Base64 严格意义上不能用于加解密工作,即它的安全性不高。但这种编码方法在传输数据时非常有用,可以有效的防止被人直接看到明文。

Base64的特殊场景

前言中提到,Base64 是基于64个可打印字符来进行编码的,但现实中存在很多使用场景,有的场景中标准64字符不一定全部适用。本文主要介绍标准、URL和MIME三种场景。

标准场景

标准模式中,64字符表如下图所示(来自Wikipedia):

640?wx_fmt=png可以看到64个字符由:A~Z、a~z、0~9以及 + 、 / 组成。

URL场景

标准的 Base64 并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为ANSI SQL中已将“%”号用作通配符。

MIME场景

MIME介绍

在MIME场景中,Base64编码后的数据每行不超过76个字符,还会在行末添加“\r”和“\n”(CR/LF)。

Base64的算法规则

简单来说,Base64 的规则就是把3个8Bit字节转化为4个6Bit字节(3 * 8 = 4 * 6)。那6个Bit怎么算成一个字节呢,就需要高位补两个0,这样就凑成了8Bit。然后,新构成的8Bit二进制数就可以在表中找到对应的字符。如果待处理数据长度(字节数)不是3的倍数,则会有1个字节或者2个字节的剩余,这就导致无法直接转成4个字节,所以需要补充“=”号(最多两个“=”号)。

640?wx_fmt=png原文“Man”,找到每个字符对应的二进制,然后每6个Bit进行处理,高位补00,然后在表中找到新字符。可以看出,编码后的字符串长度约是原字符串的4/3。

640?wx_fmt=png当原串字节长度不是3的倍数时,编码后需要补充“=”号来凑够4个字节。这里看到,最后的“a”只剩下了“0001”不够6Bit,所以在地位补充了两个0。

640?wx_fmt=png这里也是,不够6Bit就在地位补0。这里说明下,为什么最多补充两个“=”号,因为一个8Bit会构成两个新字符,也就是说,4个新字符中最多缺少2个字符,所以最多需要补充2个“=”号。

以上是 Base64 的编码规则,至于解码,则是对应的逆过程。主要是注意对“=”号的处理。

640?wx_fmt=png

Base64的实现

在Android中可以使用 java.util.Base64android.util.Base64两个类,它们的实现类似,这里只分析 java.util.Base64

// 标准模式的编解码	
public static Encoder getEncoder()	
public static Decoder getDecoder()	
// URL模式的编解码	
public static Encoder getUrlEncoder()	
public static Decoder getUrlDecoder()	
// MIME模式的编解码	
public static Encoder getMimeEncoder()	
public static Decoder getMimeDecoder()

编码过程中的核心方法是: privateintencode0(byte[]src,intoff,intend,byte[]dst),分析如下:

private int encode0(byte[] src, int off, int end, byte[] dst) {	
            // 判断是否是URL模式	
            char[] base64 = isURL ? toBase64URL : toBase64;	
            int sp = off;	
            int slen = (end - off) / 3 * 3; // 3的倍数长度	
            int sl = off + slen; // 3的倍数长度位置	
            if (linemax > 0 && slen  > linemax / 4 * 3)	
                slen = linemax / 4 * 3; // MIME模式特殊处理	
            int dp = 0;	
            while (sp < sl) {	
                // 最后一段可能不够3的倍数	
                int sl0 = Math.min(sp + slen, sl);	
                // 处理完3的倍数长度的字节	
                for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {	
                    // 取出三个字节	
                    // 第一个字节左移16位,整个就变成了24个位	
                    // 然后依次加入连续的2个字节	
                    int bits = (src[sp0++] & 0xff) << 16 |	
                               (src[sp0++] & 0xff) <<  8 |	
                               (src[sp0++] & 0xff);	
                    // 每6位处理一次,0x3f=00111111,即高位补00	
                    dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];	
                    dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];	
                    dst[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];	
                    dst[dp0++] = (byte)base64[bits & 0x3f];	
                }	
                int dlen = (sl0 - sp) / 3 * 4; // 长度变成原来的4/3	
                dp += dlen;	
                sp = sl0; // 原数据中新的起始坐标	
                // MIME模式,每行最长76字符,行末需要增加 \r\n	
                if (dlen == linemax && sp < end) {	
                    for (byte b : newline){	
                        dst[dp++] = b;	
                    }	
                }	
            }	
            // 原数据长度不是3的倍数,有剩余,需要补充=号	
            // 最多补两个=号	
            if (sp < end) {               // 1 or 2 leftover bytes	
                int b0 = src[sp++] & 0xff;	
                dst[dp++] = (byte)base64[b0 >> 2];	
                if (sp == end) {	
                    dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];	
                    if (doPadding) {	
                        dst[dp++] = '=';	
                        dst[dp++] = '=';	
                    }	
                } else {	
                    int b1 = src[sp++] & 0xff;	
                    dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];	
                    dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];	
                    if (doPadding) {	
                        dst[dp++] = '=';	
                    }	
                }	
            }	
            return dp;	
        }

解码过程中的核心方法是: privateintdecode0(byte[]src,intsp,intsl,byte[]dst),分析如下:

private int decode0(byte[] src, int sp, int sl, byte[] dst) {	
            int[] base64 = isURL ? fromBase64URL : fromBase64;	
            int dp = 0;	
            int bits = 0;	
            int shiftto = 18;       // pos of first byte of 4-byte atom	
            while (sp < sl) {	
                int b = src[sp++] & 0xff;	
                if ((b = base64[b]) < 0) {	
                    if (b == -2) {         // padding byte '='	
                        // =     shiftto==18 unnecessary padding	
                        // x=    shiftto==12 a dangling single x	
                        // x     to be handled together with non-padding case	
                        // xx=   shiftto==6&&sp==sl missing last =	
                        // xx=y  shiftto==6 last is not =	
                        if (shiftto == 6 && (sp == sl || src[sp++] != '=') ||	
                            shiftto == 18) {	
                            throw new IllegalArgumentException(	
                                "Input byte array has wrong 4-byte ending unit");	
                        }	
                        break;	
                    }	
                    if (isMIME)    // skip if for rfc2045	
                        continue;	
                    else	
                        throw new IllegalArgumentException(	
                            "Illegal base64 character " +	
                            Integer.toString(src[sp - 1], 16));	
                }	
                bits |= (b << shiftto); // 通过或操作拼接字节	
                shiftto -= 6;	
                if (shiftto < 0) { // 这里再通过左移操作完成最后的计算	
                    dst[dp++] = (byte)(bits >> 16);	
                    dst[dp++] = (byte)(bits >>  8);	
                    dst[dp++] = (byte)(bits);	
                    shiftto = 18;	
                    bits = 0;	
                }	
            }	
            // reached end of byte array or hit padding '=' characters.	
            if (shiftto == 6) { // 处理两个=号	
                dst[dp++] = (byte)(bits >> 16);	
            } else if (shiftto == 0) { // 处理一个=号	
                dst[dp++] = (byte)(bits >> 16);	
                dst[dp++] = (byte)(bits >>  8);	
            } else if (shiftto == 12) {	
                // dangling single "x", incorrectly encoded.	
                throw new IllegalArgumentException(	
                    "Last unit does not have enough valid bits");	
            }	
            // anything left is invalid, if is not MIME.	
            // if MIME, ignore all non-base64 character	
            while (sp < sl) {	
                if (isMIME && base64[src[sp++]] < 0)	
                    continue;	
                throw new IllegalArgumentException(	
                    "Input byte array has incorrect ending byte at " + sp);	
            }	
            return dp;	
        }

要想很好地理解上面的两个方法,可以使用上面“算法规则”那一小节的例子,通过debug来一步一步分析。android.util.Base64中,主要注意一下初始化。

    /**	
     * Default values for encoder/decoder flags.	
     * 默认模式,不够长度会补充=号,每行最长76字符,自动换行	
     */	
    public static final int DEFAULT = 0;	
    /**	
     * Encoder flag bit to omit the padding '=' characters at the end	
     * of the output (if any).	
     * 不补充=号	
     */	
    public static final int NO_PADDING = 1;	
    /**	
     * Encoder flag bit to omit all line terminators (i.e., the output	
     * will be on one long line).	
     * 不自动换行	
     */	
    public static final int NO_WRAP = 2;	
    /**	
     * Encoder flag bit to indicate lines should be terminated with a	
     * CRLF pair instead of just an LF.  Has no effect if {@code	
     * NO_WRAP} is specified as well.	
     * 每行行末添加\r\n	
     */	
    public static final int CRLF = 4;	
    /**	
     * Encoder/decoder flag bit to indicate using the "URL and	
     * filename safe" variant of Base64 (see RFC 3548 section 4) where	
     * {@code -} and {@code _} are used in place of {@code +} and	
     * {@code /}.	
     * URL安全模式	
     */	
    public static final int URL_SAFE = 8;	
    /**	
     * Flag to pass to {@link Base64OutputStream} to indicate that it	
     * should not close the output stream it is wrapping when it	
     * itself is closed.	
     */	
    public static final int NO_CLOSE = 16;

Base64的示例请移步:https://github.com/zjxstar/AndroidSamples/tree/master/SecuritySample

参考资料

  1. 百度百科;

  2. Wikipedia;

--END--

识别二维码,关注我们

640?wx_fmt=png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值