views:

2243

answers:

8

Has anyone been able to use the SSCrypto Framework for Cocoa to encrypt text and then decrypt it in C#/.NET ? Or can someone offer some guidance?

I'm pretty sure my issue has to do with getting the crypto settings correct but I am far from fluent in Cocoa so I can't really tell what settings are being used in the library. However my attempt at deciphering it seems like md5 hashing, CBC mode, padding with zeros and I have no idea if the IV is set or not...

My C# code looks like this:

     public static string Decrypt( string toDecrypt, string key, bool useHashing )
 {
  byte[] keyArray;
  byte[] toEncryptArray = Convert.FromBase64String( toDecrypt );

  if( useHashing )
  {
   MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
   keyArray = hashmd5.ComputeHash( UTF8Encoding.UTF8.GetBytes( key ) );
   hashmd5.Clear();
  }
  else
   keyArray = UTF8Encoding.UTF8.GetBytes( key );

  TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
  tdes.Key = keyArray;
  tdes.Mode = CipherMode.CBC;
  tdes.Padding = PaddingMode.Zeros;

  ICryptoTransform cTransform = tdes.CreateDecryptor();
  byte[] resultArray = cTransform.TransformFinalBlock( toEncryptArray, 0, toEncryptArray.Length );

  tdes.Clear();

  return UTF8Encoding.UTF8.GetString( resultArray );
 }

When I run encryption on the Cocoa side I get the encrypted text:

UMldOZh8sBnHAbfN6E/9KfS1VyWAa7RN

but that won't decrypt on the C# side with the same key.

Any help is appreciated, thanks.

+1  A: 

A couple of things to watch out for:

1- Make sure that you're interpreting the key and data strings correctly. For example, is the key encoded in ASCII instead of UTF8? Does it perhaps represented in binhex format instead?

2- You're not initializing the IV (Initialization Vector) before decrypting. It needs to match the IV you're using to encrypt on the Cocoa side.

tomasr
A: 

IIRC, OpenSSL uses what MS calls PKCS7 padding (though OpenSSL refers to it as PKCS5, and I'm not enough of a standards wonk to care why).

Mark Brackett
A: 

One of the classic issues in moving data back and forth from Mac to PC is byte ordering. You didn't say what the execution platform is for the Cocoa code, but that's something to look out for, especially if it's a PowerPC Mac.

Sixten Otto
A: 

There could be something to do with endianness,

Try to call Array.Reverse before decryption.

var reversedArr = Array.Reverse(toEncrytArray)
byte[] resultArray = cTransform.TransformFinalBlock( reversedArr, 0, reversedArr.Length );
Can Erten
Why would you only do that on the receiving side? The sending side (the Mac) could also be an Intel machine. The correct place to do byte-swapping is either on the Mac (use little-endian over the wire) or both (use big-endian, aka network byte order, over the wire).
Peter Hosey
A: 

So still working on this... It seems the key to my original question is how the Key and IV are generated, as stated above. The function used is EVP_BytesToKey() (http://www.openssl.org/docs/crypto/EVP_BytesToKey.html)

I haven't had the time to try an implementation of the function in C# yet however I decided to skip EVP_BytesToKey and just use a pre-selected Key and IV on the Cocoa side. So I can get the EXACT same Key and IV on both sides but the final output is still different.

I've also moved to 3DES in the process since it seems easier to work with on both sides. Here is my current code:

        byte[] data = Encoding.UTF8.GetBytes("secret");
        byte[] enc = new byte[0];
        TripleDES tdes = TripleDES.Create();
        tdes.Mode = CipherMode.CBC;
        tdes.Padding = PaddingMode.Zeros;
        tdes.IV = Convert.FromBase64String("cGFzc3dvcmQ="); //iv from Cocoa;
        tdes.Key = Convert.FromBase64String("cGFzc3dvcmREUjB3U1NAUDY2NjBqdWh0"); //key from Cocoa;
        ICryptoTransform ict = tdes.CreateEncryptor();
        enc = ict.TransformFinalBlock(data, 0, data.Length);
        Console.WriteLine(Convert.ToBase64String(enc));

        tdes.Clear();

        // Decrypt
        ICryptoTransform icd = tdes.CreateDecryptor();
        byte[] dec = icd.TransformFinalBlock(Convert.FromBase64String("PyPqLI/d18Q="), 0, enc.Length);
        string d = Encoding.UTF8.GetString(dec);
        Console.WriteLine(d);

Even though the IV and Key are the same, the encrypted text is different than Cocoa...

Cocoa output: "PyPqLI/d18Q=" .NET output: "9h8OSwCU1D8="

Any ideas?

A: 

You should really post the Cocoa code, too, to give us a chance to find your problem.

But there are some hints hidden in what you have posted:

Decrypting PyPqLI/d18Q= (base64) with the key and iv gives "97737D09E48B0202" (hex). This looks like the plaintext "97737D09E48B" with PKCS7-padding. So I would start by changing the .NET code to use PaddingMode.PKCS7 and look closely at where you pass the plaintext to the Cocoa code.

Rasmus Faber
A: 

Ok, I'm using the SSCrypto framework and I've added the relevant Cocoa code below...

We're using the test code that comes with SSCrpyto (in particular des3):

    // Test 2: Symmetric encryption and decryption using various ciphers

//NSData *seedData1 = [SSCrypto getKeyDataWithLength:32];
NSData *seedData1 = [@"passwordDR0wSS@P6660juht" dataUsingEncoding:NSUTF8StringEncoding];
crypto = [[SSCrypto alloc] initWithSymmetricKey:seedData1];

NSArray *ciphers = [NSArray arrayWithObjects:@"aes256", @"aes128", @"blowfish", @"aes192",
 @"RC4", @"blowfish", @"RC5", @"des3", @"des", nil];

NSString *password = @"secret";
[crypto setClearTextWithString:password];

for(n = 0; n < [ciphers count]; n++)
{
 NSData *cipherText = [crypto encrypt:[ciphers objectAtIndex:n]];
 NSData *clearText = [crypto decrypt:[ciphers objectAtIndex:n]];

 NSLog(@"Original password: %@", password);
 NSLog(@"Cipher text: '%@' using %@", [cipherText encodeBase64WithNewlines:NO], [ciphers objectAtIndex:n]);
 NSLog(@"Clear text: '%s' using %@", [clearText bytes], [ciphers objectAtIndex:n]);

 NSLog(@" ");
}

NSLog(@" ");
NSLog(@" ");

[crypto release];

And then here is the actual encrypt function in the SSCrypto framework that we're using... We have made modifications like I mentioned above to skip EVP_BytesToKey():

    // If there is no clear text set, or the clear text is an empty string (zero length data)
// then there is nothing to encrypt, and we may as well return nil
if(clearText == nil || [clearText length] == 0)
{
 return nil;
}

unsigned char *input = (unsigned char *)[clearText bytes];
//unsigned char *outbuf, iv[EVP_MAX_IV_LENGTH];
unsigned char *outbuf;
int outlen, templen, inlen;
inlen = [clearText length];

if([self isSymmetric])
{
 // Perform symmetric encryption...

   // unsigned char evp_key[EVP_MAX_KEY_LENGTH] = {"\0"};
    EVP_CIPHER_CTX cCtx;
    const EVP_CIPHER *cipher;

    if (cipherName){
        cipher = EVP_get_cipherbyname((const char *)[cipherName UTF8String]);
        if (!cipher){
            NSLog(@"cannot get cipher with name %@", cipherName);
            return nil;
        }
    } else {
        cipher = EVP_bf_cbc();
        if (!cipher){
            NSLog(@"cannot get cipher with name %@", @"EVP_bf_cbc");
            return nil;
        }
    }

    EVP_CIPHER_CTX_init(&cCtx);

 NSData *testEVPKeyData = [self symmetricKey];
 NSString *testEVPKeyString = [testEVPKeyData encodeBase64];

 NSData *testIvData = [@"password" dataUsingEncoding:NSUTF8StringEncoding];
 NSString *testIvString = [testIvData encodeBase64];
 NSLog(@"\nevp_key: %@\niv: %@", testEVPKeyString, testIvString);

 if (!EVP_EncryptInit(&cCtx, cipher, [testEVPKeyData bytes], [testIvData bytes])) {
        NSLog(@"EVP_EncryptInit() failed!");
        EVP_CIPHER_CTX_cleanup(&cCtx);
        return nil;
    }

    EVP_CIPHER_CTX_set_key_length(&cCtx, EVP_MAX_KEY_LENGTH);

    // add a couple extra blocks to the outbuf to be safe
    outbuf = (unsigned char *)calloc(inlen + EVP_CIPHER_CTX_block_size(&cCtx), sizeof(unsigned char));
    NSAssert(outbuf, @"Cannot allocate memory for buffer!");

    if (!EVP_EncryptUpdate(&cCtx, outbuf, &outlen, input, inlen)){
        NSLog(@"EVP_EncryptUpdate() failed!");
        EVP_CIPHER_CTX_cleanup(&cCtx);
        return nil;
    }
    if (!EVP_EncryptFinal(&cCtx, outbuf + outlen, &templen)){
        NSLog(@"EVP_EncryptFinal() failed!");
        EVP_CIPHER_CTX_cleanup(&cCtx);
        return nil;
    }
    outlen += templen;
    EVP_CIPHER_CTX_cleanup(&cCtx);

}
else
{
 // Irrelevant code
}

// Store the encrypted data as the cipher text
[self setCipherText:[NSData dataWithBytes:outbuf length:outlen]];

// Release the outbuf, since it was malloc'd
if(outbuf) {
    free(outbuf);
}

return [self cipherTextAsData];
Could you try logging the contents of "input" just before the call to EVP_EncryptUpdate?
Rasmus Faber
input string base64: 2Zxsh0Kj
Okay. That is not what it is supposed to be. It should have been "secret" (c2VjcmV0 in base64). Something is corrupting it. Perhaps try moving [crypto setClearTextWithString:password]; inside the for-loop. Can SSCrypto decrypt the encrypted data? What is logged by "Clear text: '%s' using des3"?
Rasmus Faber
If you haven't updated the decrypt-method to also skip EVP_BytesToKey() then that is your problem. The decrypt-method overwrites the cleartext property of the SSCrypto-class. So you are basically encrypting garbage after the first iteration of the loop.
Rasmus Faber
Right about the decrypt method. I'm now generating the correct base64 value for "input": c2VjcmV0Unfortunately .NET calls "Bad Data" on:byte[] dec = icd.TransformFinalBlock(fromb64, 0, fromb64.Length);where fromb64 = Convert.FromBase64String("2x+a46x9K6k=");I'm also using PKCS7 now.
Hmmm. Strange. And the code still logs the same values for the key and the iv?
Rasmus Faber
+2  A: 

You could use OpenSSL directly in C# with the OpenSSL.NET wrapper!

fried