
Calculating the hash of a row string in iOS
Let's look at a very simple task with you - calculating the hash sum of a string. The problem is ubiquitous, it is worth recalling at least user authentication through OAuth. We will consider the solution of the problem as part of the development of applications for iOS. Below, I will try to show the most beautiful (in my opinion) solution of the problem from the point of view of software code architecture.
So, we obtain the following conditions of the problem:
So let us be given some string
All this ultimately looks like this:
Accordingly, the function
In addition, let's look at a case in which the result of the hash function is processed by the HMAC algorithm. This, like the previous example, is done almost in one line - by calling a function
Now we need to interpret the result in some way to get the output of the NSString. There are two most common options for this:
The first option is very simple and is implemented as follows:
This option is somewhat more difficult to do with your own hands. But this, in fact, is not required, there is a fairly good implementation of encoding and decoding data in Base64, which can be found here . This library already has a ready-made static method that, for an instance of NSData, builds an interpretation in the form of Base64 at the output. Then the calculation of the interpretation will look like this:
So, it remains to solve the last problem - how to make the presented code the most compact. Here we can recall one remarkable feature of the Objective-C language - namely, categories. Categories is a mechanism for expanding the functionality of an existing class by adding new methods to it. Moreover, you can expand with them absolutely any class, either your own or any system one. We will write just such a category for the class
We will not completely write down the implementation file - it is absolutely the same, but we will show one method from each group for an example of the SHA1 algorithm.
As a result, calculating the hash sum of any string can be very fast and compact. For example as follows:
So, we obtain the following conditions of the problem:
- We are given some string as an instance
NSString
; - It is necessary to calculate the value of its hash sum also in the form of some string
NSString
; - We must try to make the selected solution the most compact and beautiful;
Actually calculating the amount
So let us be given some string
NSString* string = @”Trololo”
. Consider the calculation of its hash sum by the MD5 algorithm. This is done extremely simply:- We include the header file CommonCrypto / CommonDigest.h;
- We get a representation of our string as const char *;
- Configure the hash output buffer as unsigned char;
- We calculate the actual value of the hash sum;
- Finally, we wrap the raw bytes in an instance of NSData;
All this ultimately looks like this:
#import
…
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];
Accordingly, the function
CC_MD5(...)
and constant are CC_MD5_DIGEST_LENGTH
declared in the file we connected. In addition, let's look at a case in which the result of the hash function is processed by the HMAC algorithm. This, like the previous example, is done almost in one line - by calling a function
CCHmac(...)
that is passed the hash algorithm identifier as one of the parameters, in our case it is kCCHmacAlgMD5
. Do not forget to connect in addition one more header file -
.#import
#import
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];
Data interpretation
Now we need to interpret the result in some way to get the output of the NSString. There are two most common options for this:
- Getting a string by interpreting each byte as a hexadecimal number;
- And the interpretation of data in a string through encoding in Base64;
As a hexadecimal string
The first option is very simple and is implemented as follows:
- We delete the last step of the previous algorithm (we do not use NSData) and work with raw bytes;
- We create an instance of a mutable string twice as long as the raw bytes received;
- For each byte from the received hash buffer, add to the string its string representation as a hexadecimal number;
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]];
As a Base64 string
This option is somewhat more difficult to do with your own hands. But this, in fact, is not required, there is a fairly good implementation of encoding and decoding data in Base64, which can be found here . This library already has a ready-made static method that, for an instance of NSData, builds an interpretation in the form of Base64 at the output. Then the calculation of the interpretation will look like this:
NSData* buffer = [NSData dataWithBytes:hashBuffer length:CC_MD5_DIGEST_LENGTH];
NSString* result = [Base64 encode:buffer];
Category Design
So, it remains to solve the last problem - how to make the presented code the most compact. Here we can recall one remarkable feature of the Objective-C language - namely, categories. Categories is a mechanism for expanding the functionality of an existing class by adding new methods to it. Moreover, you can expand with them absolutely any class, either your own or any system one. We will write just such a category for the class
NSString
. Create two files, respectively, header NSString+Hash.h
and implementation fileNSString+Hash.m
. A category is declared in almost the same way as any class, with the following exceptions: after the class name, the name of the category is indicated in brackets, the block for declaring class members is omitted. Thus, we obtain the following type of header file (all methods for working with the two most common hashing algorithms MD5 and SHA1 are laid down):#import
#import
#import
#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
We will not completely write down the implementation file - it is absolutely the same, but we will show one method from each group for an example of the SHA1 algorithm.
#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
Conclusion
As a result, calculating the hash sum of any string can be very fast and compact. For example as follows:
#import “NSString+Hash.h”
NSString* string = @”Trololo”;
NSString* string_md5 = [string MD5_HEX];
NSString* string_sh1 = [string SHA1_x64];