I am trying to make an app for my school that interacts with PowerSchool, a software that allows user's to view their grades, teachers, schedules, and much more. I found a library for the basics of interacting with PowerSchool written in PHP and have been trying to write it in objective c for the past week. It seems the issue is how I create an HMAC (MD5) with the user's password. Either I am using a hex digest rather than a digest, not sure. The error I get back from the server is an odd number of characters.
Here is the link to the PHP library class I am trying to re-create:
https://github.com/horvste/powerapi-php/blob/master/src/PowerAPI/Core.php
Here is my code in my test project,
Command line main class:
https://gist.github.com/anonymous/c40cdd99a826c06073aa
NSString Category Implementation file:
#import "NSString+MyAdditions.h"
#implementation NSString (MyAdditions)
- (NSString *) hmacMD5WithData: (NSString *) data
{
const char *cKey = [self cStringUsingEncoding:NSASCIIStringEncoding];
const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];
const unsigned int blockSize = 64;
char ipad[blockSize], opad[blockSize], keypad[blockSize];
unsigned int keyLen = strlen(cKey);
CC_MD5_CTX ctxt;
if(keyLen > blockSize)
{
//CC_MD5(cKey, keyLen, keypad);
CC_MD5_Init(&ctxt);
CC_MD5_Update(&ctxt, cKey, keyLen);
CC_MD5_Final((unsigned char *)keypad, &ctxt);
keyLen = CC_MD5_DIGEST_LENGTH;
}
else
{
memcpy(keypad, cKey, keyLen);
}
memset(ipad, 0x36, blockSize);
memset(opad, 0x5c, blockSize);
int i;
for(i = 0; i < keyLen; i++)
{
ipad[i] ^= keypad[i];
opad[i] ^= keypad[i];
}
CC_MD5_Init(&ctxt);
CC_MD5_Update(&ctxt, ipad, blockSize);
CC_MD5_Update(&ctxt, cData, strlen(cData));
unsigned char md5[CC_MD5_DIGEST_LENGTH];
CC_MD5_Final(md5, &ctxt);
CC_MD5_Init(&ctxt);
CC_MD5_Update(&ctxt, opad, blockSize);
CC_MD5_Update(&ctxt, md5, CC_MD5_DIGEST_LENGTH);
CC_MD5_Final(md5, &ctxt);
const unsigned int hex_len = CC_MD5_DIGEST_LENGTH*2+2;
char hex[hex_len];
for(i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
{
snprintf(&hex[i*2], hex_len-i*2, "%02x", md5[i]);
}
NSData *HMAC = [[NSData alloc] initWithBytes:hex length:strlen(hex)];
NSString *hash = [HMAC base64EncodedStringWithOptions:0];
return hash;
}
#end
Thank you for taking the time to look at this issue!
First, don't build your own HMAC routine here. Use CCHmac. It's built-in and handles HMAC+MD5 correctly.
If at all possible, I recommend going to the API documentation rather than trying to reverse engineer another code base. There are lots of little things going on in the PHP that you may be overlooking; an API doc should explain all of those.
If the PHP code is the only reference you have, then you should break down each piece and see where it's going wrong. For instance, verify that you are getting the auth data in the same form. Then confirm that each program, given the same auth data generates the same HMAC. Then confirm that given the same HMAC, each program generates the same response. Etc. Somewhere you are doing something differently. Make sure that you're using Base64 vs raw data in the same places (PHP devs tend to treat Base64 strings as though they were actually raw data, which causes confusion when coming over to ObjC).
And of course you should examine the server logs to validate that your final request matches the PHP requests.
Related
I'm need to wipe an NSString (It is an existing SKD that I will probably wont be able to change method signatures in as clients are working with it already.
For Security reasons that emerged right now we Need to wipe the bytes in the NSStrings we have.
I found the code below that suppose to give me a pointer to the CFString underlying pointer and from there I supposably can use memset to wipe the data.
(unsigned char*) CFStringGetCStringPtr((CFStringRef) password, CFStringGetSystemEncoding());
I am not setting a specific encoding for the the NSString.
Is there a way to get the NSString encoding?
I've tried the code below and it didn't work:
unsigned char *charPin = (unsigned char*) CFStringGetCStringPtr((CFStringRef) pin, CFStringGetSystemEncoding());
if (charPin == NULL) {
for (int i = 0; i < kCFStringEncodingShiftJIS_X0213_00; ++i) {
charPin = (unsigned char*) CFStringGetCStringPtr((CFStringRef) pin, (CFStringEncodings)i);
if (charPin != NULL) {
break;
}
}
}
memset(charPin, 0, [pin length]);
pin = nil;
kCFStringEncodingShiftJIS_X0213_00 is the last member of the CFStringEncodings enum
charPin is still NULL after looking for all the possible encodings and this is kind of impossible, Unless i'm missing something.
Any ideas or comments?
Please don't say that I need to change the NSString to something else...
10x
I would to log a binary hash representation in the console, using an hex or ascii representation. The algorithm is MD5, so the function is CC_MD5
I get the binary hash representation via a Theos tweak, which is working well.
EDIT: this tweak intercept the CC_MD5 call. The call is implemented in the method described below. When CC_MD5 is called, replaced_CC_MD5 intercept the call.
The app tested, is a simple app which i made myself and it's using this method to calculate MD5 Hash:
- (NSString *) md5:(NSString *) input
{
const char *cStr = [input UTF8String];
unsigned char digest[16];
CC_MD5( cStr, strlen(cStr), digest ); // This is the md5 call
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[output appendFormat:#"%02x", digest[i]];
return output;
}
The hashing it's ok, and the app returns to me the correct hash for the input
input = prova
MD5 Digest = 189bbbb00c5f1fb7fba9ad9285f193d1
The function in my Theos Tweak where i manipulate the CC_MD5 function is
EDIT: where data would be cStr, len would be strlen(cStr) and md would be digest.
static unsigned char * replaced_CC_MD5(const void *data, CC_LONG len, unsigned char *md) {
CC_LONG dataLength = (size_t) len;
NSLog(#"==== START CC_MD5 HOOK ====");
// hex of digest
NSData *dataDigest = [NSData dataWithBytes:(const void *)md length:(NSUInteger)CC_MD5_DIGEST_LENGTH];
NSLog(#"%#", dataDigest);
// hex of string
NSData *dataString = [NSData dataWithBytes:(const void *)data length:(NSUInteger)dataLength];
NSLog(#"%#", dataString);
NSLog(#"==== END CC_MD5 HOOK ====");
return original_CC_MD5(data, len, md);
}
The log of dataString it's ok: 70726f76 61 which is the HEX representation of prova
The log of dataDigest is e9aa0800 01000000 b8c00800 01000000 which is, if i understood, the binary hash representation.
How can i convert this representation to have the MD5 Hash digest?
In replaced_CC_MD5 you are displaying md before the call to original_CC_MD5 which sets its value. What you are seeing is therefore random data (or whatever was last stored in md).
Move the call to original_CC_MD5 to before the display statement and you should see the value you expect. (You'll of course need to save the result of the call in a local so you can return the value in the return statement.)
I'm working on a custom bluetooth product, the manufacturer has embeded data in the advertisement packet. How do I effectively parse this data so it's usable within an iOS app?
I'm currently grabbing the data from the NSDictionary as follows:
NSData *rawData = [advertisement objectForKey:#"kCBAdvDataManufacturerData"];
The data in the packet is structured like so:
uint8_t compId[2];
uint8_t empty[6];
uint8_t temperature[2];
uint8_t rampRate[2];
uint8_t dutyFactor[2];
uint8_t alarms[2];
uint8_t statusFlag;
uint8_t speedRpm[2];
uint8_t vib[2];
uint8_t deviceTypeId;
uint8_t radioStatus;
uint8_t cycleTimer[2];
uint8_t batteryLevel;
My first thought was to convert it to a string and parse out the data that I need, but this seems slow and really inefficient. There has to be a standard way developers deal with this. I'm still pretty green when it comes to bitwise operators. All the data is formatted in little endian.
Certainly don't convert it to a string, as it isn't one, and you'll have issues with encoding.
Start by checking that the length of the data matches what you're expecting (26 bytes)
Use the bytes method to get a pointer to the data
Add a function or method to combine two bytes into a 16-bit integer. You'll have to find out if those 2-byte fields are signed or unsigned.
Something along these lines:
- (int)getWordFromBuffer:(const unsigned char *)bytes atOffset:(int) offset
{
return (int)bytes[offset] | (bytes[offset+1] << 8);
}
- (NSDictionary *)decodeData:(NSData *)data
{
if (data.length != 26)
{
NSLog(#"wrong length %d instead of 26", data.length);
return nil;
}
const unsigned char *bytes = (unsigned char *)data.bytes;
return
#{
#"compId": #([self getWordFromBuffer:bytes atOffset:0]),
#"temperature": #([self getWordFromBuffer:bytes atOffset:8]),
#"rampRate": #([self getWordFromBuffer:bytes atOffset:10]),
....
};
}
At the moment I'm having an issue with producing a public key for a given Google Maps Web Services API query.
The documentation stipulates that the signature must be produced with a modified base-64 HMAC-SHA1 hash, on the path and query part of the URL.
However using this function, and testing it with this tool shows that it isn't working correctly.
+ (NSString *) hmac:(NSString *)data withKey:(NSString *)key{
const char *cKey = [key cStringUsingEncoding:NSUTF8StringEncoding];
const char *cData = [data cStringUsingEncoding:NSUTF8StringEncoding];
unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
NSString *hash = [HMAC base64EncodedStringWithOptions:0];
hash = [hash stringByReplacingOccurrencesOfString:#"+" withString:#"-"];
hash = [hash stringByReplacingOccurrencesOfString:#"/" withString:#"_"];
return hash;
}
I am calling this function where data has been precent encoded;
[urlString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
Where am I going wrong? Any help greatly appreciated.
You can use this for your url signing : https://github.com/youssman/GMUrlSigner
I am interested in your feedback and comments ;-)
Google has a sample Objective C code - is there any particular reason that you are not using them and some of the google functions in your code? In particular the equivalence of CKey is not plain 7 bit asCII (as coded in your sample) but Base64 (rfc4648Base64WebsafeStringEncoding). For the unfamiliar, Base64 is typically uses in mime Mail to allow attachments to go through plain text email years ago (prior to HTML Mail) problem is if google thinks that your key is base 64 and it is not, the decoding will change your key - example base64 abcedfg = iy in ascii. ... so google is not getting the correct decoding key from your URL.
There are other differences but I guess most of them are just format.
I have a binary file I've loaded using an NSData object. Is there a way to locate a sequence of characters, 'abcd' for example, within that binary data and return the offset without converting the entire file to a string? Seems like it should be a simple answer, but I'm not sure how to do it. Any ideas?
I'm doing this on iOS 3 so I don't have -rangeOfData:options:range: available.
I'm going to award this one to Sixteen Otto for suggesting strstr. I went and found the source code for the C function strstr and rewrote it to work on a fixed length Byte array--which incidentally is different from a char array as it is not null terminated. Here is the code I ended up with:
- (Byte*)offsetOfBytes:(Byte*)bytes inBuffer:(const Byte*)buffer ofLength:(int)len;
{
Byte *cp = bytes;
Byte *s1, *s2;
if ( !*buffer )
return bytes;
int i = 0;
for (i=0; i < len; ++i)
{
s1 = cp;
s2 = (Byte*)buffer;
while ( *s1 && *s2 && !(*s1-*s2) )
s1++, s2++;
if (!*s2)
return cp;
cp++;
}
return NULL;
}
This returns a pointer to the first occurrence of bytes, the thing I'm looking for, in buffer, the byte array that should contain bytes.
I call it like this:
// data is the NSData object
const Byte *bytes = [data bytes];
Byte* index = [self offsetOfBytes:tag inBuffer:bytes ofLength:[data length]];
Convert your substring to an NSData object, and search for those bytes in the larger NSData using rangeOfData:options:range:. Make sure that the string encodings match!
On iPhone, where that isn't available, you may have to do this yourself. The C function strstr() will give you a pointer to the first occurrence of a pattern within the buffer (as long as neither contain nulls!), but not the index. Here's a function that should do the job (but no promises, since I haven't tried actually running it...):
- (NSUInteger)indexOfData:(NSData*)needle inData:(NSData*)haystack
{
const void* needleBytes = [needle bytes];
const void* haystackBytes = [haystack bytes];
// walk the length of the buffer, looking for a byte that matches the start
// of the pattern; we can skip (|needle|-1) bytes at the end, since we can't
// have a match that's shorter than needle itself
for (NSUInteger i=0; i < [haystack length]-[needle length]+1; i++)
{
// walk needle's bytes while they still match the bytes of haystack
// starting at i; if we walk off the end of needle, we found a match
NSUInteger j=0;
while (j < [needle length] && needleBytes[j] == haystackBytes[i+j])
{
j++;
}
if (j == [needle length])
{
return i;
}
}
return NSNotFound;
}
This runs in something like O(nm), where n is the buffer length, and m is the size of the substring. It's written to work with NSData for two reasons: 1) that's what you seem to have in hand, and 2) those objects already encapsulate both the actual bytes, and the length of the buffer.
If you're using Snow Leopard, a convenient way is the new -rangeOfData:options:range: method in NSData that returns the range of the first occurrence of a piece of data. Otherwise, you can access the NSData's contents yourself using its -bytes method to perform your own search.
I had the same problem.
I solved it doing the other way round, compared to the suggestions.
first, I reformat the data (assume your NSData is stored in var rawFile) with:
NSString *ascii = [[NSString alloc] initWithData:rawFile encoding:NSAsciiStringEncoding];
Now, you can easily do string searches like 'abcd' or whatever you want using the NSScanner class and passing the ascii string to the scanner. Maybe this is not really efficient, but it works until the -rangeOfData method will be available for iPhone also.