Вычисление хеш-суммы строки в 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];

Средняя зарплата в IT

120 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 9 250 анкет, за 1-ое пол. 2021 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 3

    0
    Я так понял, что предполагается, что включен ARC? Потому что в коде я ничего про autorelease не вижу, и в примерах кода освобождения строк я тоже не вижу…
      0
      Да, все верно предполагается, что ARC включен. В любом случае расстановка блоков освобождения памяти сложности не составит.
        0
        а так не короче:

        NSString * md5( NSString *str ) {
              const char *cStr = [str UTF8String];
              unsigned char result[CC_MD5_DIGEST_LENGTH]; 
              CC_MD5( cStr, strlen(cStr), result );
        
             return [NSString stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", result[0], result[1],   result[2], result[3], result[4], result[5], result[6], result[7], result[8], result[9], result[10], result[11], result[12], result[13], result[14], result[15] ];
           } 
        
        - (void)viewDidLoad {
         NSString *testHash = md5( @"privet"); NSLog(@"%@",testHash);
                              }
        

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое