Skip to content
Permalink
Browse files

Replace OpenSSL for SHA1 calculation with Apple's CommonCrypto

(The easier half of #2223)
Also added some unit tests.
  • Loading branch information...
dmoagx committed Sep 13, 2015
1 parent b958ab0 commit fa2cd0b4841324776b10d968e1250676b0c65e32
Showing with 169 additions and 7 deletions.
  1. +2 −0 Source/SPDataAdditions.h
  2. +45 −7 Source/SPDataAdditions.m
  3. +112 −0 UnitTests/SPDataAdditionsTests.m
  4. +10 −0 sequel-pro.xcodeproj/project.pbxproj
@@ -30,6 +30,8 @@

@interface NSData (SPDataAdditions)

- (NSData *)sha1Hash;

- (NSData *)dataEncryptedWithPassword:(NSString *)password;
- (NSData *)dataDecryptedWithPassword:(NSString *)password;
- (NSData *)compress;
@@ -36,11 +36,41 @@

#include <zlib.h>
#include <openssl/aes.h>
#include <openssl/sha.h>
#include <CommonCrypto/CommonCrypto.h>
#include <stdlib.h>

uint32_t LimitUInt32(NSUInteger i);

#pragma mark -

@implementation NSData (SPDataAdditions)

- (NSData *)sha1Hash
{
unsigned char digest[CC_SHA1_DIGEST_LENGTH];

//let's do it as a one step operation, if it fits
if([self length] <= UINT32_MAX) {
CC_SHA1([self bytes], (uint32_t)[self length], digest);
}
// or multi-step if length > 32 bit
else {
CC_SHA1_CTX ctx;
CC_SHA1_Init(&ctx);

NSUInteger offset = 0;
uint32_t len;
while((len = LimitUInt32([self length]-offset)) > 0) {
CC_SHA1_Update(&ctx, ([self bytes]+offset), len);
offset += len;
}

CC_SHA1_Final(digest, &ctx);
}

return [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
}

- (NSData *)dataEncryptedWithPassword:(NSString *)password
{
// Create a random 128-bit initialization vector
@@ -68,10 +98,9 @@ - (NSData *)dataEncryptedWithPassword:(NSString *)password
memcpy(paddedBytes + (paddedLength - 4), &bigIntDataLength, 4);

// Create the key from first 128-bits of the 160-bit password hash
unsigned char passwordDigest[20];
SHA1((const unsigned char *)[password UTF8String], strlen([password UTF8String]), passwordDigest);
NSData *passwordDigest = [[password dataUsingEncoding:NSUTF8StringEncoding] sha1Hash];
AES_KEY aesKey;
AES_set_encrypt_key(passwordDigest, 128, &aesKey);
AES_set_encrypt_key([passwordDigest bytes], 128, &aesKey);

// AES-128-cbc encrypt the data, filling in the buffer after the IV
AES_cbc_encrypt(paddedBytes, encryptedBytes + 16, paddedLength, &aesKey, iv, AES_ENCRYPT);
@@ -83,12 +112,11 @@ - (NSData *)dataEncryptedWithPassword:(NSString *)password
- (NSData *)dataDecryptedWithPassword:(NSString *)password
{
// Create the key from the password hash
unsigned char passwordDigest[20];
SHA1((const unsigned char *)[password UTF8String], strlen([password UTF8String]), passwordDigest);
NSData *passwordDigest = [[password dataUsingEncoding:NSUTF8StringEncoding] sha1Hash];

// AES-128-cbc decrypt the data
AES_KEY aesKey;
AES_set_decrypt_key(passwordDigest, 128, &aesKey);
AES_set_decrypt_key([passwordDigest bytes], 128, &aesKey);

// Total length = encrypted length + IV
NSInteger totalLength = [self length];
@@ -312,3 +340,13 @@ - (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding
}

@end

#pragma mark -

uint32_t LimitUInt32(NSUInteger i) {
#if NSUIntegerMax > UINT32_MAX
return (i > UINT32_MAX)? UINT32_MAX : (uint32_t)i;
#else
return i;
#endif
}
@@ -0,0 +1,112 @@
//
// SPDataAdditionsTests.m
// sequel-pro
//
// Created by Max Lohrmann on 13.09.15.
// Copyright (c) 2015 Max Lohrmann. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// More info at <https://github.com/sequelpro/sequelpro>

#import <Cocoa/Cocoa.h>
#import <SenTestingKit/SenTestingKit.h>
#import "SPDataAdditions.h"
#import <errno.h>

@interface SPDataAdditionsTests : SenTestCase

- (void)testSha1Hash;

@end

@implementation SPDataAdditionsTests

- (void)testSha1Hash
{
//simple straight forward case
{
NSString *input = @"Hello World!";
unsigned char bytes[] = {0x2e,0xf7,0xbd,0xe6,0x08,0xce,0x54,0x04,0xe9,0x7d,0x5f,0x04,0x2f,0x95,0xf8,0x9f,0x1c,0x23,0x28,0x71};

STAssertTrue(memcmp([[[input dataUsingEncoding:NSUTF8StringEncoding] sha1Hash] bytes], bytes, 20) == 0, @"SHA1 simple hash from ASCII text");
}
// 16MB of all 8bit values
{
int bufSz = 16*1024*1024;
unsigned char *buf = malloc(bufSz);
for (int i = 0; i < bufSz; i++) {
buf[i] = (i % 0xff);
}
NSData *input = [NSData dataWithBytesNoCopy:buf length:bufSz];
NSString *result = @"25E05EB8E9E2B06036DF4026630FE01A19BF0F16";

STAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash from full ASCII range");
}
// empty hash
{
NSData *input = [NSData data];
NSString *result = @"DA39A3EE5E6B4B0D3255BFEF95601890AFD80709";

STAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash from empty data");
}
// test with > 4GB data (other code path)
// HFS+ does not support sparse files, so enable this one only if you have enough disk space.
{/*
// not everyone has 4GB RAM to spare and even then we probably won't be able to get
// them en-block, so we'll just use a file and mmap() to simulate that.
NSString *fileNameTpl = [NSTemporaryDirectory() stringByAppendingPathComponent:@"sha1test.XXXXXX"];
STAssertNotNil(fileNameTpl, @"No temporary directory available!?");
const char *cFileNameTpl = [fileNameTpl fileSystemRepresentation];
char *cFileName = malloc(strlen(cFileNameTpl)+1);
strcpy(cFileName, cFileNameTpl);
if(mkstemp(cFileName) == -1)
STFail(@"could not create temporary filename. errno=%d",errno);
FILE *fp = fopen(cFileName, "w+");
fputc(1, fp);
fseek(fp, UINT32_MAX, SEEK_CUR);
fputc(2, fp);
fflush(fp);
fclose(fp);
NSString *fileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:cFileName length:strlen(cFileName)];
NSData *input = [NSData dataWithContentsOfFile:fileName];
NSString *result = @"A31A151AFC12B0D66A4DBE917CB55CEAA0AD639E";
STAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash > 4gb data");
unlink(cFileName);
free(cFileName);
*/}
//utf8 string input
{
NSData *input = [@"føöbärbãz" dataUsingEncoding:NSUTF8StringEncoding];
NSString *result = @"8A8B6142281950CBB9B01C9DF0DADB0BDAE2D0E1";

STAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash of UTF-8 string");
}

}

@end
@@ -179,6 +179,9 @@
4DECC48F0EC2B436008D359E /* Sparkle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 4DECC3320EC2A170008D359E /* Sparkle.framework */; };
4DECC4910EC2B436008D359E /* Growl.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 4DECC3340EC2A170008D359E /* Growl.framework */; };
501B1D181728A3DA0017C92E /* SPCharsetCollationHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 501B1D171728A3DA0017C92E /* SPCharsetCollationHelper.m */; };
502D21F61BA50710000D4CE7 /* SPDataAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 502D21F51BA50710000D4CE7 /* SPDataAdditionsTests.m */; };
502D21F81BA50966000D4CE7 /* SPDataAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2C16D30FEBEDF10003993B /* SPDataAdditions.m */; };
502D21FA1BA509AF000D4CE7 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 502D21F91BA509AF000D4CE7 /* libcrypto.dylib */; };
503B02CA1AE82C5E0060CAB1 /* SPTableFilterParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */; };
503B02CF1AE95C2C0060CAB1 /* SPTableFilterParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02CE1AE95C2C0060CAB1 /* SPTableFilterParserTest.m */; };
503B02D11AE95DD40060CAB1 /* SPTableFilterParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */; };
@@ -891,6 +894,8 @@
4DECC3340EC2A170008D359E /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = Frameworks/Growl.framework; sourceTree = "<group>"; };
501B1D161728A3DA0017C92E /* SPCharsetCollationHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCharsetCollationHelper.h; sourceTree = "<group>"; };
501B1D171728A3DA0017C92E /* SPCharsetCollationHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCharsetCollationHelper.m; sourceTree = "<group>"; };
502D21F51BA50710000D4CE7 /* SPDataAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDataAdditionsTests.m; sourceTree = "<group>"; };
502D21F91BA509AF000D4CE7 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; };
5037F79A1B00148000733564 /* SPNamedNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SPNamedNode.h; sourceTree = "<group>"; };
503B02C81AE82C5E0060CAB1 /* SPTableFilterParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableFilterParser.h; sourceTree = "<group>"; };
503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableFilterParser.m; sourceTree = "<group>"; };
@@ -1272,6 +1277,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
502D21FA1BA509AF000D4CE7 /* libcrypto.dylib in Frameworks */,
1717F9DB1558114D0065C036 /* OCMock.framework in Frameworks */,
1717FA43155831600065C036 /* libicucore.dylib in Frameworks */,
50EA92671AB23EE1008D3C4F /* SPMySQL.framework in Frameworks */,
@@ -1936,6 +1942,7 @@
380F4EF40FC0B68F00B0BFD7 /* SPStringAdditionsTests.m */,
1760599E1336199D0098E162 /* SPMenuAdditionsTests.m */,
1798F1C2155018D4004B0AB8 /* SPMutableArrayAdditionsTests.m */,
502D21F51BA50710000D4CE7 /* SPDataAdditionsTests.m */,
);
name = "Category Additions";
sourceTree = "<group>";
@@ -2349,6 +2356,7 @@
2A37F4C3FDCFA73011CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
502D21F91BA509AF000D4CE7 /* libcrypto.dylib */,
1058C7A6FEA54F5311CA2CBB /* Linked Frameworks */,
);
name = Frameworks;
@@ -3052,6 +3060,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
502D21F81BA50966000D4CE7 /* SPDataAdditions.m in Sources */,
502D21F61BA50710000D4CE7 /* SPDataAdditionsTests.m in Sources */,
503B02D21AE95E010060CAB1 /* SPConstants.m in Sources */,
503B02D11AE95DD40060CAB1 /* SPTableFilterParser.m in Sources */,
503B02CF1AE95C2C0060CAB1 /* SPTableFilterParserTest.m in Sources */,

0 comments on commit fa2cd0b

Please sign in to comment.
You can’t perform that action at this time.