I am currently working on an iPhone app that is a port of a subset of .NET C# functionality. I must log into a server using a 3DES encrypted password (yes, I know that is not an optimal standard, but please bear with me).
So far, however, no joy. I cannot properly replicate the encryption in this C# code. The code in both C# in objective-c shares these common variables:
strPassword
is the unencrypted password, like "secret"abPlain
is a byte array with the hex values of strPassword {73, 65, 63, 72, 65, 74, 02, 02}
rpmPassword
is a string of random characters.rpmPasswordAsData
is the objective-C only representation of rpmPassword as NSData using UTF8 encodingabPassword
is a byte array with the values of rpmPasswordnLen
in the objective-C code belowHere's the C# code first:
static int ITERATIONCOUNT = 2048;
static int KEYBYTES = 24;
static int BLOCKBYTES = 8;
byte[] abInitV = CryptoSysAPI.Rng.NonceBytes(BLOCKBYTES);
byte[] abKey = CryptoSysAPI.Pbe.Kdf2(KEYBYTES, abPassword, abInitV, ITERATIONCOUNT);
CryptoSysAPI.Tdea cipher = CryptoSysAPI.Tdea.Instance();
cipher.InitEncrypt(abKey, Mode.CBC, abInitV);
byte[] abCipher = cipher.Update(abPlain);
abOutput = new byte[abCipher.Length + BLOCKBYTES];
for (int i = 0; i < BLOCKBYTES; i++) abOutput[i] = abInitV[i];
for (int i = 0; i < nLen + nPad; i++) abOutput[BLOCKBYTES + i] = abCipher[i];
return CryptoSysAPI.Cnv.ToHex(abOutput)
As you can see, the encrypted value this returns is actually a concatenation of the hex values of abInitV
and abCipher
.
I have been cribbing from Rob Napier to try and transform this into working objective-c code, but so far, it's not happening. I am producing abInitV
and abCipher
values of the right length, and I'm also concatenating them properly into abOutput
, but I am being rejected by the server when I try to log in.
Here's my objective-c code (the constants are also declared, I promise):
int nLen = [strPassword length];
int nPad = ((nLen / BLOCKBYTES) + 1) * BLOCKBYTES - nLen;
NSData *abInitV = [self randomDataOfLength:BLOCKBYTES]; // This is the salthex for the encryption
const unsigned char *abInitVAsBytes = [abInitV bytes];
NSData *abKey = [self TDEAKeyForPassword:strPassword salt:abInitV];
size_t movedBytes = 0;
NSMutableData *abCipher = [NSMutableData dataWithLength:BLOCKBYTES];
CCCryptorStatus result = CCCrypt(kCCEncrypt,
kCCAlgorithm3DES,
ccNoPadding & kCCModeCBC,
[abKey bytes],
kCCKeySize3DES,
[abInitV bytes],
abPassword,
[rpmPasswordAsData length],
abCipher.mutableBytes,
KEYBYTES,
&movedBytes);
if (result == kCCSuccess)
{
NSLog(@"abCipher == %@ \n", [abCipher description] );
}
NSMutableData *abOutput = [NSMutableData dataWithCapacity:[abCipher length] + BLOCKBYTES];
const unsigned char *abCipherAsBytes = [abCipher bytes];
for (int i = 0; i < BLOCKBYTES; i++)
{
[abOutput replaceBytesInRange:NSMakeRange(i, sizeof(abInitVAsBytes[i])) withBytes:&abInitVAsBytes[i]];
}
for (int i = 0; i < nLen + nPad; i++)
{
[abOutput replaceBytesInRange:NSMakeRange(BLOCKBYTES + i, sizeof(abCipherAsBytes[i])) withBytes:&abCipherAsBytes[i]];
}
return [EncryptionUtil NSDataToHex:abOutput];
And here are the supporting methods called in the code above:
+(NSString*) NSDataToHex:(NSData*)data
{
const unsigned char *dbytes = [data bytes];
NSMutableString *hexStr =
[NSMutableString stringWithCapacity:[data length]*2];
int i;
for (i = 0; i < [data length]; i++) {
[hexStr appendFormat:@"%02x ", dbytes[i]];
}
return [NSString stringWithString: hexStr];
}
+(NSData*)HexToNSData:(NSString*)hex
{
NSMutableData* data = [NSMutableData data];
int idx;
for (idx = 0; idx+2 <= [hex length]; idx+=2) {
NSRange range = NSMakeRange(idx, 2);
NSString* hexStr = [hex substringWithRange:range];
NSScanner* scanner = [NSScanner scannerWithString:hexStr];
unsigned int intValue;
[scanner scanHexInt:&intValue];
[data appendBytes:&intValue length:1];
}
return data;
}
+(NSData *)randomDataOfLength:(size_t)length
{
NSMutableData *data = [NSMutableData dataWithLength:length];
int result = SecRandomCopyBytes(kSecRandomDefault,
length,
data.mutableBytes);
NSAssert(result == 0, @"Unable to generate random bytes: %d",
errno);
return data;
}
+(NSData *)TDEAKeyForPassword:(NSString *)password
salt:(NSData *)salt
{
NSMutableData *
derivedKey = [NSMutableData dataWithLength:kCCKeySize3DES];
int result = CCKeyDerivationPBKDF(kCCPBKDF2, // algorithm
password.UTF8String, // password
password.length, // passwordLength
salt.bytes, // salt
salt.length, // saltLen
kCCPRFHmacAlgSHA1, // PRF
ITERATIONCOUNT, // rounds
derivedKey.mutableBytes, // derivedKey
derivedKey.length); // derivedKeyLen
return derivedKey;
}
So, if anyone can tell me what I'm doing wrong, I'd sincerely appreciate it. If I had to hazard a guess, I'd assume the problem is in one of 2 places:
TDEAKeyForPassword
CCCrypt
.That said, I've tried every available PRF constant, as well as padding and no padding.
I'm very inexperienced with encryption, so I'd appreciate any help anyone can offer.
Thanks!
First, your code samples seem very different. The C# code is encrypting abPlain
. The ObjC code is encrypting abPassword
. You indicate that abPlain
is static, while abPassword
is random. You have some kind of padding going on in the ObjC that I don't see in the C# (maybe this is 0 padding? That's not a standard way to pad.)
If you remove the randomness, then each implementation should return exactly the same result for the same input. So first, hard-code a random value (I generally like to use 0, which is just as random as any other number), and also choose a common abPassword
. Then, at each step of the process, make sure that both implementations generate the same result. Most significantly, is abKey
the same, and then is abCipher
, and finally is abOutput
. At one of these points your implementations clearly diverge. (From my quick reading, they seem to be doing very different things all along.)
One thing that is very confusing is how you switch between abPassword
and rpmPassword
. In one case you pass abPassword
, but the length of rpmPasswordAsData
. That feels like a place where it's easy to make a mistake.
The length of abPassword
is unclear. I assume it's a multiple of 8, or else your ccNoPadding
is going to blow up.
NSMutableData
has an appendData
method. That's much easier than your replaceBytesInRange:…