Reputation: 4905
I found AES Encryption and Decryption sample from http://pastie.org/pastes/297563/text
It works very fine except a scenario on decryption. when i encrypt user email string which has a lengthy string([email protected]), no issues on encryption and decryption. After encryption i store in a plist file and then reading it later for decryption. But, when i encrypt email string which has short lengthy string (for ex: [email protected]), encryption is fine, but when i try to decrypt the string and place it in a label, i found decryption is giving error like "Problem with encipherment ccStatus == -4301"
UDPATE I found this decryption issue is happening only when the string length is "16", otherwise it works always fine. Any help please?
Please find the code below. This is the code for encryption and decryption.
import "CryptoHelper.h"
#define LOGGING_FACILITY(X, Y) \
if(!(X)) { \
NSLog(Y); \
}
#define LOGGING_FACILITY1(X, Y, Z) \
if(!(X)) { \
NSLog(Y, Z); \
}
@interface CryptoHelper(Private)
- (NSData *)doCipher:(NSData *)plainText key:(NSData *)theSymmetricKey context:(CCOperation)encryptOrDecrypt padding:(CCOptions *)pkcs7;
- (NSString *)base64EncodeData:(NSData*)dataToConvert;
- (NSData*)base64DecodeString:(NSString *)string;
@end
@implementation CryptoHelper
static CryptoHelper *MyCryptoHelper = nil;
const uint8_t kKeyBytes[] = "abcdefgh0123456"; // Must be 16 bytes
static CCOptions pad = 0;
static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- (NSString*)encryptString:(NSString*)string
{
NSRange fullRange;
fullRange.length = [string length];
fullRange.location = 0;
uint8_t buffer[[string length]];
[string getBytes:&buffer maxLength:[string length] usedLength:NULL encoding:NSUTF8StringEncoding options:0 range:fullRange remainingRange:NULL];
NSData *plainText = [NSData dataWithBytes:buffer length:[string length]];
NSData *encryptedResponse = [self doCipher:plainText key:symmetricKey context:kCCEncrypt padding:&pad];
return [self base64EncodeData:encryptedResponse];
}
- (NSString*)decryptString:(NSString*)string
{
NSLog(@"string: %@", string);
NSData *decryptedResponse = [self doCipher:[self base64DecodeString:string] key:symmetricKey context:kCCDecrypt padding:&pad];
NSString *result = [NSString stringWithFormat:@"decryptedResponse: %@", decryptedResponse];
NSLog(@"decryptedResponse: %@", result);
return [NSString stringWithCString:[decryptedResponse bytes] length:[decryptedResponse length]];
}
- (NSData *)doCipher:(NSData *)plainText key:(NSData *)theSymmetricKey context:(CCOperation)encryptOrDecrypt padding:(CCOptions *)pkcs7
{
CCCryptorStatus ccStatus = kCCSuccess;
// Symmetric crypto reference.
CCCryptorRef thisEncipher = NULL;
// Cipher Text container.
NSData * cipherOrPlainText = nil;
// Pointer to output buffer.
uint8_t * bufferPtr = NULL;
// Total size of the buffer.
size_t bufferPtrSize = 0;
// Remaining bytes to be performed on.
size_t remainingBytes = 0;
// Number of bytes moved to buffer.
size_t movedBytes = 0;
// Length of plainText buffer.
size_t plainTextBufferSize = 0;
// Placeholder for total written.
size_t totalBytesWritten = 0;
// A friendly helper pointer.
uint8_t * ptr;
// Initialization vector; dummy in this case 0's.
uint8_t iv[kCCBlockSizeAES128];
memset((void *) iv, 0x0, (size_t) sizeof(iv));
LOGGING_FACILITY(plainText != nil, @"PlainText object cannot be nil." );
LOGGING_FACILITY(theSymmetricKey != nil, @"Symmetric key object cannot be nil." );
LOGGING_FACILITY(pkcs7 != NULL, @"CCOptions * pkcs7 cannot be NULL." );
LOGGING_FACILITY([theSymmetricKey length] == kCCKeySizeAES128, @"Disjoint choices for key size." );
plainTextBufferSize = [plainText length];
LOGGING_FACILITY(plainTextBufferSize > 0, @"Empty plaintext passed in." );
// We don't want to toss padding on if we don't need to
if(encryptOrDecrypt == kCCEncrypt)
{
if(*pkcs7 != kCCOptionECBMode)
{
if((plainTextBufferSize % kCCBlockSizeAES128) == 0)
{
*pkcs7 = 0x0000;
}
else
{
*pkcs7 = kCCOptionPKCS7Padding;
}
}
}
else if(encryptOrDecrypt != kCCDecrypt)
{
LOGGING_FACILITY1( 0, @"Invalid CCOperation parameter [%d] for cipher context.", *pkcs7 );
}
// Create and Initialize the crypto reference.
ccStatus = CCCryptorCreate( encryptOrDecrypt,
kCCAlgorithmAES128,
*pkcs7,
(const void *)[theSymmetricKey bytes],
kCCKeySizeAES128,
(const void *)iv,
&thisEncipher
);
LOGGING_FACILITY1( ccStatus == kCCSuccess, @"Problem creating the context, ccStatus == %d.", ccStatus );
// Calculate byte block alignment for all calls through to and including final.
bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true);
// Allocate buffer.
bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t) );
// Zero out buffer.
memset((void *)bufferPtr, 0x0, bufferPtrSize);
// Initialize some necessary book keeping.
ptr = bufferPtr;
// Set up initial size.
remainingBytes = bufferPtrSize;
// Actually perform the encryption or decryption.
ccStatus = CCCryptorUpdate( thisEncipher,
(const void *) [plainText bytes],
plainTextBufferSize,
ptr,
remainingBytes,
&movedBytes
);
LOGGING_FACILITY1( ccStatus == kCCSuccess, @"Problem with CCCryptorUpdate, ccStatus == %d.", ccStatus );
// Handle book keeping.
ptr += movedBytes;
remainingBytes -= movedBytes;
totalBytesWritten += movedBytes;
// Finalize everything to the output buffer.
ccStatus = CCCryptorFinal( thisEncipher,
ptr,
remainingBytes,
&movedBytes
);
totalBytesWritten += movedBytes;
if(thisEncipher)
{
(void) CCCryptorRelease(thisEncipher);
thisEncipher = NULL;
}
LOGGING_FACILITY1( ccStatus == kCCSuccess, @"Problem with encipherment ccStatus == %d", ccStatus );
cipherOrPlainText = [NSData dataWithBytes:(const void *)bufferPtr length:(NSUInteger)totalBytesWritten];
if(bufferPtr) free(bufferPtr);
return cipherOrPlainText;
}
#pragma mark -
#pragma mark Base64 Encode/Decoder
- (NSString *)base64EncodeData:(NSData*)dataToConvert
{
if ([dataToConvert length] == 0)
return @"";
char *characters = malloc((([dataToConvert length] + 2) / 3) * 4);
if (characters == NULL)
return nil;
NSUInteger length = 0;
NSUInteger i = 0;
while (i < [dataToConvert length])
{
char buffer[3] = {0,0,0};
short bufferLength = 0;
while (bufferLength < 3 && i < [dataToConvert length])
buffer[bufferLength++] = ((char *)[dataToConvert bytes])[i++];
// Encode the bytes in the buffer to four characters, including padding "=" characters if necessary.
characters[length++] = encodingTable[(buffer[0] & 0xFC) >> 2];
characters[length++] = encodingTable[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xF0) >> 4)];
if (bufferLength > 1)
characters[length++] = encodingTable[((buffer[1] & 0x0F) << 2) | ((buffer[2] & 0xC0) >> 6)];
else characters[length++] = '=';
if (bufferLength > 2)
characters[length++] = encodingTable[buffer[2] & 0x3F];
else characters[length++] = '=';
}
return [[[NSString alloc] initWithBytesNoCopy:characters length:length encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease];
}
- (NSData*)base64DecodeString:(NSString *)string
{
if (string == nil)
[NSException raise:NSInvalidArgumentException format:nil];
if ([string length] == 0)
return [NSData data];
static char *decodingTable = NULL;
if (decodingTable == NULL)
{
decodingTable = malloc(256);
if (decodingTable == NULL)
return nil;
memset(decodingTable, CHAR_MAX, 256);
NSUInteger i;
for (i = 0; i < 64; i++)
decodingTable[(short)encodingTable[i]] = i;
}
const char *characters = [string cStringUsingEncoding:NSASCIIStringEncoding];
if (characters == NULL) // Not an ASCII string!
return nil;
char *bytes = malloc((([string length] + 3) / 4) * 3);
if (bytes == NULL)
return nil;
NSUInteger length = 0;
NSUInteger i = 0;
while (YES)
{
char buffer[4];
short bufferLength;
for (bufferLength = 0; bufferLength < 4; i++)
{
if (characters[i] == '\0')
break;
if (isspace(characters[i]) || characters[i] == '=')
continue;
buffer[bufferLength] = decodingTable[(short)characters[i]];
if (buffer[bufferLength++] == CHAR_MAX) // Illegal character!
{
free(bytes);
return nil;
}
}
if (bufferLength == 0)
break;
if (bufferLength == 1) // At least two characters are needed to produce one byte!
{
free(bytes);
return nil;
}
// Decode the characters in the buffer to bytes.
bytes[length++] = (buffer[0] << 2) | (buffer[1] >> 4);
if (bufferLength > 2)
bytes[length++] = (buffer[1] << 4) | (buffer[2] >> 2);
if (bufferLength > 3)
bytes[length++] = (buffer[2] << 6) | buffer[3];
}
realloc(bytes, length);
return [NSData dataWithBytesNoCopy:bytes length:length];
}
#pragma mark -
#pragma mark Singleton methods
- (id)init
{
if(self = [super init])
{
symmetricKey = [[NSData dataWithBytes:kKeyBytes length:sizeof(kKeyBytes)] retain];
}
return self;
}
+ (CryptoHelper*)sharedInstance
{
@synchronized(self)
{
if (MyCryptoHelper == nil)
{
[[self alloc] init];
}
}
return MyCryptoHelper;
}
+ (id)allocWithZone:(NSZone *)zone
{
@synchronized(self)
{
if (MyCryptoHelper == nil)
{
MyCryptoHelper = [super allocWithZone:zone];
return MyCryptoHelper; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain
{
return self;
}
- (unsigned)retainCount
{
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release
{
//do nothing
}
- (id)autorelease
{
return self;
}
@end
The below code is the caller of the above,
-(NSString *) getUsername
{
NSString *usernameString = NULL;
// Data.plist code
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:LOGIN_PLIST];
// check to see if Data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle
plistPath = [[NSBundle mainBundle] pathForResource:@"LoginInfo" ofType:@"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property liost into dictionary object
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!temp)
{
//NSLog(@"Error reading plist: %@, format: %lu", errorDesc, format);
}
else
{
NSString *nameStr=[temp objectForKey:@"Username"];
NSLog(@"nameStr: %@",nameStr);
// do AES128 decryption
CryptoHelper *pCrypto = [CryptoHelper sharedInstance];
usernameString = [pCrypto decryptString:nameStr];
// usernameString becomes empty after when encryption and trying to decrypt.
NSLog(@"usernameString: %@: usernameString length: %ld", usernameString, [usernameString length] );
}
return usernameString;
}
Upvotes: 3
Views: 2565
Reputation: 4773
Your code is based upon the SecKeyWrapper class from Apple's 'CryptoExercise' example. I just ran into the same error message when using the doCipher method.
'-4301' is in this case the value of the ccStatus variable after the call of CCCryptoFinal, which returns a value of type CCryptorStatus. It is defined in CommonCryptor.h as follows:
enum {
kCCSuccess = 0,
kCCParamError = -4300,
kCCBufferTooSmall = -4301,
kCCMemoryFailure = -4302,
kCCAlignmentError = -4303,
kCCDecodeError = -4304,
kCCUnimplemented = -4305
};
So somehow the buffer created in the doCipher method is too small, which is odd, because it's size is determined via CCCryptorGetOutputLength, just as the documentation of CCCryptorFinal suggests:
@result kCCBufferTooSmall indicates insufficent space in the dataOut buffer.
The caller can use CCCryptorGetOutputLength() to determine the required
output buffer size in this case. The operation can be retried; no state is
lost when this is returned.
I've got the hunch that the SecKeyWrapper class from the Apple example is not bug free. I'll see if I can solve the problem or find another way to do AES encryption on iPhone. There's an explanation and example code in Rob Napier's and Mugunth Kumar's book 'iOS 5 programming - Pushing the limits' that I'll try out. Also, the authors recommend two WWDC Sessions, available at developer.apple.com.
EDIT:
I just found the error. It's within the doCipher method. Simply substitute the following lines:
// We don't want to toss padding on if we don't need to
if(encryptOrDecrypt == kCCEncrypt)
{
if(*pkcs7 != kCCOptionECBMode)
{
if((plainTextBufferSize % kCCBlockSizeAES128) == 0)
{
*pkcs7 = 0x0000;
}
else
{
*pkcs7 = kCCOptionPKCS7Padding;
}
}
}
else if(encryptOrDecrypt != kCCDecrypt)
{
LOGGING_FACILITY1( 0, @"Invalid CCOperation parameter [%d] for cipher context.", *pkcs7 );
}
with the following lines:
// check for valid context parameter
if (encryptOrDecrypt != kCCEncrypt && encryptOrDecrypt != kCCDecrypt) {
LOGGING_FACILITY1( 0, @"Invalid CCOperation parameter [%d] for cipher context.", encryptOrDecrypt );
}
As Ortwin pointed out below, you furthermore have to replace the third parameter in the subsequent CCCryptorCreate call to be always kCCOptionPKCS7Padding:
// Create and Initialize the crypto reference.
ccStatus = CCCryptorCreate( encryptOrDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
(const void *)[theSymmetricKey bytes],
kCCKeySizeAES128,
(const void *)iv,
&thisEncipher
);
Upvotes: 1
Reputation: 54113
I found that it works for me when I replace
// We don't want to toss padding on if we don't need to
if(encryptOrDecrypt == kCCEncrypt)
{
if(*pkcs7 != kCCOptionECBMode)
{
if((plainTextBufferSize % kCCBlockSizeAES128) == 0)
{
*pkcs7 = 0x0000;
}
else
{
*pkcs7 = kCCOptionPKCS7Padding;
}
}
}
else if(encryptOrDecrypt != kCCDecrypt)
{
LOGGING_FACILITY1( 0, @"Invalid CCOperation parameter [%d] for cipher context.", *pkcs7 );
}
// Create and Initialize the crypto reference.
ccStatus = CCCryptorCreate( encryptOrDecrypt,
kCCAlgorithmAES128,
*pkcs7,
(const void *)[theSymmetricKey bytes],
kCCKeySizeAES128,
(const void *)iv,
&thisEncipher
);
by
// We don't want to toss padding on if we don't need to
if (encryptOrDecrypt != kCCEncrypt && encryptOrDecrypt != kCCDecrypt) {
LOGGING_FACILITY1( 0, @"Invalid CCOperation parameter [%d] for cipher context.", *pkcs7 );
}
// Create and Initialize the crypto reference.
ccStatus = CCCryptorCreate( encryptOrDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
(const void *)[theSymmetricKey bytes],
kCCKeySizeAES128,
(const void *)iv,
&thisEncipher
);
Essentially, I always set the kCCOptionPKCS7Padding
option for CCCryptorCreate
no matter whether encrypting or decrypting and no matter what text length.
Note that my case is limited and I didn't do any tests whether this works under all circumstances! NO GUARANTEES!
Upvotes: 0