iOS 中 Base64 编解码分类实现

iOS 中 Base64 编解码分类实现

太阳火神的美丽人生 (http://blog.csdn.net/opengl_es)

本文遵循“署名-非商业用途-保持一致”创作公用协议

转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS、Android、Html5、Arduino、pcDuino否则,出自本博客的文章拒绝转载或再转载,谢谢合作。


再补充:由此可见,当一个人极力地想要说明自已,表述清楚自已的想法的时侯,最后会被自已搞得更加不清楚了,从而给别人看到的是,你很混乱。即然我们是生活在人所组成的社会中,那么更多的应该考虑人的思维,而不应该过份地关注事物本身的真伪。无论你说的是对的或是错的,但只要你能针对你的观众,按他(她)们的心理需要来说明白就可以了,更确切地说,不是把真象告诉他们,而是把他们心中所知道的和能接受的事情,再重复说给他们听一遍就可以了,就这么简单。因为你是生活和生存在人的世界中,而非真理的世界中,过份强调真理,只能是违背人的内心需求,即使是真理,也会变成谬论,更何况,真理本身是没办法真正表达清楚的。

从另一个角度去看,当你看到一个人在努力地表达一些事情的时侯,本来已经有点靠谱了,但越说越不靠谱了,这其实并不能说明他混乱,而是他进入一种我们无法进入的境界了。不是我们进入不了,而是我们没有专注而已。

专注该专注的,但从长久来看,就是失去本真初,妄心肆意,耐表真心,不可如来,如初生之时。

唉,别说了,再说下去,我也要入境了,哈哈哈,那可不好玩啊......

----------

补充:我还是转一下 base64 的维基解释和用途吧,免得被我个人的理解误导了,我的理解足够我理解和使用,但并不代表我表述的确实是我的想法,也或者说,并不代表我能很好地表述我的想法。

这同样是一个世界性的难题,一个人想到的本身就有偏差,说出来的往往与想到的偏差更大,所以这个世界上并没有真理存在,真正的真理是无法被表达出来的!(大概意思是这样子的,忘了是哪个名人说的至理明言了,以前觉得他好像是疯了,不过这几年来看,有些理解了,所以也明白了高数中的极限的概念,只能是趋近而已)

------

Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个位元为一个单元,对应某个可打印字符。三个字节有24个位元,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。它可用来作为电子邮件的传输编码。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。一些如uuencode的其他编码方法,和之后binhex的版本使用不同的64字符集来代表6个二进制数字,但是它们不叫Base64。

Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据。包括MIME的email,email via MIME,在XML中存储复杂数据.




我们知道,可识别的文本及各种结构,最终都能转换成二进制来表示,而二进制无非就是它们的内存存储结构的字节排布。

那么,在 iOS  中文本及相关结构有 NSString 等;二进制则使用 NSData 来管理和表示。

而 NSData 可以和这些文本及相关结构进行转换,所以我们只要能对 NSData 进行相应的 Base64 编码,那么就可以对其它能转换成 NSData 的文本及相关结构进行编码:

其实 Base64 编码出来的也是文本,但这种文本串有个特点,就是它使用的都是最基本的字符,不会因7位编码和8位编码等一系列问题,而在某些原始的网络设备中(比如某国外的一台路由器,有这个可能会路由到那里去)无法处理。

并且在一些如 XML 和 JSON 这样的数据承载结构中,只支持明文字符,如果用二进制,则会使其所基于的底层结构混乱,而破坏其自身的可用性。

可以这样来表示这个过程:

Others => 二进制 => Base64编码串

NSString => NSData => Base64

同样的道理,从 Base64 串解码出原始内容,也需要先将 Base64 串转换成 NSData 二进制内容,然后再进行各自的转换。

这样一来,我们就只需要完成 NSData 与 Base64 串的互转,这个问题,会让我们想起在 iOS 中使用分类来扩展 NSData 的功能是最好的办法,且无需额外的管理。

下面就贴一下曾经使用过的一套分类扩展,忘记是从哪里下载到的了,权当是自已的发个原创吧。不过这里的 C 编解码 base64 的方式和当年我学习 C 语言写网页(这个确实是可以的,而且作为 CGI 处理少量请求,效率很高,但写起来拼输出串很费事)时按 base64 原理 和 URL编解码原理写的方式不同,当时的硬件配置,算法精练精高效,而现在的查表方式更轻松易懂一些。


废话不多说,上代码:

Objective-C 类声明:

#import <Foundation/Foundation.h>

void *NewBase64Decode(const char *inputBuffer, size_t length, size_t *outputLength);
char *NewBase64Encode(const void *inputBuffer, size_t length, bool separateLines, size_t *outputLength);

@interface NSData (Base64)

+ (NSData *)dataFromBase64String:(NSString *)aString;
- (NSString *)base64EncodedString;

@end

Objective-C 类实现:

#import "NSData+Base64.h"

//
// Mapping from 6 bit pattern to ASCII character.
//
static unsigned char base64EncodeLookup[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

//
// Definition for "masked-out" areas of the base64DecodeLookup mapping
//
#define xx 65

//
// Mapping from ASCII character to 6 bit pattern.
//
static unsigned char base64DecodeLookup[256] =
{
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 62, xx, xx, xx, 63, 
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, xx, xx, xx, xx, xx, xx, 
    xx,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, xx, xx, xx, xx, xx, 
    xx, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 
};

//
// Fundamental sizes of the binary and base64 encode/decode units in bytes
//
#define BINARY_UNIT_SIZE 3
#define BASE64_UNIT_SIZE 4

//
// NewBase64Decode
//
// Decodes the base64 ASCII string in the inputBuffer to a newly malloced
// output buffer.
//
//  inputBuffer - the source ASCII string for the decode
//	length - the length of the string or -1 (to specify strlen should be used)
//	outputLength - if not-NULL, on output will contain the decoded length
//
// returns the decoded buffer. Must be free'd by caller. Length is given by
//	outputLength.
//
void *NewBase64Decode(const char *inputBuffer, size_t length, size_t *outputLength)
{
	if (length == -1)
	{
		length = strlen(inputBuffer);
	}
	
	size_t outputBufferSize = (length / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE;
	unsigned char *outputBuffer = (unsigned char *)malloc(outputBufferSize);
	
	size_t i = 0;
	size_t j = 0;
	while (i < length)
	{
		//
		// Accumulate 4 valid characters (ignore everything else)
		//
		unsigned char accumulated[BASE64_UNIT_SIZE];
		size_t accumulateIndex = 0;
		while (i < length)
		{
			unsigned char decode = base64DecodeLookup[inputBuffer[i++]];
			if (decode != xx)
			{
				accumulated[accumulateIndex] = decode;
				accumulateIndex++;
				
				if (accumulateIndex == BASE64_UNIT_SIZE)
				{
					break;
				}
			}
		}
		
		//
		// Store the 6 bits from each of the 4 characters as 3 bytes
		//
		outputBuffer[j] = (accumulated[0] << 2) | (accumulated[1] >> 4);
		outputBuffer[j + 1] = (accumulated[1] << 4) | (accumulated[2] >> 2);
		outputBuffer[j + 2] = (accumulated[2] << 6) | accumulated[3];
		j += accumulateIndex - 1;
	}
	
	if (outputLength)
	{
		*outputLength = j;
	}
	return outputBuffer;
}

//
// NewBase64Decode
//
// Encodes the arbitrary data in the inputBuffer as base64 into a newly malloced
// output buffer.
//
//  inputBuffer - the source data for the encode
//	length - the length of the input in bytes
//  separateLines - if zero, no CR/LF characters will be added. Otherwise
//		a CR/LF pair will be added every 64 encoded chars.
//	outputLength - if not-NULL, on output will contain the encoded length
//		(not including terminating 0 char)
//
// returns the encoded buffer. Must be free'd by caller. Length is given by
//	outputLength.
//
char *NewBase64Encode(
	const void *buffer,
	size_t length,
	bool separateLines,
	size_t *outputLength)
{
	const unsigned char *inputBuffer = (const unsigned char *)buffer;
	
	#define MAX_NUM_PADDING_CHARS 2
	#define OUTPUT_LINE_LENGTH 64
	#define INPUT_LINE_LENGTH ((OUTPUT_LINE_LENGTH / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE)
	#define CR_LF_SIZE 2
	
	//
	// Byte accurate calculation of final buffer size
	//
	size_t outputBufferSize =
			((length / BINARY_UNIT_SIZE)
				+ ((length % BINARY_UNIT_SIZE) ? 1 : 0))
					* BASE64_UNIT_SIZE;
	if (separateLines)
	{
		outputBufferSize +=
			(outputBufferSize / OUTPUT_LINE_LENGTH) * CR_LF_SIZE;
	}
	
	//
	// Include space for a terminating zero
	//
	outputBufferSize += 1;

	//
	// Allocate the output buffer
	//
	char *outputBuffer = (char *)malloc(outputBufferSize);
	if (!outputBuffer)
	{
		return NULL;
	}

	size_t i = 0;
	size_t j = 0;
	const size_t lineLength = separateLines ? INPUT_LINE_LENGTH : length;
	size_t lineEnd = lineLength;
	
	while (true)
	{
		if (lineEnd > length)
		{
			lineEnd = length;
		}

		for (; i + BINARY_UNIT_SIZE - 1 < lineEnd; i += BINARY_UNIT_SIZE)
		{
			//
			// Inner loop: turn 48 bytes into 64 base64 characters
			//
			outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2];
			outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4)
				| ((inputBuffer[i + 1] & 0xF0) >> 4)];
			outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i + 1] & 0x0F) << 2)
				| ((inputBuffer[i + 2] & 0xC0) >> 6)];
			outputBuffer[j++] = base64EncodeLookup[inputBuffer[i + 2] & 0x3F];
		}
		
		if (lineEnd == length)
		{
			break;
		}
		
		//
		// Add the newline
		//
		outputBuffer[j++] = '\r';
		outputBuffer[j++] = '\n';
		lineEnd += lineLength;
	}
	
	if (i + 1 < length)
	{
		//
		// Handle the single '=' case
		//
		outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2];
		outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4)
			| ((inputBuffer[i + 1] & 0xF0) >> 4)];
		outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i + 1] & 0x0F) << 2];
		outputBuffer[j++] =	'=';
	}
	else if (i < length)
	{
		//
		// Handle the double '=' case
		//
		outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2];
		outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0x03) << 4];
		outputBuffer[j++] = '=';
		outputBuffer[j++] = '=';
	}
	outputBuffer[j] = 0;
	
	//
	// Set the output length and return the buffer
	//
	if (outputLength)
	{
		*outputLength = j;
	}
	return outputBuffer;
}

@implementation NSData (Base64)

//
// dataFromBase64String:
//
// Creates an NSData object containing the base64 decoded representation of
// the base64 string 'aString'
//
// Parameters:
//    aString - the base64 string to decode
//
// returns the autoreleased NSData representation of the base64 string
//
+ (NSData *)dataFromBase64String:(NSString *)aString {

	NSData *data = [aString dataUsingEncoding:NSASCIIStringEncoding];
	size_t outputLength;
	void *outputBuffer = NewBase64Decode([data bytes], [data length], &outputLength);
	NSData *result = [NSData dataWithBytes:outputBuffer length:outputLength];
	free(outputBuffer);
	return result;
}

//
// base64EncodedString
//
// Creates an NSString object that contains the base 64 encoding of the
// receiver's data. Lines are broken at 64 characters long.
//
// returns an autoreleased NSString being the base 64 representation of the
//	receiver.
//
- (NSString *)base64EncodedString {
    
	size_t outputLength;
	char *outputBuffer =
		NewBase64Encode([self bytes], [self length], true, &outputLength);
	
	NSString *result =
		[[[NSString alloc]
			initWithBytes:outputBuffer
			length:outputLength
			encoding:NSASCIIStringEncoding]
		autorelease];
	free(outputBuffer);
	return result;
}

@end

或许这段的逻辑更精炼一些,但确实需要你头脑清醒些,才能挼清每一小步的逻辑,这就是早些年中国式编程的一个误区,也是印度当时能成为世界软件外包大国的原因(避勉这种无意义的抽象和极致,更多精力花费在业务逻辑上,以满足客户要求):

BOOL encodeBase64(const unsigned char *src, NSInteger srcLength, char *dest, NSUInteger *pDestLength) {
    
	static char base64Char[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	NSUInteger shouldLength = (srcLength + 2) / 3 * 3;
	NSUInteger base64Length = shouldLength / 3 * 4;
	if (NULL == pDestLength || *pDestLength < base64Length) {
		return NO;
	}
	*pDestLength = base64Length;
	char *data = malloc(shouldLength);
	memcpy(data, src, srcLength);
	for (NSUInteger i = srcLength; i < shouldLength; i++) {
		data[i] = 0;
	}
	for (NSUInteger i = 0; i < shouldLength; i += 3) {
		NSUInteger idx = i / 3 * 4;
		dest[idx] = base64Char[(data[i] & 0xfc) >> 2];
		dest[idx + 1] = base64Char[((data[i] & 0x03) << 4 ) + ((data[i + 1] & 0xf0) >> 4)];
		dest[idx + 2] = base64Char[((data[i + 1] & 0x0f) << 2) + ((data[i + 2] & 0xc0) >> 6)];
		dest[idx + 3] = base64Char[data[i + 2] & 0x3f];
	}
	free( data );
	for (NSUInteger i = 1; i <= shouldLength - srcLength; i++) {
		dest[base64Length - i] = '=';
	}
	return YES;
}

好了,Base64 编解码完成,其实叫它编解码是正确的,不过它同时有加密的功能,至少用肉眼无法误别内容,就像二维码一样,也是一种编解码,同时具有加密功能,加密的对象是肉眼!

后续还有MD5、DES、AES等等对称加密、非对称加密,这些不光有编解码的功效,同时更主要是加解密,加密对象是任何未知密钥或源内容的人或机器。


编码的意义并不一定在于要加密,而加密本身就是一种编码的方式!


不过,MD5已经被中国科学家破解,详见报道  山东大学王小云教授成功破解MD5 。

但,这只说明 MD5 并不是不可逆(无法反算出原始内容)的加密方式,而并不表示我们也很容易地能做到。

一种常规的 MD5 破解方式是跑字典,而跑字典也是很多破解的常用方式。






上一篇:JS压缩图片,在线图片压缩,Cavas压缩图片


下一篇:OSPF区域间路由汇总--CCNP学习笔记