text: /**
   * md5.js
   * Copyright (c) 2011, Yoshinori Kohyama (http://algobit.jp/)
   * all rights reserved.
   */
  
  exports.digest = function (M) {
  	function F(x, y, z) { return (x & y) | (~x & z); }
  	function G(x, y, z) { return (x & z) | (y & ~z); }
  	function H(x, y, z) { return x ^ y ^ z;          }
  	function I(x, y, z) { return y ^ (x | ~z);       }
  
  	function to4bytes(n) {
  		return [n&0xff, (n>>>8)&0xff, (n>>>16)&0xff, (n>>>24)&0xff];
  	}
  
  	var originalLength = M.length; // for Step.2
  
  	// 3.1 Step 1. Append Padding Bits
  	M.push(0x80);
  	var l = (56 - M.length)&0x3f;
  	for (var i = 0; i < l; i++)
  		M.push(0);
  
  	// 3.2 Step 2. Append Length
  	to4bytes(8*originalLength).forEach(function (e) { M.push(e); });
  	[0, 0, 0, 0].forEach(function (e) { M.push(e); });
  
  	// 3.3 Step 3. Initialize MD Buffer
  	var A = [0x67452301];
  	var B = [0xefcdab89];
  	var C = [0x98badcfe];
  	var D = [0x10325476];
  	var AA, BB, CC, DD;
  
  	// 3.4 Step 4. Process Message in 16-Word Blocks
  	function rounds(a, b, c, d, k, s, t, f) {
  		a[0] += f(b[0], c[0], d[0]) + X[k] + t;
  		a[0] = ((a[0]<<s)|(a[0]>>>(32 - s)));
  		a[0] += b[0];
  	}
  	var X;
  	for (var i = 0; i < M.length; i += 64) {
  		X = [];
  		for (var j = 0; j < 64; j += 4) {
  			k = i + j;
  			X.push(M[k]|(M[k + 1]<<8)|(M[k + 2]<<16)|(M[k + 3]<<24));
  		}
  		AA = A[0];
  		BB = B[0];
  		CC = C[0];
  		DD = D[0];
  
  		// Round 1.
  		rounds(A, B, C, D,  0,  7, 0xd76aa478, F);
  		rounds(D, A, B, C,  1, 12, 0xe8c7b756, F);
  		rounds(C, D, A, B,  2, 17, 0x242070db, F);
  		rounds(B, C, D, A,  3, 22, 0xc1bdceee, F);
  		rounds(A, B, C, D,  4,  7, 0xf57c0faf, F);
  		rounds(D, A, B, C,  5, 12, 0x4787c62a, F);
  		rounds(C, D, A, B,  6, 17, 0xa8304613, F);
  		rounds(B, C, D, A,  7, 22, 0xfd469501, F);
  		rounds(A, B, C, D,  8,  7, 0x698098d8, F);
  		rounds(D, A, B, C,  9, 12, 0x8b44f7af, F);
  		rounds(C, D, A, B, 10, 17, 0xffff5bb1, F);
  		rounds(B, C, D, A, 11, 22, 0x895cd7be, F);
  		rounds(A, B, C, D, 12,  7, 0x6b901122, F);
  		rounds(D, A, B, C, 13, 12, 0xfd987193, F);
  		rounds(C, D, A, B, 14, 17, 0xa679438e, F);
  		rounds(B, C, D, A, 15, 22, 0x49b40821, F);
  
  		// Round 2.
  		rounds(A, B, C, D,  1,  5, 0xf61e2562, G);
  		rounds(D, A, B, C,  6,  9, 0xc040b340, G);
  		rounds(C, D, A, B, 11, 14, 0x265e5a51, G);
  		rounds(B, C, D, A,  0, 20, 0xe9b6c7aa, G);
  		rounds(A, B, C, D,  5,  5, 0xd62f105d, G);
  		rounds(D, A, B, C, 10,  9, 0x02441453, G);
  		rounds(C, D, A, B, 15, 14, 0xd8a1e681, G);
  		rounds(B, C, D, A,  4, 20, 0xe7d3fbc8, G);
  		rounds(A, B, C, D,  9,  5, 0x21e1cde6, G);
  		rounds(D, A, B, C, 14,  9, 0xc33707d6, G);
  		rounds(C, D, A, B,  3, 14, 0xf4d50d87, G);
  		rounds(B, C, D, A,  8, 20, 0x455a14ed, G);
  		rounds(A, B, C, D, 13,  5, 0xa9e3e905, G);
  		rounds(D, A, B, C,  2,  9, 0xfcefa3f8, G);
  		rounds(C, D, A, B,  7, 14, 0x676f02d9, G);
  		rounds(B, C, D, A, 12, 20, 0x8d2a4c8a, G);
  
  		// Round 3.
  		rounds(A, B, C, D,  5,  4, 0xfffa3942, H);
  		rounds(D, A, B, C,  8, 11, 0x8771f681, H);
  		rounds(C, D, A, B, 11, 16, 0x6d9d6122, H);
  		rounds(B, C, D, A, 14, 23, 0xfde5380c, H);
  		rounds(A, B, C, D,  1,  4, 0xa4beea44, H);
  		rounds(D, A, B, C,  4, 11, 0x4bdecfa9, H);
  		rounds(C, D, A, B,  7, 16, 0xf6bb4b60, H);
  		rounds(B, C, D, A, 10, 23, 0xbebfbc70, H);
  		rounds(A, B, C, D, 13,  4, 0x289b7ec6, H);
  		rounds(D, A, B, C,  0, 11, 0xeaa127fa, H);
  		rounds(C, D, A, B,  3, 16, 0xd4ef3085, H);
  		rounds(B, C, D, A,  6, 23, 0x04881d05, H);
  		rounds(A, B, C, D,  9,  4, 0xd9d4d039, H);
  		rounds(D, A, B, C, 12, 11, 0xe6db99e5, H);
  		rounds(C, D, A, B, 15, 16, 0x1fa27cf8, H);
  		rounds(B, C, D, A,  2, 23, 0xc4ac5665, H);
  
  		// Round 4.
  		rounds(A, B, C, D,  0,  6, 0xf4292244, I);
  		rounds(D, A, B, C,  7, 10, 0x432aff97, I);
  		rounds(C, D, A, B, 14, 15, 0xab9423a7, I);
  		rounds(B, C, D, A,  5, 21, 0xfc93a039, I);
  		rounds(A, B, C, D, 12,  6, 0x655b59c3, I);
  		rounds(D, A, B, C,  3, 10, 0x8f0ccc92, I);
  		rounds(C, D, A, B, 10, 15, 0xffeff47d, I);
  		rounds(B, C, D, A,  1, 21, 0x85845dd1, I);
  		rounds(A, B, C, D,  8,  6, 0x6fa87e4f, I);
  		rounds(D, A, B, C, 15, 10, 0xfe2ce6e0, I);
  		rounds(C, D, A, B,  6, 15, 0xa3014314, I);
  		rounds(B, C, D, A, 13, 21, 0x4e0811a1, I);
  		rounds(A, B, C, D,  4,  6, 0xf7537e82, I);
  		rounds(D, A, B, C, 11, 10, 0xbd3af235, I);
  		rounds(C, D, A, B,  2, 15, 0x2ad7d2bb, I);
  		rounds(B, C, D, A,  9, 21, 0xeb86d391, I);
  
  		A[0] += AA;
  		B[0] += BB;
  		C[0] += CC;
  		D[0] += DD;
  	}
  
  	var rval = [];
  	to4bytes(A[0]).forEach(function (e) { rval.push(e); });
  	to4bytes(B[0]).forEach(function (e) { rval.push(e); });
  	to4bytes(C[0]).forEach(function (e) { rval.push(e); });
  	to4bytes(D[0]).forEach(function (e) { rval.push(e); });
  	return rval;
  }
  
  exports.digest_s = function (s) {
  	var M = [];
  	for (var i = 0; i < s.length; i++)
  		M.push(s.charCodeAt(i));
  	var d = exports.digest(M);
  	var rstr = '';
  	d.forEach(function (e) {
  		var s = e.toString(16);
  		while (s.length < 2)
  			s = '0' + s;
  		rstr += s;
  	});
  	return rstr;
  }
  
  // to test
  /*
  function test(s) {
  	console.log('MD5 ("' + s + '") = ' + exports.digest_s(s));
  }
  test('');
  test('a');
  test('abc');
  test('message digest');
  test('abcdefghijklmnopqrstuvwxyz');
  test('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789');
  test('12345678901234567890123456789012345678901234567890123456789012345678901234567890');
  */
  