關於base64編碼的原理及實現

轉  http://www.cnblogs.com/hongru/archive/2012/01/14/2321397.html

我們的圖片大部分都是可以轉換成base64編碼的data:image。 這個在將canvas保存爲img的時候尤其有用。雖然除ie外,大部分現代瀏覽器都已經支持原生的基於base64的encode和decode,例如btoa和atob。(將canvas畫布保存成img並強制改變mimetype進行下載,會在下一篇記錄)

但是處於好奇心,還是驅使我去了解下base64編碼的原理。以便也在不支持原生base64編碼的ie下可以得以實現。

【Base64】
-base64的編碼都是按字符串長度,
以每3個8bit的字符爲一組
-然後針對每組,首先獲取每個字符的ASCII編碼,
-然後將ASCII編碼轉換成8bit的二進制,得到一組3*8=24bit的字節
-然後再將這24bit劃分爲4個6bit的字節,並在每個6bit的字節前面都填兩個高位0,得到4個8bit的字節
-然後將這4個8bit的字節轉換成10進制,對照Base64編碼表 (下表),得到對應編碼後的字符。

(注:1. 要求被編碼字符是8bit的,所以須在ASCII編碼範圍內,\u0000-\u00ff,中文就不行。
   2.
如果被編碼字符長度不是3的倍數的時候,則都用0代替,對應的輸出字符爲=

Base64 編碼表
Value Char   Value Char   Value Char   Value Char
0 A 16 Q 32 g 48 w
1 B 17 R 33 h 49 x
2 C 18 S 34 i 50 y
3 D 19 T 35 j 51 z
4 E 20 U 36 k 52 0
5 F 21 V 37 l 53 1
6 G 22 W 38 m 54 2
7 H 23 X 39 n 55 3
8 I 24 Y 40 o 56 4
9 J 25 Z 41 p 57 5
10 K 26 a 42 q 58 6
11 L 27 b 43 r 59 7
12 M 28 c 44 s 60 8
13 N 29 d 45 t 61 9
14 O 30 e 46 u 62 +
15 P 31 f 47 v 63 /

比如舉下面2個例子:
a) 字符長度爲能被3整除時:比如“Tom” :

複製代碼
            T           o           m
ASCII:      84          111         109
8bit字節:   01010100    01101111    01101101
6bit字節:     010101      000110      111101      101101
十進制:     21          6           61          45
對應編碼:   V           G           9           t  
複製代碼

所以,btoa('Tom') = VG9t

b) 字符串長度不能被3整除時,比如“Lucy”:  (如果被編碼字符長度不是3的倍數的時候,則都用0代替,對應的輸出字符爲=)

複製代碼
            L           u           c           y
ASCII:      76          117         99          121
8bit字節:   01001100    01110101    01100011    01111001      00000000    00000000  (補足3的倍數  x*3*8=y*4*6)
6bit字節:     010011      000111      010101      100011      011110  010000  000000  000000
十進制:     19          7           21          35             30      16      (異常) (異常)      
對應編碼:   T           H           V           j               e       Q       =       =
複製代碼

由於Lucy只有4個字母,所以按3個一組的話,第二組還有兩個空位,所以需要用0來補齊。這裏就需要注意,因爲是需要補齊而出現的0,所以轉化成十進制的時候就不能按常規用base64編碼表來對應,所以不是a, 可以理解成爲一種特殊的“異常”,編碼應該對應“=”

有了上面的理論,那我們實現一個base64編碼就容易了。


轉  http://chhj-292.iteye.com/blog/379700


package com.******.framework.util.encrpytion;

/**
 * 
 * @author 
 * @version 
 */
public final class Base64 implements IEncrypt {
	/**
	 * 標準base64編碼表
	 */
	private final static String	CODEC	= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	private final static Base64	base64	= new Base64();

	private Base64() {

	}

	public static Base64 getInstance() {
		return base64;
	}

	/* (non-Javadoc)
	 * @see com.pantosoft.framework.util.encrpytion.IEncryptencrypt(java.lang.String)
	 */
	public String encrypt(String s) throws Exception {
		return encode(s.getBytes());
	}

	/* (non-Javadoc)
	 * @see com.pantosoft.framework.util.encrpytion.IEncrypt#decrypt(java.lang.String)
	 */
	public String decrypt(String s) throws Exception {
		return new String(this.decode(s));
	}

	private String encode(byte[] bytes) {
		StringBuilder s = new StringBuilder();
		int i = 0;
		byte pos;
		/*
		 一次處理3個字節,3*8 == 4*6 的運算規則來進行重新編碼
		 
		 該方法中的*&63,*&15,*&3操作的意義如下:
		 計算機中byte數據類型存儲的64形式如下:11111111
		 計算機中byte數據類型存儲的15形式如下:1111,即 2^3 + 2^2 + 2^1 + 2^0 = 15
		 “&”、“與”,運算這裏主要進行高位清零操作。
		 */
		for (i = 0; i < (bytes.length - (bytes.length % 3)); i += 3) {

			//第一個字節,根據源字節的第一個字節處理。
			//規則:源第一字節右移兩位,去掉低2位,高2位補零。
			//既:00 + 高6位
			pos = (byte) ((bytes[i] >> 2) & 63);
			s.append(CODEC.charAt(pos));

			//第二個字節,根據源字節的第一個字節和第二個字節聯合處理。
			//規則如下,第一個字節高6位去掉左移四位,第二個字節右移四位
			//即:源第一字節低2位 + 源第2字節高4位
			pos = (byte) (((bytes[i] & 3) << 4) + ((bytes[i + 1] >> 4) & 15));
			s.append(CODEC.charAt(pos));

			//第三個字節,根據源字節的第二個字節和第三個字節聯合處理,
			//規則第二個字節去掉高4位並左移兩位(得高6位),第三個字節右移6位並去掉高6位(得低2位),相加即可
			pos = (byte) (((bytes[i + 1] & 15) << 2) + ((bytes[i + 2] >> 6) & 3));
			s.append(CODEC.charAt(pos));

			//第四個字節,規則,源第三字節去掉高2位即可
			pos = (byte) (((bytes[i + 2]) & 63));
			s.append(CODEC.charAt(pos));

			//根據base64的編碼規則,每76個字符需要一個換行
			//76*3/4 = 57
			if (((i + 2) % 56) == 0) {
				s.append("\r\n");
			}
		}

		if (bytes.length % 3 != 0) {

			if (bytes.length % 3 == 2) {

				pos = (byte) ((bytes[i] >> 2) & 63);
				s.append(CODEC.charAt(pos));

				pos = (byte) (((bytes[i] & 3) << 4) + ((bytes[i + 1] >> 4) & 15));
				s.append(CODEC.charAt(pos));

				pos = (byte) ((bytes[i + 1] & 15) << 2);
				s.append(CODEC.charAt(pos));

				s.append("=");

			} else if (bytes.length % 3 == 1) {
				//分出第一個二進制位的前6位,右移兩位,得到一個新8位
				pos = (byte) ((bytes[i] >> 2) & 63);
				s.append(CODEC.charAt(pos));
				//先清零比3高的高位,分出8位的後兩位,然後左移4位,得到一個新8位
				pos = (byte) ((bytes[i] & 3) << 4);
				s.append(CODEC.charAt(pos));

				s.append("==");
			}
		}
		return s.toString();
	}

	/**
	 * 
	 * @param s
	 * @return
	 * @throws Exception
	 */
	public byte[] decode(String s) throws Exception {
		StringBuffer buf = new StringBuffer(s);
		int i = 0;
		char c = ' ';
		char oc = ' ';
		while (i < buf.length()) {
			oc = c;
			c = buf.charAt(i);
			if (oc == '\r' && c == '\n') {
				buf.deleteCharAt(i);
				buf.deleteCharAt(i - 1);
				i -= 2;
			} else if (c == '\t') {
				buf.deleteCharAt(i);
				i--;
			} else if (c == ' ') {
				i--;
			}
			i++;
		}

		//base64編碼的字符長度必須爲4的倍數   
		if (buf.length() % 4 != 0) {
			throw new Exception("Base64 decoding invalid length");
		}

		//預設的字節數組的長度
		byte[] bytes = new byte[3 * (buf.length() / 4)];
		int index = 0;

		/**
		 * 每4個base64字符代表一個源字符編碼後的字符!
		 * 
		 * 然後每四個字符分別做循環,每個循環左移6位,作爲低6位,該低6位再補上下一個base64字符在base64碼錶中的序列。
		 * 因爲字符在碼錶中的序列小於等於64,即,小於等於2的6次方(6位)!
		 */
		for (i = 0; i < buf.length(); i += 4) {

			byte base64Index = 0;
			int nGroup = 0;

			for (int j = 0; j < 4; j++) {

				char theChar = buf.charAt(i + j);

				if (theChar == '=') {
					base64Index = 0;
				} else {
					base64Index = getBase64Index(theChar);
				}

				if (base64Index == -1) {
					throw new Exception("Base64 decoding bad character");
				}
				//每次都想高位移動6個位置後再加上新的字符所在base64編碼表中的位置。
				nGroup = (nGroup << 6) + base64Index;
			}

			//右移16位,取高8位
			bytes[index] = (byte) (255 & (nGroup >> 16));
			index++;

			//右移8位,取高16位,且與00..0011111111(32位windows系統)進行and操作,取該高16位的低8位。
			if ((255 & (nGroup >> 8)) == 0) {
				continue;
			}
			bytes[index] = (byte) (255 & (nGroup >> 8));
			index++;

			//直接與00..0011111111進行and操作,該32位數的低8位
			if ((255 & nGroup) == 0) {
				continue;
			}
			bytes[index] = (byte) (255 & (nGroup));
			index++;
		}

		byte[] newBytes = new byte[index];
		for (i = 0; i < index; i++) {
			newBytes[i] = bytes[i];
		}

		return newBytes;
	}

	/**
	 * 從編碼表中找出對應的字符序列
	 * @param c
	 * @return
	 */
	private byte getBase64Index(char c) {
		byte index = -1;
		for (byte i = 0, j = (byte) (CODEC.length() & 225); i < j; i++) {
			if (CODEC.charAt(i) == c) {
				index = i;
				break;
			}
		}
		return index;
	}

	static final char	digits[]	= { '0', '1', '2', '3', '4', '5', '6', '7',
			'8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
			'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
			'y', 'z'				};

	private static String toUnsignedString(int i, int j) {
		char ac[] = new char[32];
		int k = 32;
		int l = 1 << j;
		int i1 = l - 1;
		do {
			ac[--k] = digits[i & i1];
			i >>>= j;
		} while (i != 0);
		return new String(ac, k, 32 - k);
	}

	public static void main(String[] args) {

		String encodedString = null;
		String s = "	linux  和	windows下默認使用firefox下載文件的時侯,rar文件會自動被firefox下載後顯示爲一堆";

		byte[] bytes = s.getBytes();
		for (int i = 0; i < bytes.length; i++) {
			String s1 = toUnsignedString(bytes[i], 1);
			if (s1.length() >= 24)
				System.out.println(s1 + "," + s1.substring(24, s1.length()));
		}

		try {
			encodedString = Base64.getInstance().encrypt(s);
			System.out.println("加密:" + encodedString);
			System.out.println("解密:"
					+ Base64.getInstance().decrypt(encodedString));
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章