Песочница →
Вычисление хеш-суммы строки в iOS
Давайте рассмотрим с вами очень простую задачу – вычисление хеш-суммы некоторой строки. Задача встречается повсеместно, стоит вспомнить хотя бы аутентификацию пользователя посредством OAuth. Решение задачи будем рассматривать в рамках разработки приложений под iOS. Ниже, я постараюсь показать наиболее красивое (на мой взгляд) решение задачи с точки зрения архитектуры программного кода.
Итак, получаем следующие условия задачи:
Итак, пусть нам дана некоторая строка
Все, это в итоге выглядит следующим образом:
Соответственно, функция
Помимо этого, давайте рассмотрим случай, в котором результат работы функции хеширования постобрабатывается алгоритмом HMAC. Делается это, как и предыдущий пример, практически в одну строчку – вызовом функции
Теперь нам необходимо проинтерпретировать каким-либо образом полученный результат, чтобы получить на выходе экземпляр NSString. Для этого есть два наиболее распространенных варианта:
Первый вариант очень простой и реализуется следующим образом:
Этот вариант несколько сложнее в реализации собственными руками. Но этого собственно говоря и не требуется, существует достаточно хорошая реализация кодирования и декодирования данных в Base64, которую можно найти здесь. В данной библиотеке как раз уже есть готовый статический метод, который для экземпляра NSData строит на выходе интепретацию в виде Base64. Тогда вычисление интерпретации будет выглядеть следующим образом:
Итак осталось решить последнюю задачу – как сделать представленный код наиболее компактным. Здесь можно вспомнить одну замечатульную возможность языка Objective-C – а именно категории. Категории – это механизм расширения функционала существующего класса посредством добавления к нему новых методов. Причем расширять ими вы можете абсолютно любой класс, как свой собственный, так и любой системный. Напишем именно такую категорию для класса
Полностью раписывать файл реализации не будем – он абсолютно однотипен, но покажем по одному методу из каждой группы на пример алгоритма SHA1.
В результате, вычисление хеш-суммы любой строки можно очень быстро и компактно. Например следующим образом:
Итак, получаем следующие условия задачи:
- Нам дана некоторая строка в виде экземпляра
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];
29.11.2011 00:20+0400