Яндекс.Метрика

    Песочница

    Вычисление хеш-суммы строки в iOS

    Давайте рассмотрим с вами очень простую задачу – вычисление хеш-суммы некоторой строки. Задача встречается повсеместно, стоит вспомнить хотя бы аутентификацию пользователя посредством OAuth. Решение задачи будем рассматривать в рамках разработки приложений под iOS. Ниже, я постараюсь показать наиболее красивое (на мой взгляд) решение задачи с точки зрения архитектуры программного кода.

    Итак, получаем следующие условия задачи:
    • Нам дана некоторая строка в виде экземпляра NSString;
    • Необходимо вычислить значение ее хеш-суммы также в виде некоторой строки NSString;
    • Надо постараться сделать выбранное решение наиболее компактным и красивым;


    Собственно вычисление суммы

    Итак, пусть нам дана некоторая строка NSString* string = @”Trololo”. Рассмотрим вычисление ее хеш-суммы алгоритмом MD5. Делается это крайне просто:
    • Подключаем заголовочный файл CommonCrypto/CommonDigest.h;
    • Получаем представление нашей строки в виде const char*;
    • Формируем выходной буфер хеширования как unsigned char;
    • Вычисляем собственно само значение хеш-суммы;
    • И наконец оборачиваем сырые байтики в экземпляр NSData;

    Все, это в итоге выглядит следующим образом:
    #import	<CommonCrypto/CommonDigest.h>
    …
    NSString*	string	=	@”Trololo”;
    const char*	data	=	[string UTF8String];
    unsigned char	hashBuffer[CC_MD5_DIGEST_LENGTH];
    
    CC_MD5(data, strlen(data), hashBuffer);
    
    NSData*		result	=	[NSData dataWithBytes:hashBuffer length:CC_MD5_DIGEST_LENGTH];

    Соответственно, функция CC_MD5(...) и константа CC_MD5_DIGEST_LENGTH объявлены в подключенном нами файле.

    Помимо этого, давайте рассмотрим случай, в котором результат работы функции хеширования постобрабатывается алгоритмом HMAC. Делается это, как и предыдущий пример, практически в одну строчку – вызовом функции CCHmac(...), которой в качестве одного из параметров передается идентификатор алгоритма хеширования, в нашем случае это kCCHmacAlgMD5. Не забываем только подключить дополнительно еще один заголовочный файл — <CommonCrypto/CommonHMAC.h>.

    #import <CommonCrypto/CommonDigest.h>
    #import <CommonCrypto/CommonHMAC.h>
    
    NSString*	string	=	@”Trololo”;
    NSString*	hmacKey	=	@”hmacKey”;
    
    const char*	data	=	[string 	UTF8String];
    const char*	hmacData=	[hmacKey	UTF8String];
    unsigned char	hashBuffer[CC_MD5_DIGEST_LENGTH];
    
    CCHmac(kCCHmacAlgMD5, hmacData, strlen(hmacData), data, strlen(data), hashBuffer);
    
    NSData*		result	=	[NSData dataWithBytes:hashBuffer length:CC_MD5_DIGEST_LENGTH];


    Интерпретация данных

    Теперь нам необходимо проинтерпретировать каким-либо образом полученный результат, чтобы получить на выходе экземпляр NSString. Для этого есть два наиболее распространенных варианта:
    • Получение строки посредством интерпретации каждого байта как шестнадцатиричного числа;
    • И интерпретация данных в строку посредством кодирования в Base64;

    В виде шестандцатиричной строки

    Первый вариант очень простой и реализуется следующим образом:
    • Удаляем последний шаг предыдущего алгоритма (не используем NSData) и работаем с сырыми байтами;
    • Создаем экземпляр изменяемой строки длиной в два раза больше полученных сырых байтов;
    • Для каждого байта из полученного буфера хеширования добавляем к строке его строковое представление как шестнадцатиричного числа;

    NSString*	string	=	@”Trololo”;
    const	char*	data	=	[string	UTF8String];
    unsigned char	hashBuffer[CC_MD5_DIGEST_LENGTH];
    
    CC_MD5(data, strlen(data), hashBuffer);
    
    NSMutableString* result	=	[[NSMutableString	alloc]	initWithCapacity:CC_MD5_DIGEST_LENGTH*2];
    
    for (int i= 0;	i < CC_MD5_DIGEST_LENGTH; i++)
    	[result	appenFormat:@”%02X”, hashBuffer[i]];


    В виде строки Base64

    Этот вариант несколько сложнее в реализации собственными руками. Но этого собственно говоря и не требуется, существует достаточно хорошая реализация кодирования и декодирования данных в Base64, которую можно найти здесь. В данной библиотеке как раз уже есть готовый статический метод, который для экземпляра NSData строит на выходе интепретацию в виде Base64. Тогда вычисление интерпретации будет выглядеть следующим образом:
    NSData*		buffer	=	[NSData	dataWithBytes:hashBuffer	length:CC_MD5_DIGEST_LENGTH];
    NSString* 	result  =	[Base64	encode:buffer];


    Оформление категории

    Итак осталось решить последнюю задачу – как сделать представленный код наиболее компактным. Здесь можно вспомнить одну замечатульную возможность языка Objective-C – а именно категории. Категории – это механизм расширения функционала существующего класса посредством добавления к нему новых методов. Причем расширять ими вы можете абсолютно любой класс, как свой собственный, так и любой системный. Напишем именно такую категорию для класса NSString. Создаем два файла, соответственно заголовочный NSString+Hash.h и файл реализации NSString+Hash.m. Объявляется категория практически также как и любой класс, за следующими исключениями: после названия класса в скобках указывается имя категории, опускается блок объявления членов класса. Таким образом получаем следующий вид заголовочного файла (заложены все методы для работы с двумя наиболее распространенными алгоритмами хеширования MD5 и SHA1):

    #import <Foundation/Foundation.h>
    
    #import	<CommonCrypto/CommonHMAC.h>
    #import	<CommonCrypto/CommonDigest.h>
    
    #import "Base64.h"
    
    @interface NSString (NSString_NM_HASH)
    
        //RAW
    - (NSData*) MD5;
    - (NSData*) SHA1;
    - (NSData*) HMAC_MD5:	(NSString*)hmacKey;
    - (NSData*) HMAC_SHA1:	(NSString*)hmacKey;
    
        //INTERPRET Base64
    - (NSString*) MD5_x64;
    - (NSString*) SHA1_x64;
    - (NSString*) HMAC_MD5_x64:(NSString*)hmacKey;
    - (NSString*) HMAC_SHA1_x64:(NSString*)hmacKey;
    
        //INTERPRET HEX
    - (NSString*) MD5_HEX;
    - (NSString*) SHA1_HEX;
    - (NSString*) HMAC_MD5_HEX:(NSString*)hmacKey;
    - (NSString*) HMAC_SHA1_HEX:(NSString*)hmacKey;
    
    @end


    Полностью раписывать файл реализации не будем – он абсолютно однотипен, но покажем по одному методу из каждой группы на пример алгоритма SHA1.

    #import “NSString+Hash.h”
    
    @implementation NSString (NSString_NM_HASH)
    
    - (NSData*) HMAC_SHA1: (NSString *)hmacKey{
    	const char*	data	= [self		UTF8String];
    	const char*	hashKey	= [hmacKey	UTF8String];
    	unsigned char	hashingBuffer[CC_SHA1_DIGEST_LENGTH];
    		
    	CCHmac(kCCHmacAlgSHA1, hashKey, strlen(hashKey), data, strlen(data), hashingBuffer);
    
    	return	[NSData	dataWithBytes:hashingBuffer length:CC_SHA1_DIGEST_LENGTH];
    }
    
    - (NSString*) HMAC_SHA1_x64:(NSString *)hmacKey{
        return [Base64  encode:[self    HMAC_SHA1:hmacKey]];
    }
    
    - (NSString*) HMAC_SHA1_HEX:(NSString *)hmacKey{
    	const char*	data	= [self		UTF8String];
    	const char*	hashKey	= [hmacKey	UTF8String];
    	unsigned char	hashingBuffer[CC_SHA1_DIGEST_LENGTH];
        
    	CCHmac(kCCHmacAlgSHA1, hashKey, strlen(hashKey), data, strlen(data), hashingBuffer);
       
    	NSMutableString*    result  =   [[NSMutableString   alloc]  initWithCapacity:CC_SHA1_DIGEST_LENGTH*2];
    	for (int i = 0; i   < CC_SHA1_DIGEST_LENGTH; i++)
    		[result appendFormat:@"%02X", hashingBuffer[i]];
        
    	return result;
    }
    @end


    Заключение

    В результате, вычисление хеш-суммы любой строки можно очень быстро и компактно. Например следующим образом:

    #import “NSString+Hash.h”
    
    NSString*	string		=	@”Trololo”;
    NSString*	string_md5	=	[string	MD5_HEX];
    NSString*	string_sh1	=	[string	SHA1_x64];