├── KSSHA1Stream.h ├── KSSHA1Stream.m └── README.md /KSSHA1Stream.h: -------------------------------------------------------------------------------- 1 | // 2 | // KSSHA1Stream.h 3 | // Sandvox 4 | // 5 | // Created by Mike on 12/03/2011. 6 | // Copyright © 2011 Karelia Software 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | #import 29 | 30 | 31 | 32 | @interface KSSHA1Stream : NSOutputStream 33 | { 34 | @private 35 | CC_SHA1_CTX _ctx; 36 | NSData *_digest; 37 | } 38 | 39 | /** 40 | @result The SHA1 digest of all bytes written to the stream. 41 | 42 | The stream needs to know when you've finished writing bytes. This is signified 43 | by calling `-close`. So until `-close` is called, this method returns `nil`. 44 | */ 45 | @property(nonatomic, copy, readonly) NSData *SHA1Digest; 46 | 47 | @end 48 | 49 | 50 | @interface KSSHA1Stream (KSURLHashing) 51 | 52 | /** 53 | Hashes the contents of a file. 54 | 55 | @param URL The URL of the file to be hashed. May be anything Cocoa's URL Loading system supports. 56 | @result The SHA1 digest of the file at `URL`, or `nil` if accessing the file failed. 57 | */ 58 | + (NSData *)SHA1DigestOfContentsOfURL:(NSURL *)URL __attribute((nonnull(1))); 59 | 60 | /** 61 | Asynchronously hashes the contents of a file. 62 | 63 | Only suitable for calling from threads with a running runloop at present. 64 | `handler` is called on an arbitrary thread/queue. `digest` is `nil` if accesing 65 | the file failed, with `error` providing details. 66 | 67 | @param url The URL of the file to be hashed. May be anything Cocoa's URL Loading system supports. 68 | @param handler A block to be called when hashing finishes 69 | */ 70 | + (void)SHA1HashContentsOfURL:(NSURL *)url completionHandler:(void (^)(NSData *digest, NSError *error))handler __attribute__((nonnull(1,2))); 71 | 72 | @end 73 | 74 | 75 | #pragma mark - 76 | 77 | 78 | @interface NSData (KSSHA1Stream) 79 | 80 | /** 81 | Hashes an `NSData` object. 82 | 83 | @return The SHA1 digest of the receiver. 84 | */ 85 | - (NSData *)ks_SHA1Digest; 86 | 87 | /** 88 | Hashes an `NSData` object. 89 | 90 | @return The SHA1 digest of the receiver, in a human-friendly hex form. 91 | */ 92 | - (NSString *)ks_SHA1DigestString; 93 | 94 | /** 95 | Converts a raw SHA1 digest to a more human-friendly string form. 96 | 97 | @param digest An existing SHA1 digest 98 | @return The SHA1 digest, converted to a human-friendly hex form. 99 | */ 100 | + (NSString *)ks_stringFromSHA1Digest:(NSData *)digest; 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /KSSHA1Stream.m: -------------------------------------------------------------------------------- 1 | // 2 | // KSSHA1Stream.m 3 | // Sandvox 4 | // 5 | // Created by Mike on 12/03/2011. 6 | // Copyright © 2011 Karelia Software 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "KSSHA1Stream.h" 28 | 29 | 30 | @interface KSAsyncSHA1Stream : KSSHA1Stream 31 | { 32 | void (^_completionBlock)(NSData *digest, NSError *error); 33 | } 34 | 35 | - (id)initWithURL:(NSURL *)url completionHandler:(void (^)(NSData *digest, NSError *error))handler __attribute__((nonnull(1,2))); 36 | 37 | @end 38 | 39 | 40 | #pragma mark - 41 | 42 | 43 | @implementation KSSHA1Stream 44 | 45 | - (id)init; 46 | { 47 | if (self = [super init]) 48 | { 49 | CC_SHA1_Init(&_ctx); 50 | } 51 | return self; 52 | } 53 | 54 | - (void)close; 55 | { 56 | unsigned char digest[CC_SHA1_DIGEST_LENGTH]; 57 | CC_SHA1_Final(digest, &_ctx); 58 | 59 | _digest = [[NSData alloc] initWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; 60 | } 61 | 62 | - (void)dealloc; 63 | { 64 | [_digest release]; 65 | [super dealloc]; 66 | } 67 | 68 | - (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len; 69 | { 70 | CC_SHA1_Update(&_ctx, buffer, (CC_LONG) len); 71 | return len; 72 | } 73 | 74 | @synthesize SHA1Digest = _digest; 75 | 76 | @end 77 | 78 | 79 | #pragma mark - 80 | 81 | 82 | @implementation NSData (KSSHA1Stream) 83 | 84 | - (NSData *)ks_SHA1Digest 85 | { 86 | KSSHA1Stream *stream = [[KSSHA1Stream alloc] init]; 87 | [stream write:[self bytes] maxLength:[self length]]; 88 | [stream close]; 89 | NSData *result = [[[stream SHA1Digest] copy] autorelease]; 90 | 91 | [stream release]; 92 | return result; 93 | } 94 | 95 | - (NSString *)ks_SHA1DigestString 96 | { 97 | return [[self class] ks_stringFromSHA1Digest:[self ks_SHA1Digest]]; 98 | } 99 | 100 | + (NSString *)ks_stringFromSHA1Digest:(NSData *)digestData; 101 | { 102 | if (!digestData) return nil; 103 | 104 | static char sHEHexDigits[] = "0123456789abcdef"; 105 | 106 | unsigned char *digest = (unsigned char *)[digestData bytes]; 107 | 108 | unsigned char digestString[2 * CC_SHA1_DIGEST_LENGTH]; 109 | NSUInteger i; 110 | for (i=0; i> 4]; 113 | digestString[2*i+1] = sHEHexDigits[digest[i] & 0x0f]; 114 | } 115 | 116 | return [[[NSString alloc] initWithBytes:(const char *)digestString 117 | length:2 * CC_SHA1_DIGEST_LENGTH 118 | encoding:NSASCIIStringEncoding] autorelease]; 119 | } 120 | 121 | @end 122 | 123 | 124 | #pragma mark - 125 | 126 | 127 | @implementation KSSHA1Stream (KSURLHashing) 128 | 129 | + (NSData *)SHA1DigestOfContentsOfURL:(NSURL *)URL; 130 | { 131 | NSParameterAssert(URL); 132 | 133 | NSData *result; 134 | if ([URL isFileURL]) 135 | { 136 | KSSHA1Stream *hasher = [[KSSHA1Stream alloc] init]; 137 | 138 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 139 | NSInputStream *stream = [[NSInputStream alloc] initWithURL:URL]; 140 | #else 141 | NSInputStream *stream = [[NSInputStream alloc] initWithFileAtPath:[URL path]]; 142 | #endif 143 | [stream open]; 144 | 145 | #define READ_BUFFER_SIZE 2048*CC_SHA1_BLOCK_BYTES // just experimentation, but bigger has always run faster for me so far 146 | uint8_t buffer[READ_BUFFER_SIZE]; 147 | 148 | while ([stream streamStatus] < NSStreamStatusAtEnd) 149 | { 150 | NSInteger length = [stream read:buffer maxLength:READ_BUFFER_SIZE]; 151 | 152 | if (length > 0) 153 | { 154 | NSInteger written = [hasher write:buffer maxLength:length]; 155 | NSAssert((written == length), @"KSSHA1Stream is expected to handle all data you pass to it, but didn't this time for some reason"); 156 | } 157 | } 158 | 159 | [stream close]; 160 | [stream release]; 161 | 162 | [hasher close]; 163 | result = [[[hasher SHA1Digest] copy] autorelease]; 164 | [hasher release]; 165 | } 166 | else 167 | { 168 | KSSHA1Stream *hasher = [[KSSHA1Stream alloc] initWithURL:URL]; 169 | 170 | // Run the runloop until done 171 | while (!(result = [hasher SHA1Digest])) 172 | { 173 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantPast]]; 174 | } 175 | 176 | // Finish up. Empty hash means load failed 177 | if ([result length]) 178 | { 179 | result = [[result copy] autorelease]; 180 | } 181 | else 182 | { 183 | result = nil; 184 | } 185 | 186 | [hasher release]; 187 | } 188 | 189 | 190 | return result; 191 | } 192 | 193 | + (void)SHA1HashContentsOfURL:(NSURL *)url completionHandler:(void (^)(NSData *digest, NSError *error))handler __attribute__((nonnull(1,2))); 194 | { 195 | [[[KSAsyncSHA1Stream alloc] initWithURL:url completionHandler:handler] release]; 196 | } 197 | 198 | - (id)initWithURL:(NSURL *)URL; 199 | { 200 | if (self = [self init]) 201 | { 202 | [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:URL] 203 | delegate:self]; 204 | } 205 | return self; 206 | } 207 | 208 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 209 | { 210 | [self write:[data bytes] maxLength:[data length]]; 211 | } 212 | 213 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 214 | { 215 | [self close]; 216 | } 217 | 218 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 219 | { 220 | _digest = [[NSData alloc] init]; 221 | } 222 | 223 | @end 224 | 225 | 226 | #pragma mark - 227 | 228 | 229 | @implementation KSAsyncSHA1Stream 230 | 231 | - (id)initWithURL:(NSURL *)url completionHandler:(void (^)(NSData *digest, NSError *error))handler; 232 | { 233 | // Rely on super's NSURLConnection to retain us 234 | if (self = [self initWithURL:url]) 235 | { 236 | _completionBlock = [handler copy]; 237 | } 238 | return self; 239 | } 240 | 241 | - (void)dealloc; 242 | { 243 | [_completionBlock release]; 244 | [super dealloc]; 245 | } 246 | 247 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 248 | { 249 | [super connectionDidFinishLoading:connection]; 250 | _completionBlock([self SHA1Digest], nil); 251 | } 252 | 253 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 254 | { 255 | [super connection:connection didFailWithError:error]; 256 | _completionBlock(nil, error); 257 | } 258 | 259 | @end 260 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Features 2 | ======== 3 | 4 | Adapts `NSOutputStream`'s API for use to generate SHA1 digests. 5 | 6 | Also provides convenience methods for: 7 | 8 | * Directly hashing a lump of data: `-[NSData ks_SHA1Digest]`. 9 | * Hashing the contents of a URL, even a remote one: `+SHA1DigestOfContentsOfURL:` 10 | * Converting a digest into a hexadecimal string representation: `+ks_stringFromSHA1Digest:` 11 | 12 | Contact 13 | ======= 14 | 15 | I'm Mike Abdullah, of [Karelia Software](http://karelia.com). [@mikeabdullah](http://twitter.com/mikeabdullah) on Twitter. 16 | 17 | Questions about the code should be left as issues at https://github.com/karelia/KSCrypto or message me on Twitter. 18 | 19 | Dependencies 20 | ============ 21 | 22 | CommonCrypto (and Foundation, obviously). 23 | 24 | Licence 25 | ======= 26 | 27 | Copyright © 2011 Karelia Software 28 | 29 | Permission is hereby granted, free of charge, to any person obtaining a copy 30 | of this software and associated documentation files (the "Software"), to deal 31 | in the Software without restriction, including without limitation the rights 32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 33 | copies of the Software, and to permit persons to whom the Software is 34 | furnished to do so, subject to the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included in 37 | all copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 45 | THE SOFTWARE. 46 | 47 | Usage 48 | ===== 49 | 50 | 1. Add `KSSHA1Stream.h` and `KSSHA1Stream.m` to your project. Ideally, make this repo a submodule, but hey, it's your codebase, do whatever you feel like. 51 | 2. Link against `CommonCrypto` 52 | 53 | To hash a stream of data: 54 | 55 | 1. `[[KSSHA1Stream alloc] init]` 56 | 2. Call `-write:maxLength:` as your data arrives. `KSSHA1Stream` promises to always process all bytes passed to it 57 | 3. Call `-close` on the stream 58 | 4. Retrieve the result using the `SHA1Digest` property 59 | --------------------------------------------------------------------------------