MYCryptor.m
author snej@snej-mbp.mtv.corp.google.com
Wed Apr 08 16:30:52 2009 -0700 (2009-04-08)
changeset 3 1dfe820d7ebe
parent 2 8982b8fada63
child 14 3af1d1c0ceb5
permissions -rw-r--r--
* Replaced MYKeyPair with MYPrivateKey.
* Changed config files.
     1 //
     2 //  Cryptor.m
     3 //  MYCrypto
     4 //
     5 //  Created by Jens Alfke on 3/21/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYCryptor.h"
    10 #import "MYDigest.h"
    11 #import "Test.h"
    12 
    13 #if MYCRYPTO_USE_IPHONE_API
    14 #import <Security/SecRandom.h>
    15 #else
    16 #import "MYCrypto_Private.h"
    17 #import "MYKeychain.h"
    18 #import <stdlib.h>
    19 #endif
    20 
    21 
    22 NSString* const CryptorErrorDomain = @"CCCryptor";
    23 
    24 #if !MYCRYPTO_USE_IPHONE_API
    25 static BOOL generateRandomBytes(CSSM_CSP_HANDLE module, uint32_t lengthInBytes, void *dstBytes);
    26 #endif
    27 
    28 
    29 @interface MYCryptor ()
    30 @property (readwrite, retain) NSError *error;
    31 @end
    32 
    33 
    34 
    35 @implementation MYCryptor
    36 
    37 
    38 + (NSData*) randomKeyOfLength: (size_t)lengthInBits {
    39     size_t lengthInBytes = (lengthInBits + 7)/8;
    40     NSParameterAssert(lengthInBytes<100000);
    41     uint8_t *bytes = malloc(lengthInBytes);
    42     if (!bytes) return nil;
    43 #if MYCRYPTO_USE_IPHONE_API
    44     BOOL ok = SecRandomCopyBytes(kSecRandomDefault, lengthInBytes,bytes) >= 0;
    45 #else
    46     BOOL ok = generateRandomBytes([[MYKeychain defaultKeychain] CSPHandle], lengthInBytes, bytes);
    47 #endif
    48     if (ok)
    49         return [NSData dataWithBytesNoCopy: bytes length: lengthInBytes freeWhenDone: YES];
    50     else {
    51         free(bytes);
    52         return nil;
    53     }
    54 }
    55 
    56 + (NSData*) keyOfLength: (size_t)lengthInBits
    57          fromPassphrase: (NSString*)passphrase
    58                    salt: (id)salt
    59 {
    60     // This follows algorithm PBKDF1 from PKCS#5 v2.0, with Hash=SHA-256 and c=13.
    61     Assert(passphrase);
    62     Assert(salt);
    63     passphrase = $sprintf(@"MYCrypto|%@|%@", passphrase, salt);
    64     size_t lengthInBytes = (lengthInBits + 7)/8;
    65     MYDigest *digest = [[passphrase dataUsingEncoding: NSUTF8StringEncoding] my_SHA256Digest];
    66     for (int i=0; i<12; i++)
    67         digest = digest.asData.my_SHA256Digest;
    68     if (lengthInBytes <= digest.length)
    69         return [digest.asData subdataWithRange: NSMakeRange(0,lengthInBytes)];
    70     else
    71         return nil;
    72 }
    73 
    74 
    75 - (id) initWithKey: (NSData*)key
    76          algorithm: (CCAlgorithm)algorithm
    77          operation: (CCOperation)op {
    78     self = [super init];
    79     if (self) {
    80         NSParameterAssert(key);
    81         _key = [key copy];
    82         _operation = op;
    83         _algorithm = algorithm;
    84         _options = kCCOptionPKCS7Padding;
    85     }
    86     return self;
    87 }
    88 
    89 - (id) initEncryptorWithKey: (NSData*)key algorithm: (CCAlgorithm)algorithm {
    90     return [self initWithKey: key algorithm: algorithm operation: kCCEncrypt];
    91 }
    92 
    93 - (id) initDecryptorWithKey: (NSData*)key algorithm: (CCAlgorithm)algorithm {
    94     return [self initWithKey: key algorithm: algorithm operation: kCCDecrypt];
    95 }
    96 
    97 - (void) dealloc
    98 {
    99     if (_cryptor)
   100         CCCryptorRelease(_cryptor);
   101     [_key autorelease];
   102     [_output autorelease];
   103     [_outputStream release];
   104     [super dealloc];
   105 }
   106 
   107 - (void) finalize
   108 {
   109     if (_cryptor)
   110         CCCryptorRelease(_cryptor);
   111     [super finalize];
   112 }
   113 
   114 
   115 @synthesize key=_key, algorithm=_algorithm, options=_options,
   116     outputStream=_outputStream, error=_error;
   117 
   118 
   119 - (BOOL) _check: (CCCryptorStatus)status {
   120     if (status == kCCSuccess)
   121         return YES;
   122     else {
   123         Warn(@"MYCryptor: CCCryptor error %i", status);
   124         self.error = [NSError errorWithDomain: CryptorErrorDomain code: status userInfo: nil];
   125         return NO;
   126     }
   127 }
   128 
   129 
   130 - (BOOL) _outputBytes: (const void*)bytes length: (size_t)length {
   131     if (_outputStream) {
   132         size_t written = [_outputStream write: bytes maxLength: length];
   133         if (written < 0) {
   134             self.error = _outputStream.streamError;
   135             if (_error)
   136                 Warn(@"MYCryptor: NSOutputStream error %@", _error);
   137             else
   138                 [self _check: kMYCryptorErrorOutputStreamChoked];
   139             return NO;
   140         } else if (written < length) {
   141             [self _check: kMYCryptorErrorOutputStreamChoked];
   142             return NO;
   143         }
   144     } else if (length > 0) {
   145         [_output appendBytes: bytes length: length];
   146     }
   147     return YES;
   148 }
   149 
   150 
   151 - (BOOL) _start {
   152     if (!_cryptor && !_error) {
   153         if ([self _check: CCCryptorCreate(_operation, _algorithm, _options,
   154                                           _key.bytes, _key.length, NULL, &_cryptor)]) {
   155             _output = [[NSMutableData alloc] initWithCapacity: 1024];
   156         }
   157     }
   158     return !_error;
   159 }
   160 
   161 
   162 - (BOOL) addBytes: (const void*)bytes length: (size_t)length {
   163     if (length > 0) {
   164         NSParameterAssert(bytes!=NULL);
   165         if(!_error && (_cryptor || [self _start])) {
   166             size_t outputLength = CCCryptorGetOutputLength(_cryptor,length,false);
   167             void *output = malloc(outputLength);
   168             if ([self _check: CCCryptorUpdate(_cryptor, bytes, length,
   169                                               output, outputLength, &outputLength)]) {
   170                 [self _outputBytes: output length: outputLength];
   171             }
   172             free(output);
   173         }
   174     }
   175     return !_error;
   176 }
   177 
   178 - (BOOL) addData: (NSData*)data
   179 {
   180     return [self addBytes: data.bytes length: data.length];
   181 }
   182 
   183 - (BOOL) addString: (NSString*)str {
   184     return [self addData: [str dataUsingEncoding: NSUTF8StringEncoding]];
   185 }
   186 
   187 
   188 - (BOOL) addFromStream: (NSInputStream*)input
   189 {
   190     uint8_t inputBuffer[1024];
   191     size_t avail;
   192     while (!_error && input.hasBytesAvailable) {
   193         avail = sizeof(inputBuffer);
   194         NSInteger nRead = [input read: inputBuffer maxLength: sizeof(inputBuffer)];
   195         if (nRead < 0) {
   196             self.error = input.streamError;
   197             return NO;
   198         } else if (nRead == 0) {
   199             break;
   200         } else if (![self addBytes: inputBuffer length: nRead])
   201             return NO;
   202     }
   203     return YES;
   204 }
   205 
   206 
   207 - (BOOL) finish
   208 {
   209     if(!_error && (_cryptor || [self _start])) {
   210         size_t outputLength = 100; //CCCryptorGetOutputLength(_cryptor,1,true);
   211         void *output = malloc(outputLength);
   212         if ([self _check: CCCryptorFinal(_cryptor, output, outputLength, &outputLength)]) {
   213             [self _outputBytes: output length: outputLength];
   214         }
   215         free(output);
   216     }
   217     CCCryptorRelease(_cryptor);
   218     _cryptor = NULL;
   219     return !_error;
   220 }
   221 
   222 
   223 - (NSData*) outputData {
   224     if (_cryptor) [self finish];
   225     if(_error) {
   226         [_output release];
   227         _output = nil;
   228     }
   229     return _output;
   230 }
   231 
   232 - (NSString*) outputString {
   233     NSData *output = self.outputData;
   234     if (output) {
   235         NSString *str = [[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding];
   236         return [str autorelease];
   237     } else
   238         return nil;
   239 }
   240 
   241 
   242 // NSStream delegate method
   243 - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
   244     switch (eventCode) {
   245         case NSStreamEventHasBytesAvailable:
   246             [self addFromStream: (NSInputStream*)stream];
   247             break;
   248         case NSStreamEventEndEncountered:
   249             [self finish];
   250             break;
   251         case NSStreamEventErrorOccurred:
   252             if (!_error)
   253                 self.error = stream.streamError;
   254             break;
   255         default:
   256             break;
   257     }
   258 }
   259 
   260 
   261 
   262 @end
   263 
   264 
   265 
   266 
   267 #if !MYCRYPTO_USE_IPHONE_API
   268 static BOOL generateRandomBytes(CSSM_CSP_HANDLE module, uint32_t lengthInBytes, void *dstBytes) {
   269     // Adapted from code in Keychain.framework's KeychainUtils.m by Wade Tregaskis.
   270     CSSM_CC_HANDLE ccHandle;
   271     if (!checkcssm(CSSM_CSP_CreateRandomGenContext(module, CSSM_ALGID_APPLE_YARROW, NULL,
   272                                                   lengthInBytes, &ccHandle),
   273                    @"CSSM_CSP_CreateRandomGenContext"))
   274         return NO;
   275     CSSM_DATA data = {.Data=dstBytes, .Length=lengthInBytes};
   276     BOOL ok = checkcssm(CSSM_GenerateRandom(ccHandle, &data), @"CSSM_GenerateRandom");
   277     CSSM_DeleteContext(ccHandle);
   278     return ok;
   279 }
   280 #endif
   281 
   282 
   283 
   284 
   285 TestCase(MYCryptor) {
   286     // Encryption:
   287     NSData *key = [MYCryptor randomKeyOfLength: 256];
   288     Log(@"Key = %@",key);
   289     MYCryptor *enc = [[MYCryptor alloc] initEncryptorWithKey: key algorithm: kCCAlgorithmAES128];
   290     CAssert(enc);
   291     CAssert([enc addString: @"This is a test. "]);
   292     CAssert([enc addString: @"This is only a test."]);
   293     CAssertEqual(enc.error, nil);
   294     NSData *encrypted = enc.outputData;
   295     CAssertEqual(enc.error, nil);
   296     CAssert(encrypted.length > 0);
   297     [enc release];
   298     Log(@"Encrypted = %@", encrypted);
   299     
   300     // Decryption:
   301     MYCryptor *dec = [[MYCryptor alloc] initDecryptorWithKey: key algorithm: kCCAlgorithmAES128];
   302     CAssert(dec);
   303     CAssert([dec addData: encrypted]);
   304     NSString *decrypted = dec.outputString;
   305     CAssertEqual(dec.error, nil);
   306     [dec release];
   307     Log(@"Decrypted = '%@'", decrypted);
   308     CAssertEqual(decrypted, @"This is a test. This is only a test.");
   309     
   310     // Encryption to stream:
   311     key = [MYCryptor randomKeyOfLength: 256];
   312     Log(@"Key = %@",key);
   313     enc = [[MYCryptor alloc] initEncryptorWithKey: key algorithm: kCCAlgorithmAES128];
   314     CAssert(enc);
   315     enc.outputStream = [NSOutputStream outputStreamToMemory];
   316     [enc.outputStream open];
   317     CAssert([enc addString: @"This is a test. "]);
   318     CAssert([enc addString: @"This is only a test."]);
   319     CAssert([enc finish]);
   320     CAssertEqual(enc.error, nil);
   321     encrypted = [[enc.outputStream propertyForKey: NSStreamDataWrittenToMemoryStreamKey] retain];
   322     CAssert(encrypted.length > 0);
   323     [enc release];
   324     Log(@"Encrypted = %@", encrypted);
   325     
   326     dec = [[MYCryptor alloc] initDecryptorWithKey: key algorithm: kCCAlgorithmAES128];
   327     CAssert(dec);
   328     dec.outputStream = [NSOutputStream outputStreamToMemory];
   329     [dec.outputStream open];
   330     CAssert([dec addData: encrypted]);
   331     CAssert([dec finish]);
   332     CAssertEqual(dec.error, nil);
   333     NSData *decryptedData = [dec.outputStream propertyForKey: NSStreamDataWrittenToMemoryStreamKey];
   334     [dec release];
   335     decrypted = [[NSString alloc] initWithData: decryptedData
   336                                                       encoding: NSUTF8StringEncoding];
   337     Log(@"Decrypted = '%@'", decrypted);
   338     CAssertEqual(decrypted, @"This is a test. This is only a test.");
   339     [encrypted release];
   340     [decrypted release];
   341 }