Base64算法

Base64算法概述

Base64算法最先應用於解決電子郵件傳輸的問題。早期,因爲「歷史問題」,電子郵件只容許ASCII碼字符,若是郵件中包含非ASCII碼字符,當它經過有「歷史問題」的網關時,這個網關會對該字符的二進制位進行調整,即將其8位二進制碼的最高位置0,這樣用戶收到的郵件就會是一封亂碼。爲了解決這個問題,產生了Base64算法。html

Base64主要用於將不可打印的字符轉換成可打印字符,或者簡單的說將二進制數據編碼成ASCII字符(注:可打印的)java

將二進制數據編碼成ASCII字符主要的目的是能在純文本內容中插入二進制數據,常見的應用場景包括:web

1. 電子郵件算法

這個可參考阮一峯的《MIME筆記》segmentfault

2. 微軟的MHT格式app

這是模仿郵件格式將多種資源打包在一個文件中的格式,全部二進制資源都採用 Base64 編碼。編碼

3. XML文件加密

這是一個純文本文件,若是要基於 XML 格式設計能夠保存圖片或其它附件的數據格式,那就須要將這些二進制數據轉碼成 ASCII 字符。url

4. DATA URLspa

最近流行起來的 Data URL,要在URL中使用二進制數據,固然也只能進行 ASCII 編碼。

固然除了 Base64 以外,還有其它一些編碼方式能夠將二進制數據編碼成 ASCII 字符,好比十六進制編碼,除此以外還有 Quoted-printable 等。甚至 URL 中使用 %XX 來對非 ASCII 字符進行編碼的方式也能夠算在內。

固然通常非特定環境下,選用十六進制編碼和 Base64 編碼的狀況比較多,主要是由於這兩種編碼易用,並且轉換後的數據量相對較小。

十六進制編碼是將 1 個字節編碼成 2 個十六進制字符,好比 0x10110110 編碼成 B6,轉換後數據量會增大 1 倍

Base64 編碼是將 3 個字節共 24 位數據,以每 6 位一個 Base64 字符 [0-9a-zA-Z+/] 表示,24 位數據共須要 4 個 Base64 字符表示,編碼後數據增加約 1/3。爲何是「約」?由於若是原數據字節數不是 3 的倍數,須要補位,這樣轉換出來的數據量就會比原來的 4/3 略多一點。

從上面的數據增加比來看,Base64編碼 比十六進制編碼更節省磁盤容量,因此通常較大的數據須要進行 ASCII 編碼多采用 Base64;而較小的數據,則使用易於人工識別十六進制(用紙筆就能解碼出來)。

具體參考:爲何有的代碼要用 base64 進行編碼?

Base64算法基本原理

Base64算法的轉換方式相似於古典加密算法裏的單表置換算法 。RFC 2045中給出了Base64的字符映射表,以下圖所示。

這張字符映射表中,Value是十進制編碼,Encoding是字符,共映射了64個字符,這也是Base64算法命名的由來。映射表的最後一個字符「=」是用來補位的。

Base64算法的編碼和解碼操做可用做加密解密,可是Base64的字符映射表是公開的,所以並不能叫作加密算法。

Base64算法主要是將給定的字符以字符編碼(如ASCII、UTF-8等)對應的十進制數爲基準,作編碼操做:

1. 將給定的字符串以字符爲單位,轉換爲對應的字符編碼。

2. 將得到的字符編碼轉換爲二進制串。

3. 將得到的二進制串作分組轉換操做,每3個8位的二進制串爲一組,將這樣的一組再轉換爲4個6位二進制串,不足6位時低位補0。

4. 對每組4個6位二進制串補位,即向6位二進制串的高位補兩個0,生成4個8位二進制串。

5. 將得到的4-8二進制碼轉換爲十進制碼。

6. 將得到的十進制碼用Base64字符映射表中對應的字符替換。

通過Base64編碼後的數據會比原始數據略長,爲原來的4/3倍,編碼後的字符數是4的倍數。

編碼後的字符串最多有2個補位的「=」,由於原始數據的二進制串的分組是以3個8位爲一組的,餘數 = 原始數據字節數 mod 3,餘數只能爲0、一、2。若是餘數爲0,3個8位轉換爲4個6位,高位補0以後是4個8位,則不須要補位符;若是餘數爲1,1個8位只能轉換爲2個6位,高位補0以後是2個8位,爲了讓編碼以後的字符數是4的倍數,要補兩個補位符;同理,若是餘數爲2,要補一個補位符。

ASCII碼進行Base64編碼的例子以下圖,字符「A」編碼以後的字符串爲「QQ==」。

非ASCII碼如GBK、UTF-8等編碼,一個字符包含多個字節,如UTF-8用3個字節表示一個漢字,GBK用2個字節表示一個漢字。以字符串「密」爲例,對應的UTF-8編碼是-2七、-8一、-122,用Base64編碼以下圖,編碼後的字符串爲「5a+G」。

具體參考:Java加密與解密 - Base64算法 (這個好)     java加密解密研究三、Base64算法

Base64算法的實現

Java API中沒有Base64的實現,實際上Sun也有Base64算法的實現,可是沒有公佈。Bouncy Castle提供了通常Base64算法的實現,Commons Codec提供了基於RFC 2045相關定義的Base64算法實現。

Bouncy Castle遵循的是通常Base64算法,就是根據字符映射表作了編碼轉換。Commons Codec中既支持RFC 2045定義的Base64算法,也支持通常的Base64算法。這兩種的差別是RFC 2045定義的算法要求在編碼後的字符串中換行和末尾添加回車換行符。

注:RFC 2045中規定,在電子郵件中,每行爲76個字符,每行末需添加一個回車換行符("\r\n"),無論每行是否夠76個字符,都要添加一個回車換行符。不過在實際應用中,根據實際須要,這一要求每每被忽略。

kSOAP中也提供Base64的實現,具體以下:

package org.kobjects.base64;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Base64
{
  static final char[] charTab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();

  public static String encode(byte[] paramArrayOfByte)
  {
    return encode(paramArrayOfByte, 0, paramArrayOfByte.length, null).toString();
  }

  public static StringBuffer encode(byte[] paramArrayOfByte, int paramInt1, int paramInt2, StringBuffer paramStringBuffer)
  {
    if (paramStringBuffer == null)
      paramStringBuffer = new StringBuffer(paramArrayOfByte.length * 3 / 2);
    int i = paramInt2 - 3;
    int j = paramInt1;
    int k = 0;
    int l;
    while (j <= i)
    {
      l = (paramArrayOfByte[j] & 0xFF) << 16 | (paramArrayOfByte[(j + 1)] & 0xFF) << 8 | paramArrayOfByte[(j + 2)] & 0xFF;
      paramStringBuffer.append(charTab[(l >> 18 & 0x3F)]);
      paramStringBuffer.append(charTab[(l >> 12 & 0x3F)]);
      paramStringBuffer.append(charTab[(l >> 6 & 0x3F)]);
      paramStringBuffer.append(charTab[(l & 0x3F)]);
      j += 3;
      if (k++ < 14)
        continue;
      k = 0;
      paramStringBuffer.append("\r\n");
    }
    if (j == paramInt1 + paramInt2 - 2)
    {
      l = (paramArrayOfByte[j] & 0xFF) << 16 | (paramArrayOfByte[(j + 1)] & 0xFF) << 8;
      paramStringBuffer.append(charTab[(l >> 18 & 0x3F)]);
      paramStringBuffer.append(charTab[(l >> 12 & 0x3F)]);
      paramStringBuffer.append(charTab[(l >> 6 & 0x3F)]);
      paramStringBuffer.append("=");
    }
    else if (j == paramInt1 + paramInt2 - 1)
    {
      l = (paramArrayOfByte[j] & 0xFF) << 16;
      paramStringBuffer.append(charTab[(l >> 18 & 0x3F)]);
      paramStringBuffer.append(charTab[(l >> 12 & 0x3F)]);
      paramStringBuffer.append("==");
    }
    return paramStringBuffer;
  }

  static int decode(char paramChar)
  {
    if ((paramChar >= 'A') && (paramChar <= 'Z'))
      return paramChar - 'A';
    if ((paramChar >= 'a') && (paramChar <= 'z'))
      return paramChar - 'a' + 26;
    if ((paramChar >= '0') && (paramChar <= '9'))
      return paramChar - '0' + 26 + 26;
    switch (paramChar)
    {
    case '+':
      return 62;
    case '/':
      return 63;
    case '=':
      return 0;
    }
    throw new RuntimeException("unexpected code: " + paramChar);
  }

  public static byte[] decode(String paramString)
  {
    ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
    try
    {
      decode(paramString, localByteArrayOutputStream);
    }
    catch (IOException localIOException)
    {
      throw new RuntimeException();
    }
    return localByteArrayOutputStream.toByteArray();
  }

  public static void decode(String paramString, OutputStream paramOutputStream)
    throws IOException
  {
    int i = 0;
    int j = paramString.length();
    while (true)
    {
      if ((i < j) && (paramString.charAt(i) <= ' '))
        ++i;
      if (i == j)
        return;
      int k = (decode(paramString.charAt(i)) << 18) + (decode(paramString.charAt(i + 1)) << 12) + (decode(paramString.charAt(i + 2)) << 6) + decode(paramString.charAt(i + 3));
      paramOutputStream.write(k >> 16 & 0xFF);
      if (paramString.charAt(i + 2) == '=')
        return;
      paramOutputStream.write(k >> 8 & 0xFF);
      if (paramString.charAt(i + 3) == '=')
        return;
      paramOutputStream.write(k & 0xFF);
      i += 4;
    }
  }
}
相關文章
相關標籤/搜索