Please forgive my clumsiness, I'm new to Stackoverflow, C#, and Objective C.
In a nutshell, I'm trying to do what is answered in this question, but in PHP:
How to authenticate the GKLocalPlayer on my 'third party server'?
Hopefully this will also help other PHP devs working on the same thing.
I'm using Unity (Unity3D) and PHP server-side. I've got Objective C properly connecting to GameCenter and returning data via a call to generateIdentityVerificationSignatureWithCompletionHandler.
Unfortunately, I can't figure out what I'm doing wrong to validate the SHA1 hash. I've been working on this for the past week, trying all sorts of things, but with no luck.
I'm trying three different ways of making the SHA1 hash (shown below). Once in Objective C, another in Unity's C#, and finally a third time on my server in PHP. The Objective C and C# SHA1 hashes end up identical. However, the PHP one does not match them. And none validate against Apple's public cert and signature.
Admittedly, I could be misunderstanding something fundamental. It would be a huge step to at least get the Objective C and C# hashes to validate.
Thanks.
Objective C code:
[localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
NSDictionary *params = #{#"public_key_url": [publicKeyUrl absoluteString],
#"timestamp": [NSString stringWithFormat:#"%llu", timestamp],
#"signature": [signature base64EncodedStringWithOptions:0],
#"salt": [salt base64EncodedStringWithOptions:0],
#"player_id": [GKLocalPlayer localPlayer].playerID,
#"app_bundle_id": [[NSBundle mainBundle] bundleIdentifier]};
// Build hash using iOS...
NSMutableData *payload = [[NSMutableData alloc] init];
[payload appendData:[[GKLocalPlayer localPlayer].playerID dataUsingEncoding:NSASCIIStringEncoding]];
[payload appendData:[[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding:NSASCIIStringEncoding]];
uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
[payload appendBytes:×tampBE length:sizeof(timestampBE)];
[payload appendData:salt];
uint8_t sha1HashDigest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1([payload bytes], [payload length], sha1HashDigest);
// Convert to hex string so it can be sent to Unity's C# then to the PHP webserver...
NSString *sIOSHash = [self stringFromDigest:sha1HashDigest length:CC_SHA1_DIGEST_LENGTH];
// END - Build hash using iOS
// Build string to send to Unity's C#...
NSMutableString * data = [[NSMutableString alloc] init];
[data appendString:params[#"public_key_url"]];
[data appendString:#","];
[data appendString:params[#"timestamp"]];
[data appendString:#","];
[data appendString:params[#"signature"]];
[data appendString:#","];
[data appendString:params[#"salt"]];
[data appendString:#","];
[data appendString:params[#"player_id"]];
[data appendString:#","];
[data appendString:params[#"app_bundle_id"]];
[data appendString:#","];
[data appendString:sIOSHash];
// END - Build string to send to Unity's C#.
// Send string to Unity's C# for parsing and sending off to PHP webserver.
NSString *str = [[data copy] autorelease];
UnitySendMessage("GameCenterManager", "onAuthenticateLocalPlayer", [ISNDataConvertor NSStringToChar:str]);
}];
// Helper method to convert uint8_t into a hex string for sending to the webserver.
- (NSString *)stringFromDigest:(uint8_t *)digest length:(int)length {
NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:length * 2];
for (int i = 0; i < length; i++) {
[ms appendFormat: #"%02x", (int)digest[i]];
}
return [ms copy];
}
What follows is the C# code (within Unity3D) to generate the second version of the SHA1 hash.
These variables are all sent to Unity from the iOS code (above), and came in as strings: player_id, app_bundle_id, timestamp, salt.
(I'm not showing any Unity3D C# code to send to my server. But I'm using the WWWForm and AddField to send it. Nor am I showing the "bridge" code to move data from Objective C to C#.)
var sha1 = new SHA1Managed();
var data = new List<byte>();
data.AddRange(Encoding.UTF8.GetBytes(player_id));
data.AddRange(Encoding.UTF8.GetBytes(app_bundle_id));
data.AddRange(ToBigEndian(Convert.ToUInt64(timestamp)));
data.AddRange(Convert.FromBase64String(salt));
var sig = data.ToArray();
public static string CSharpHash = ToHex(sha1.ComputeHash(sig), false);
This last code block is my server-side PHP that receives the data from the client, validates the public cert, and then tries to verify the hash against it and the signature. That last part is where I am stuck.
/*
Sample data as received within the PHP (all strings):
$public_cert_url eg: https://sandbox.gc.apple.com/public-key/gc-sb.cer
$timestamp eg: 00-00-01-47-12-9C-16-D4 [derived from: 1404766525140]
$signature eg: EGc8J9D7SdZ0qq2xl2XLz2[lots more...]
$salt eg: LDfyIQ==
$player_id eg: G:[#########]
$app_bundle_id eg: com.[mydomain].[myapp]
$sIOSHash eg: 00032b9416315c8298b5a6e7f5d9dec71bd5ace2 [The C# and Objective C code both generate the same hash.]
$CSharpHash eg: 00032b9416315c8298b5a6e7f5d9dec71bd5ace2
*/
// Verify the public cert.
// As far as I understand, PHP's openssl_pkey_get_public() cannot read raw
// cer data, so I download and convert to PEM. Optimize later.
$fp = fopen("temp.cer", "w"); // Open file for writing.
$header[] = "Content-Type: application/pkix-cert";
$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_URL, $public_cert_url);
curl_setopt($curl, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_FILE, $fp);
curl_exec($curl);
curl_close($curl);
fclose($fp);
shell_exec("openssl x509 -inform der -in temp.cer -out temp.pem"); // Convert to PEM.
$pub_cert = file_get_contents("temp.pem");
$sKey = openssl_pkey_get_public($pub_cert); // Validate PEM file here.
If( $sKey === False ) echo "pkey bad";
// This ^^ works.
// This is where I am stuck:
// Verify the data from the client against the signature from the client
// and the downloaded public key.
// First, try to verify against a hash created within PHP:
$iResult = openssl_verify(
sha1($player_id . $app_bundle_id . $timestamp . $salt),
$signature,
$pub_cert,
OPENSSL_ALGO_SHA1);
If( $iResult != 1 ) echo "not valid PHP hash!\n";
// Second, see if it will verify by using the hash created in.
$iResult = openssl_verify($sIOSHash, $signature, $pub_cert, OPENSSL_ALGO_SHA1);
If( $iResult != 1 ) echo "not valid sIOSHash hash!\n";
// Finally, does the C# has verify?
$iResult = openssl_verify($CSharpHash, $signature, $pub_cert, OPENSSL_ALGO_SHA1);
If( $iResult != 1 ) echo "not valid CSharpHash hash!\n";
// None of these ^^ ever validate.
Update: Jul 9 2014
I got it to validate the data by not doing SHA1 on it. I was confused by the Apple documentation (https://developer.apple.com/library/prerelease/ios/documentation/GameKit/Reference/GKLocalPlayer_Ref/index.html#//apple_ref/occ/instm/GKLocalPlayer/generateIdentityVerificationSignatureWithCompletionHandler:). Specifically #7 which says: "Generate a SHA-1 hash value for the buffer."
I removed ALL the C# code (to try and generate the payload) and now only use the Objective C.
Modified as follows:
NSMutableData *payload = [[NSMutableData alloc] init];
[payload appendData:[[GKLocalPlayer localPlayer].playerID dataUsingEncoding:NSUTF8StringEncoding]];
[payload appendData:[[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding:NSUTF8StringEncoding]];
uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
[payload appendBytes:×tampBE length:sizeof(timestampBE)];
[payload appendData:salt];
NSString *siOSData = [payload base64EncodedStringWithOptions:0];
Notice the removal of SHA1.
I gave up on trying to create the payload in PHP. I tried many variations of pack, conversions, upgrading my server to 64 bit, etc. But I think (please correct me if I am wrong) that since I am transmitting the exact same data from the client as makes up the payload, it should be ok.
Note to Apple:
PLEASE implement OAuth 2.0.
I also figured out how to validate the Apple cer file without wasting processing on saving to a file. As follows:
// Get data from client. I urlencoded it before sending. So need to urldecode now.
// The payload is in "iosdata" and it, along with the signature, both need to be
// base64_decoded.
$sIOSData = ( isset($_REQUEST["iosdata"]) ) ? urldecode(Trim($_REQUEST["iosdata"])) : "";
$sIOSData = base64_decode($sIOSData);
$sSignature = ( isset($_REQUEST["signature"]) ) ? urldecode(Trim($_REQUEST["signature"])) : "";
$sSignature = base64_decode($sSignature);
// Here is where I download Apple's cert (DER format), save it as raw bits
// to a variable, convert it to PEM format (the ONLY format PHP's OpenSSL
// works with apparently...?) and then validate it.
// TODO: figure out if Apple live returns different results each time, and/or if
// this can be cached. Apple sandbox returns the same each time.
$header[0] = "Content-Type: application/pkix-cert";
$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_URL, $sPublicKeyUrl);
curl_setopt($curl, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
$der_data = curl_exec($curl);
curl_close($curl);
$sPublicKey = chunk_split(base64_encode($der_data), 64, "\n");
$sPublicKey = "-----BEGIN CERTIFICATE-----\n".$sPublicKey."-----END CERTIFICATE-----\n";
$sKey = openssl_pkey_get_public($sPublicKey);
If( $sKey === False ) Return "pkey bad";
// Here I use the package ($sIOSData) and signature to validate against Apple's
// public certificate.
$iResult = openssl_verify($sIOSData, $sSignature, $sKey, OPENSSL_ALGO_SHA1);
If( $iResult != 1 ) {
echo "BAD!\n";
echo "error: ".openssl_error_string()."\n";
}else{
echo "WORKED!\n";
}
Feedback is welcome. I'm sure there are tons of things that can be improved on. But hopefully this will help save someone a week of work.
I had a heck of a time with this. Garraeth's code was helpful, but there were a few other helpful hints scattered around SO, plus the php docs, plus some lucky guessing, and I finally arrived at this:
On the iOS side:
Main verify-user code:
// Don't bother verifying not-authenticated players
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
if (localPlayer.authenticated)
{
// __weak copy for use within code-block
__weak GKLocalPlayer *useLocalPlayer = localPlayer;
[useLocalPlayer generateIdentityVerificationSignatureWithCompletionHandler: ^(NSURL * _Nullable publicKeyUrl,
NSData * _Nullable signature,
NSData * _Nullable salt,
uint64_t timestamp,
NSError * _Nullable error) {
if (error == nil)
{
[self verifyPlayer: useLocalPlayer.playerID // our verify routine: below
publicKeyUrl: publicKeyUrl
signature: signature
salt: salt
timestamp: timestamp];
}
else
{
// GameCenter returned an error; deal with it here.
}
}];
}
else
{
// User is not authenticated; it makes no sense to try to verify them.
}
My verifyPlayer: routine:
-(void)verifyPlayer: (NSString*) playerID
publicKeyUrl: (NSURL*) publicKeyUrl
signature: (NSData*) signature
salt: (NSData*) salt
timestamp: (uint64_t) timestamp
{
NSDictionary *paramsDict = #{ #"publicKeyUrl": [publicKeyUrl absoluteString],
#"timestamp" : [NSString stringWithFormat: #"%llu", timestamp],
#"signature" : [signature base64EncodedStringWithOptions: 0],
#"salt" : [salt base64EncodedStringWithOptions: 0],
#"playerID" : playerID,
#"bundleID" : [[NSBundle mainBundle] bundleIdentifier]
};
// NOTE: A lot of the code below was cribbed from another SO answer for which I have lost the URL.
// FIXME: <When found, insert other-SO-answer URL here>
// build payload
NSMutableData *payload = [NSMutableData new];
[payload appendData: [playerID dataUsingEncoding: NSASCIIStringEncoding]];
[payload appendData: [[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding: NSASCIIStringEncoding]];
uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
[payload appendBytes: ×tampBE length: sizeof(timestampBE)];
[payload appendData: salt];
// Verify with server
[self verifyPlayerOnServer: payload withSignature: signature publicKeyURL: publicKeyUrl];
#if 0 // verify locally (for testing)
//get certificate
NSData *certificateData = [NSData dataWithContentsOfURL: publicKeyUrl];
//sign
SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData); // load the certificate
SecPolicyRef secPolicy = SecPolicyCreateBasicX509();
SecTrustRef trust;
OSStatus statusTrust = SecTrustCreateWithCertificates(certificateFromFile, secPolicy, &trust);
if (statusTrust != errSecSuccess)
{
NSLog(#"%s ***** Could not create trust certificate", __PRETTY_FUNCTION__);
return;
}
SecTrustResultType resultType;
OSStatus statusTrustEval = SecTrustEvaluate(trust, &resultType);
if (statusTrustEval != errSecSuccess)
{
NSLog(#"%s ***** Could not evaluate trust", __PRETTY_FUNCTION__);
return;
}
if ((resultType != kSecTrustResultProceed)
&& (resultType != kSecTrustResultRecoverableTrustFailure) )
{
NSLog(#"%s ***** Server can not be trusted", __PRETTY_FUNCTION__);
return;
}
SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
uint8_t sha256HashDigest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256([payload bytes], (CC_LONG)[payload length], sha256HashDigest);
NSLog(#"%s [DEBUG] sha256HashDigest: %#", __PRETTY_FUNCTION__, [NSData dataWithBytes: sha256HashDigest length: CC_SHA256_DIGEST_LENGTH]);
//check to see if its a match
OSStatus verficationResult = SecKeyRawVerify(publicKey, kSecPaddingPKCS1SHA256, sha256HashDigest, CC_SHA256_DIGEST_LENGTH, [signature bytes], [signature length]);
CFRelease(publicKey);
CFRelease(trust);
CFRelease(secPolicy);
CFRelease(certificateFromFile);
if (verficationResult == errSecSuccess)
{
NSLog(#"%s [DEBUG] Verified", __PRETTY_FUNCTION__);
dispatch_async(dispatch_get_main_queue(), ^{
[self updateGameCenterUI];
});
}
else
{
NSLog(#"%s ***** Danger!!!", __PRETTY_FUNCTION__);
}
#endif
}
My routine that passes code to the server (Cribbed from this question):
- (void) verifyPlayerOnServer: (NSData*) payload withSignature: signature publicKeyURL: (NSURL*) publicKeyUrl
{
// hint courtesy of: http://stackoverflow.com/questions/24621839/how-to-authenticate-the-gklocalplayer-on-my-third-party-server-using-php
NSDictionary *jsonDict = #{ #"data" : [payload base64EncodedStringWithOptions: 0] };
//NSLog(#"%s [DEBUG] jsonDict: %#", __PRETTY_FUNCTION__, jsonDict);
NSError *error = nil;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject: jsonDict options: 0 error: &error];
if (error != nil)
{
NSLog(#"%s ***** dataWithJson error: %#", __PRETTY_FUNCTION__, error);
}
// To validate at server end:
// http://stackoverflow.com/questions/21570700/how-to-authenticate-game-center-user-from-3rd-party-node-js-server
// NOTE: MFURLConnection is my subclass of NSURLConnection.
// .. this routine just builds an NSMutableURLRequest, then
// .. kicks it off, tracking a tag and calling back to delegate
// .. when the request is complete.
[MFURLConnection connectionWitURL: [self serverURLWithSuffix: #"gameCenter.php"]
headers: #{ #"Content-Type" : #"application/json",
#"Publickeyurl" : [publicKeyUrl absoluteString],
#"Signature" : [signature base64EncodedStringWithOptions: 0],
}
bodyData: bodyData
delegate: self
tag: worfc2_gameCenterVerifyConnection
userInfo: nil];
}
On the server side:
Somewhat cribbed from this question, and others, and php docs and...
$publicKeyURL = filter_var($headers['Publickeyurl'], FILTER_SANITIZE_URL);
$pkURL = urlencode($publicKeyURL);
if (empty($pkURL))
{
$response->addparameters(array('msg' => "no pku"));
$response->addparameters(array("DEBUG-headers" => $headers));
$response->addparameters(array('DEBUG-publicKeyURL' => $publicKeyURL));
$response->addparameters(array('DEBUG-pkURL' => $pkURL));
$response->setStatusCode(400); // bad request
}
else
{
$sslCertificate = file_get_contents($publicKeyURL);
if ($sslCertificate === false)
{
// invalid read
$response->addparameters(array('msg' => "no certificate"));
$response->setStatusCode(400); // bad request
}
else
{
// Example code from http://php.net/manual/en/function.openssl-verify.php
try
{
// According to: http://stackoverflow.com/questions/10944071/parsing-x509-certificate
$pemData = der2pem($sslCertificate);
// fetch public key from certificate and ready it
$pubkeyid = openssl_pkey_get_public($pemData);
if ($pubkeyid === false)
{
$response->addparameters(array('msg' => "public key error"));
$response->setStatusCode(400); // bad request
}
else
{
// According to: http://stackoverflow.com/questions/24621839/how-to-authenticate-the-gklocalplayer-on-my-third-party-server-using-php
// .. we use differently-formatted parameters
$sIOSData = $body['data'];
$sIOSData = base64_decode($sIOSData);
$sSignature = $headers['Signature'];
$sSignature = base64_decode($sSignature);
//$iResult = openssl_verify($sIOSData, $sSignature, $sKey, OPENSSL_ALGO_SHA1);
$dataToUse = $sIOSData;
$signatureToUse = $sSignature;
// state whether signature is okay or not
$ok = openssl_verify($dataToUse, $signatureToUse, $pubkeyid, OPENSSL_ALGO_SHA256);
if ($ok == 1)
{
//* echo "good";
$response->addparameters(array('msg' => "user validated"));
}
elseif ($ok == 0)
{
//* echo "bad";
$response->addparameters(array('msg' => "INVALID USER SIGNATURE"));
$response->addparameters(array("DEBUG-$dataToUse" => $dataToUse));
$response->addparameters(array("DEBUG-$signatureToUse" => $signatureToUse));
$response->addparameters(array("DEBUG-body" => $body));
$response->setStatusCode(401); // unauthorized
}
else
{
//* echo "ugly, error checking signature";
$response->addparameters(array('msg' => "***** ERROR checking signature"));
$response->setStatusCode(500); // server error
}
// free the key from memory
openssl_free_key($pubkeyid);
}
}
catch (Exception $ex)
{
$response->addparameters(array('msg' => "verification error"));
$response->addparameters(array("DEBUG-headers" => $headers));
$response->addparameters(array('DEBUG-Exception' => $ex));
$response->setStatusCode(400); // bad request
}
}
// NODE.js code at http://stackoverflow.com/questions/21570700/how-to-authenticate-game-center-user-from-3rd-party-node-js-server
}
Don't forget the handy utility routine:
function der2pem($der_data)
{
$pem = chunk_split(base64_encode($der_data), 64, "\n");
$pem = "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n";
return $pem;
}
Using all of this, I was finally able to get "user validated" back from my server. Yay! :)
NOTE: This method seems very open to hacking, as anyone could sign whatever they want with their own certificate then pass the server the data, signature and URL to their certificate and get back a "that's a valid GameCenter login" answer so, while this code "works" in the sense that it implements the GC algorithm, the algorithm itself seems flawed. Ideally, we would also check that the certificate came from a trusted source. Extra-paranoia to check that it is Apple's Game Center certificate would be good, too.
Thank you #garraeth, your code helped me implement the logic.
From the C# code, concat a payload data on server side is working fine for me.
When using openssl_verify we needn't do the hash ourselves.
Also, I think validate the publicKeyUrl is form HTTPS and apple.com is required.
Some pseudo code here (Note that Apple has change the algorithm to OPENSSL_ALGO_SHA256 in 2015).
// do some urls, input params validate...
// do the signature validate
$payload = concatPayload($playerId, $bundleId, $timestamp, $salt);
$pubkeyId = openssl_pkey_get_public($pem);
$isValid = openssl_verify($payload, base64_decode($signature),
$pubkeyId, OPENSSL_ALGO_SHA256);
function concatPayload($playerId, $bundleId, $timestamp, $salt) {
$bytes = array_merge(
unpack('C*', $playerId),
unpack('C*', $bundleId),
int64ToBigEndianArray($timestamp),
base64ToByteArray($salt)
);
$payload = '';
foreach ($bytes as $byte) {
$payload .= chr($byte);
}
return $payload;
}
function int64ToBigEndianArray() {
//... follow the C# code
}
function base64ToByteArray() {
//...
}
I have a problem ... and It kills me
Below is my code:
PHP
1-I did not use any kind of pre-padding and generated the IV with 16 bytes.
2-I forced the pbkdf2 function to return 8 bytes for key & hmacKey
$key = "myKey";
$plainText = "iphone";
$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$keySalt = '12345678';
$hmacSalt = '12345678';
$_key = pbkdf2('SHA1', $key, $keySalt, 10000, 8, true);
$_hmacKey = pbkdf2('SHA1', $key, $hmacSalt, 10000, 8, true);
$cipherText = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $_key, $plainText, MCRYPT_MODE_CBC, $iv);
$dataWithoutHMAC = chr(2).chr(1).$keySalt.$hmacSalt.$iv.$cipherText;
3-first try I passed the cipher text to the hash_hmac
$data = base64_encode($dataWithoutHMAC.hash_hmac('SHA256',$cipherText,$_hmacKey, true));
and I got "et.robnapier.RNCryptManager error -4301" while decrypting in iOS. "which mean too small buffer as I think"
4-second try I passed the header (chr(2).chr(1).$keySalt.$hmacSalt.$iv) + cipher text to the hash_hmac (but I got the same result for the first try [-4301])
$data = base64_encode($dataWithoutHMAC.hash_hmac('SHA256',$dataWithoutHMAC,$_hmacKey, true));
In iOS
decryptionError = nil;
NSData *fromPHPData = [#"AgExMjM0NTY3ODEyMzQ1Njc4WvrmgsFy6IoWNmm2hYL9N2jNVxU13Eo15cRyQRakRZ9WsjZ2CY/B5y4YkmG9uGdB2vHFpmpjsnm3O4d59Ex7Nw==" base64DecodedData];
NSData *fromPHPDecryptedData = [RNDecryptor decryptData:fromPHPData withPassword:#"myKey" error:&decryptionError];
NSLog(#"decryptionError %#", decryptionError);
NSString *fromPHPEcryptedStr = [fromPHPDecryptedData base64EncodedString];
NSLog(#"data : %#", fromPHPEcryptedStr);
the error I got in iOS is : "The operation couldn’t be completed. (net.robnapier.RNCryptManager error -4301.)"
I have to generate encrypted key on my php server and send it to the ipad app to decrypt it.
What I did in the php server side :
$iv = mcrypt_create_iv(32);
$privatEencryptKey = "1111";
$data = "2222";
$encryptedData = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $privateEncryptKey, base64_encode($data), MCRYPT_MODE_CBC, $iv);
$decryptedData = base64_decode(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $privateEncryptkey, $encryptedData, MCRYPT_MODE_CBC, $iv));
echo base64_encode($encryptedData); //output = WT7LorzZ1EQo2BeWxawW3Q==
echo $decryptedData; // output = 2222
echo base64_encode($iv); // output = fZTj4BxWSdCYQW/scUHvx9QoiTNXmxNrGWb/n7eFkR4=
and in the xcode I import the sercurity.framwork and I added 3rd party for base64 (encoding & decoding) and I use the (CommonCryptor.h) also and here is my code :
+ (NSData *)doCipher:(NSData *)dataIn
iv:(NSData *)iv
key:(NSData *)symmetricKey
context:(CCOperation)encryptOrDecrypt{
CCCryptorStatus ccStatus = kCCSuccess;
size_t cryptBytes = 0; // Number of bytes moved to buffer.
NSMutableData *dataOut = [NSMutableData dataWithLength:dataIn.length + kCCBlockSizeAES128];
ccStatus = CCCrypt( encryptOrDecrypt,
kCCAlgorithmAES128,
0,
symmetricKey.bytes,
kCCKeySizeAES128,
iv.bytes,
dataIn.bytes,
dataIn.length,
dataOut.mutableBytes,
dataOut.length,
&cryptBytes);
if (ccStatus != kCCSuccess) {
NSLog(#"CCCrypt status: %d", ccStatus);
}
dataOut.length = cryptBytes;
return dataOut;
}
+ (void) testCipher{
NSData *dataIn = [[#"WT7LorzZ1EQo2BeWxawW3Q==" base64DecodedString] dataUsingEncoding:NSUTF8StringEncoding];
NSData *key = [#"1111" dataUsingEncoding:NSUTF8StringEncoding];
NSData *iv = [[#"fZTj4BxWSdCYQW/scUHvx9QoiTNXmxNrGWb/n7eFkR4=" base64DecodedString] dataUsingEncoding:NSUTF8StringEncoding];
NSData *dataOut = [Utils doCipher:dataIn iv:iv key:key context:kCCDecrypt];
NSString* strOut = [[[NSString alloc] initWithData:dataOut
encoding:NSUTF8StringEncoding] base64DecodedString];
NSLog(#"%#", strOut);
}
I got the nil for strOut..... :(
Any help please......
You should use 16-byte Key and IV for AES-128. Mcrypt_encrypt otherwise pads this with zero.
Most likely you should manually add PKCS#5 padding to the input since the mcrypt_encrypt pads data with zero, which is not common practice.
I've checked all the related Stack Overflow questions. Also checked the links in that answers but didn't got any usable solution.
Here is my php script and I've nothing to do with this script (as I can't change the script).
function encrypt($message,$secretKey) {
return base64_encode(
mcrypt_encrypt(
MCRYPT_RIJNDAEL_256,
$secretKey,
$message,
MCRYPT_MODE_ECB
)
);
}
I'm unable to decrypt it in Objective C. I've used a number of Categories like Strong Encryption for Cocoa / Cocoa Touch etc, also I followed this question How do I do base64 encoding on iOS?
Here is the objective C codes that I used for decryption (found in cocoa-aes Category NSData+AES.h)
- (NSData *)AESDecryptWithPassphrase:(NSString *)pass
{
NSMutableData *ret = [NSMutableData dataWithCapacity:[self length]];
unsigned long rk[RKLENGTH(KEYBITS)];
unsigned char key[KEYLENGTH(KEYBITS)];
const char *password = [pass UTF8String];
for (int i = 0; i < sizeof(key); i++)
key[i] = password != 0 ? *password++ : 0;
int nrounds = rijndaelSetupDecrypt(rk, key, KEYBITS);
unsigned char *srcBytes = (unsigned char *)[self bytes];
int index = 0;
while (index < [self length])
{
unsigned char plaintext[16];
unsigned char ciphertext[16];
int j;
for (j = 0; j < sizeof(ciphertext); j++)
{
if (index >= [self length])
break;
ciphertext[j] = srcBytes[index++];
}
rijndaelDecrypt(rk, nrounds, ciphertext, plaintext);
[ret appendBytes:plaintext length:sizeof(plaintext)];
NSString* s = [[NSString alloc] initWithBytes:plaintext length:sizeof(plaintext) encoding:NSASCIIStringEncoding];
NSLog(#"%#",s);
}
return ret;
}
Also I tried this decoder
- (NSData*) aesDecryptWithKey:(NSString *)key initialVector:(NSString*)iv
{
int keyLength = [key length];
if(keyLength != kCCKeySizeAES128)
{
DebugLog(#"key length is not 128/192/256-bits long");
///return nil;
}
char keyBytes[keyLength+1];
bzero(keyBytes, sizeof(keyBytes));
[key getCString:keyBytes maxLength:sizeof(keyBytes) encoding:NSUTF8StringEncoding];
size_t numBytesDecrypted = 0;
size_t decryptedLength = [self length] + kCCBlockSizeAES128;
char* decryptedBytes = malloc(decryptedLength);
CCCryptorStatus result = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128 ,
(iv == nil ? kCCOptionECBMode | kCCOptionPKCS7Padding : kCCOptionPKCS7Padding),
keyBytes,
keyLength,
iv,
[self bytes],
[self length],
decryptedBytes,
decryptedLength,
&numBytesDecrypted);
if(result == kCCSuccess){
NSData* d=[NSData dataWithBytesNoCopy:decryptedBytes length:numBytesDecrypted];
NSLog(#"%#",[NSString stringWithUTF8String:[d bytes]]);
return d;
}
free(decryptedBytes);
return nil;
}
From the looks of it, that php function does two things.
mcrypt using MCRYPT_RIJNDAEL_256
base64 encodes the output of (1)
That would by why simply using base64 doesn't work. I'm going to guess from the name that MCRYPT_RIJNDAEL_256 is just AES 256.
Hope that helps.
Edit:
The code you added above looks ok. You just have to base64 decode the data first.
The php script does this:
aes encrypt
base64 encode
So you want to do this in your cocoa app:
base64 decode
aes decrypt
If you're having trouble, you might want to play around and see if you can get cocoa to do the same thing as the php script: encrypt and base64 encode the data. If you can get the output of your encryption function to be the same as the output of the php encryption function, you're in a good place to get it decrypting.
I want to use AES to encrypt a password in Objective-C, and then decrypt it in PHP, but I have two problems.
I encrypt the password, but it's an NSData object, so I encode it with base64, but when I decode in PHP, the result is nil. So I can't decrypt it.
I can encrypt and decrypt the password in Objective-C, so it is the PHP that is the problem, but when I encrypt with AES and then encode with base64, the results are not the same.
Here is my code:
PHP:
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "a16byteslongkey!";
$plaintext = "iphone";
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_ECB, $iv);
$ciphertext = base64_encode($ciphertext);
echo "ciphertext: ".$ciphertext."<br/>";
$ciphertext = base64_decode($ciphertext);
$plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext, MCRYPT_MODE_ECB, $iv);
echo "plaintext: ".$plaintext."<br/>";
output:
ciphertext: SXNepKfh0IrlDDdkq4EdmQ==
plaintext: iphone
Objective-C: (Get the full source code here: https://gist.github.com/838614)
NSString *key = #"a16byteslongkey!";
NSString *plaintext = #"iphone";
NSString *ciphertext = [plaintext AES256EncryptWithKey: key];
NSLog(#"ciphertext: %#", ciphertext);
plaintext = [ciphertext AES256DecryptWithKey: key];
NSLog(#"plaintext: %#", plaintext);
output:
ciphertext: D19l3gsgXJlrLl7B2oCT6g==
plaintext: iphone
i replace kCCKeySizeAES256 with kCCKeySizeAES128, and replace "kCCOptionPKCS7Padding" with "kCCOptionPKCS7Padding | kCCOptionECBMode",
i have slove the problem.
don't change the code from https://gist.github.com/838614
the key should be 32 byte.
the results of encryt are not the same, but they'll be the same if you decrypt.
objective-c:
NSString *key = #"a16byteslongkey!a16byteslongkey!";
NSString *plaintext = #"iphone";
NSString *ciphertext = [plaintext AES256EncryptWithKey: key];
NSLog(#"ciphertext: %#", ciphertext);
plaintext = [ciphertext AES256DecryptWithKey: key];
NSLog(#"plaintext: %#", plaintext);
output:
ciphertext: I3chV+E2XUHeLCcJAhBaJQ==
plaintext: iphone
php:
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = 'a16byteslongkey!a16byteslongkey!';
$plaintext = "iphone";
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_ECB);
$base64encoded_ciphertext = base64_encode($ciphertext);
echo "ciphertext: ".$base64encoded_ciphertext."<br/>";
$plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($base64encoded_ciphertext), MCRYPT_MODE_ECB);
echo "plaintext: ".$plaintext."<br/>";
$base64encoded_ciphertext = "I3chV+E2XUHeLCcJAhBaJQ==";
$plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($base64encoded_ciphertext), MCRYPT_MODE_ECB);
echo "plaintext: ".trim($plaintext);
output:
ciphertext: kUr+YsYtb3Uy34li/GPcjg==
plaintext: iphone
plaintext: iphone
fixed use some thing like
$iv2 = '';
for ($i = 0; $i < 16; $i++) {
$iv2 .= "\0";
}
mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($text), MCRYPT_MODE_CBC, $iv2);
don't change the code from https://gist.github.com/838614
the key should be 32 byte
the results of encryt and decrypt are not the same
I have tried to test with this string:
$plaintex = "fskfladsadsadfsfs dfskl;dfs a jadfsa ds'a' j afdjdfsaadfs' jdfas af 'ksfegfffffffffffffffffffffsdfsfgfsfdsdfddfsg"
and the results are different. Anybody here know what the reason could be?
I suspect that it's the padding routine. I've approached this by making sure that the text to be encrypted is padded to 32 char with spaces, and before the result is returned from teh decryption routine, trim off the extra spaces.
There are official padding algorithms, but I found they didn't work. If you encrypt a string that is a multiple of 32 characters long, then even if the padding routine is wrong it will ignore it.