I'm trying to implement a system where A generates an RSA key pair and sends the public key to B. B then generates an AES key and encrypts it using the public key, sending the result back to A. A then decrypts the AES key using its RSA private key, encrypts the data using the AES key and send it to B, who can then decrypt it using the AES key.
I've got this all working on the Android side, but I can't get the iPhone side to play ball (I'm new to Objective C so that's probably why!)
Initially, I was getting an error 9809 when decrypting the AES key using the RSA private key, which unhelpfully translates to a general error. Researching the error points to the padding (I'm using PKCS1 Padding) being the problem, switching to No Padding allowed the iPhone client to decrypt successfully, but the decrypted AES key is different from the one generated on the Android client.
Objective C is very new to me and I'm sure I'm just making a schoolboy error, can anyone point me in the right direction please?
iPhone RSA key pair generation
static const unsigned char _encodedRSAEncryptionOID[15] = {
/* Sequence of length 0xd made up of OID followed by NULL */
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00
};
NSData * publicTag = [publicKeyIdentifier dataUsingEncoding:NSUTF8StringEncoding];
// Now lets extract the public key - build query to get bits
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
[queryPublicKey setObject:(__bridge id) kSecClassKey
forKey:(__bridge id) kSecClass];
[queryPublicKey setObject:publicTag
forKey:(__bridge id) kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id) kSecAttrKeyTypeRSA
forKey:(__bridge id) kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES]
forKey:(__bridge id) kSecReturnData];
CFTypeRef pk;
OSStatus err = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, &pk);
NSData* publicKeyBits = (__bridge_transfer NSData*)pk;
if (err != noErr) {
return nil;
}
// OK - that gives us the "BITSTRING component of a full DER
// encoded RSA public key - we now need to build the rest
unsigned char builder[15];
NSMutableData * encKey = [[NSMutableData alloc] init];
int bitstringEncLength;
// When we get to the bitstring - how will we encode it?
if ([publicKeyBits length ] + 1 < 128 )
bitstringEncLength = 1 ;
else
bitstringEncLength = (([publicKeyBits length ] +1 ) / 256 ) + 2 ;
// Overall we have a sequence of a certain length
builder[0] = 0x30; // ASN.1 encoding representing a SEQUENCE
// Build up overall size made up of -
// size of OID + size of bitstring encoding + size of actual key
size_t i = sizeof(_encodedRSAEncryptionOID) + 2 + bitstringEncLength +
[publicKeyBits length];
size_t j = encodeLength(&builder[1], i);
[encKey appendBytes:builder length:j +1];
// First part of the sequence is the OID
[encKey appendBytes:_encodedRSAEncryptionOID
length:sizeof(_encodedRSAEncryptionOID)];
// Now add the bitstring
builder[0] = 0x03;
j = encodeLength(&builder[1], [publicKeyBits length] + 1);
builder[j+1] = 0x00;
[encKey appendBytes:builder length:j + 2];
// Now the actual key
[encKey appendData:publicKeyBits];
// Now translate the result to a Base64 string
Base64* base64 = [[Base64 alloc] init];
NSString* ret = [base64 encode:encKey];
return ret;
Re-creating the public key, generating the AES key and encrypting it on Android
(note the getBytes(...) and getString(...) just do some base64 encoding.decoding)
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256, new SecureRandom());
SecretKey secretKey = keyGen.generateKey();
byte[] publicKeyBytes = getBytes(publicKey.getKey());
PublicKey rsaKey = KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, rsaKey);
String keyEncoded = getString(key);
return getString(encryptedKeyBytes));
Decrypting the AES key on iPhone
Base64* base64 = [[Base64 alloc] init];
NSData* cipherText = [base64 decode:textBase64];
const uint8_t *cipherBuffer = (const uint8_t*)[cipherText bytes];
size_t cipherBufferSize = strlen((char *) cipherBuffer);
uint8_t *plainBuffer = (uint8_t *)calloc(SecKeyGetBlockSize(publicKey), sizeof(uint8_t));
size_t plainBufferSize = SecKeyGetBlockSize(publicKey);
OSStatus status = SecKeyDecrypt(privateKey,
kSecPaddingPKCS1,
&cipherBuffer[0],
cipherBufferSize,
&plainBuffer[0],
&plainBufferSize
);
NSData* finalData = [[NSData alloc] initWithBytes:plainBuffer length:plainBufferSize];
NSString *result = [base64 encode:finalData];
return result;
EDIT: I think I've narrowed this down a bit, the following code from the Decrypting the AES key part of my code:
NSData* cipherText = [base64 decode:text];
NSLog(#"cipherText %#", cipherText);
const uint8_t *cipherBuffer = (const uint8_t*)[cipherText bytes];
NSLog(#"cipherBuffer %s", cipherBuffer);
size_t cipherBufferSize = strlen((char *) cipherBuffer);
NSLog(#"cipherBufferSize %zd", cipherBufferSize);
Produces the following output in the console:
cipherText <31226275 cc56069a e96b7f6f 0fbee853 32d07de6 436755c9 e27b88a6 04176947 d57f7108 de68e5b8 49595e9f 09bceb30 1d615927 c205f205 eb644fa7 bff6c02b 885605de eb5bd4ee 473bb4d3 df768017 24552706 ea67f347 2952614e ad63f3c6 eb0022d3 a0513afa 0e59ba63 cb5c9787 a40ecad4 a866fdc7 26b60cc2 088a3499 a84c0595 fb1c2be8 5c85b88d 7856b4bd 655f6fec 905ca221 d6bb03c0 7329410b b235ef8f 1ef97a64 7fabb280 90118ff7 4b1e91f6 162134fc 5cbf962e 813e39e7 993b0fb9 e3c4b30c ef6a7b90 9d64c41a 1211ab34 c2c52235 d2ec3b65 d1314cee 70eafe65 f4a6c5e4 660cf889 4540a784 d14cc5a8 49a12c43 c76f7f03 5fbcd44f>
cipherBuffer 1"buÃVöÈkoæËS2–}ÊCgU…‚{à¶iG’qfihÂ∏IY^ü ºÎ0aY'¬ÚÎdOßøˆ¿+àVfiÎ[‘ÓG;¥”flvÄ$U'ÍgÛG)RaN≠cÛ∆Î
cipherBufferSize 97
Occasionally however, it comes out with a cipher buffer size of 256 as expected and the decryption works perfectly! I know I must be missing something obvious?
Your issue is with the strlen function, which does not work on binary data in general, it only works on binary data that represents text and it concluded with a zero valued byte (\0). Instead you should use the actual size of the ciphertext.
So currently your code block will fail if the ciphertext contains a zero valued byte, or if it is not directly followed by a zero valued byte.
Related
I want to generate the same encrypted string in iOS, android in .net. I can generate same string android and in .net but different for Objective C.
Android code:
public static String encrypt(String key, String value) {
try {
SecretKey secretKey = new SecretKeySpec(key.getBytes(), "AES");
AlgorithmParameterSpec iv = new IvParameterSpec(key.getBytes());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
return new String(Base64.encode(cipher.doFinal(value.getBytes("UTF-8")), Base64.NO_WRAP));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
backend code
public Encryption(input: string): any {
return CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(input), this.Cryptokey,
{
keySize: 128 / 8,
iv: this.iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
}
and in Objective C code
- (NSData *)AES256EncryptWithKey:(NSString *)key
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [self length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL /* initialization vector (optional) */,
[self bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}
free(buffer); //free the buffer;
return nil;
}
and I followed these URLs
Encrypt AES/CBC/PKCS7Padding
How to encrypt with AES 256 CBC in Objective C
AES CBC Encryption With PKCS7Padding Has Different Results In Java And Objective-C
As per my understanding for Objective C first I need to convert the password in NSData then pass it to the AES encryption method.
This method will return to me encrypted Data now I need to convert this in base 64 string encoding.
Can anyone suggest to me what should I do to generate the same result for iOS, Android, and .net?
For example
if I want to encrypt string "xceedancce" in all 3 platforms and my key is
7061737323313233
then in Android and .net the result is "uXDlYA4e8Z8HWd9rvNdXaw==" same but in iOS
it is "l4zDDnwOVJ0dz2fl7HdKIA=="
Can anyone suggest what should I do in Objective C?
Thank you.
Thank you, Rob. I used the RNCryptor library for both (Objective C and C#) and it solved my problem.
Here is my code for Objective C:
-(NSString *)encryptUsingRNCryptor:(NSString *)strPassword{
NSData *data = [strPassword dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSData *encryptedData = [RNEncryptor encryptData:data
withSettings:kRNCryptorAES256Settings
password:kKeyForEncryption
error:&error];
NSString *encryptString = [encryptedData base64EncodedStringWithOptions:0];
NSLog(#"enccrypted data ----->>>## %# ## %#", encryptedData, encryptString);
// Decryption
NSData *decryptedData = [RNDecryptor decryptData:encryptedData
withPassword:kKeyForEncryption
error:&error];
NSString *decodedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];
NSLog(#" Decryted data ----%# --------and string %#", decryptedData, decodedString);
return [encryptedData base64EncodedStringWithOptions:0];
}
and for C# decryption we used RNCryptor-cs library.
Once again thank you Rob :)
I have iOS sources for data encoding and I try to implement same encoding in Android app. iOS sources:
- (NSString *)encryptRSA:(NSString *)plainTextString useKeyWithTag:(NSString *)tag withSecPadding:(SecPadding)padding {
SecKeyRef publicKey = [self _getPublicKeyRefByTag:tag];
size_t cipherBufferSize = SecKeyGetBlockSize(publicKey);
uint8_t *cipherBuffer = malloc(cipherBufferSize);
uint8_t *nonce = (uint8_t *)[plainTextString UTF8String];
SecKeyEncrypt(publicKey,
padding,
nonce,
strlen( (char*)nonce ),
&cipherBuffer[0],
&cipherBufferSize);
NSData *encryptedData = [NSData dataWithBytes:cipherBuffer length:cipherBufferSize];
free(cipherBuffer);
return [encryptedData base64EncodedStringWithOptions:0];
}
Function call:
[self.rsaManager encryptRSA:inputText withSecPadding:kSecPaddingPKCS1];
In Android I make next:
public static byte[] encrypt(byte[] text, PublicKey key) throws Exception {
final Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(text);
}
Function call:
Base64.getEncoder().encodeToString(encrypt(inputText.getBytes(), publicKey))
In result I get different strings on iOS and Android for same inputText. What I'm doing wrong?
PKCS1 padding adds an element of randomness into the encryption. If you encrypt the same thing twice, you should get different ciphertexts. But both ciphertexts should decrypt to the same plaintext (modulo the added randomness, which should be handled by the PKCS1 implementation).
https://en.wikipedia.org/wiki/Optimal_asymmetric_encryption_padding
I receive from a Server in Python a file encrypted in this manner:
import os
from Crypto.Cipher import AES
from Crypto import Random
def pad(s):
return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(message, key):
message = pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def encrypt_file(file_name, key):
with open("epub/" + file_name, 'rb') as fo:
plaintext = fo.read()
enc = encrypt(plaintext, key)
file_name = "enc/" + file_name
with open(file_name, 'wb') as fo:
fo.write(enc)
for list in os.listdir('./epub'):
if list.find('.epub') != -1:
key = '0123456789012345'
encrypt_file(list, key)
My App in Android decrypt file in this manner:
FileInputStream epubCifr = new FileInputStream(Environment.getExternalStorageDirectory() + "/Skin/readium/epub_content/" + title + "/" + title + ".epub");
FileOutputStream epubDec = new FileOutputStream(Environment.getExternalStorageDirectory() + "/Skin/readium/epub_content/" + title + "/" + title + "_dec.epub");
SecretKeySpec sks = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, sks);
CipherInputStream cis = new CipherInputStream(epubCifr, cipher);
int b;
byte[] d = new byte[16];
while ((b = cis.read(d)) != -1)
epubDec.write(d, 0, b);
epubDec.flush();
epubDec.close();
cis.close();
Now.
I would do the same thing in an app for iOS but I'm a newbie in objective C.
I can't change code on Server.
I tried to use RNCrypto for iOS but with poor results.
Any suggestions?
Thanks
EDIT 1
Sorry. This is my code in objective C.
I think that there are some settings in RNCryptor that are wrong.
int blockSize = 16;
NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:#"epub.epub"];
NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:#"epub_dec.epub" append:NO];
[cryptedStream open];
[decryptedStream open];
__block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
__block RNEncryptor *decryptor = nil;
dispatch_block_t readStreamBlock = ^{
[data setLength:blockSize];
NSInteger bytesRead = [cryptedStream read:[data mutableBytes] maxLength:blockSize];
if (bytesRead < 0) {
}
else if (bytesRead == 0) {
[decryptor finish];
}
else {
[data setLength:bytesRead];
[decryptor addData:data];
NSLog(#"Sent %ld bytes to decryptor", (unsigned long)bytesRead);
}
};
decryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
password:#"0123456789012345"
handler:^(RNCryptor *cryptor, NSData *data) {
NSLog(#"Decryptor recevied %ld bytes", (unsigned long)data.length);
[decryptedStream write:data.bytes maxLength:data.length];
if (cryptor.isFinished) {
[decryptedStream close];
}
else {
readStreamBlock();
}
}];
readStreamBlock();
EDIT 2
Python code with PKCS7 and iv of 0 bytes.
from Crypto.Cipher import AES
from Crypto import Random
from pkcs7 import PKCS7Encoder
def pad(s):
encoder = PKCS7Encoder()
return encoder.encode(s)
#return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(message, key):
message = pad(message)
#iv = Random.new().read(AES.block_size)
iv = b'0000000000000000'
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def encrypt_file(file_name, key):
with open("epub/" + file_name, 'rb') as fo:
plaintext = fo.read()
enc = encrypt(plaintext, key)
file_name = "enc/" + file_name
with open(file_name, 'wb') as fo:
fo.write(enc)
key = '0123456789012345'
encrypt_file("epub1.epub", key)
decrypt_file("epub1.epub", key)
RNCryptor in this case is not a good choice because it imposes a format and method for encryption that does not match the method being used to encrypt.
You state that there is no padding but the Python code does pad the message with zero bytes, this is both non-standard, insecure and assumes that the data being encrypted does not end with a null byte. You will have to handle stripping the null padding after decryption. The usual padding is PKCS#7.
The iv for CBC mode is prepended to the encrypted data, this is a good scheme. You will need to separate the iv from the encryption data and apply it to the decryption function call.
It is not clear that the encryption key is exactly the correct length or what AES key size is being used. Presumably the Python method is using the length of the key to determine the AES key size, it can be 128, 192 or 256 bits and if not one of these padding the key to length. You will need to determine this and handle it in your code. The AES key size should really be specified and the key should be exactly the matching length.
With the above you can use the Apple supplied Common Crypto, specifically CCCrypt function.
It is potentially possible to modify the received data to match the requirements of RNCryptor and handle the padding after the decryption.
I have a strange issue trying to match decryption between an existing iOS app, a .net server, and and Android app that I'm working on. I've checked that my program outputs byte for byte the same encryption as the iOS and decrypts it's own packets perfectly. It appears that the server is able to decrypt the packets sent from the Android app, but when I try to decode the packets from the server I'm getting a BadPaddingException on the Android, whereas the iOS version decodes properly. I've also checked that the Key and IV are byte identical.
edit: I've added the server side code (part of a UDP socket listener) from my client, at first glance it looks like padding has not been defined properly, but my research says the the default is PKCS7, so I'm still confused as to what's causing the problem.
I've tested message lengths (coming from the server) before and after decryption and I see 2 different messages. one is a null keep alive message of 16 bytes before decryption, 0 bytes after decryption. The second message is 128 bytes before decryption and 112 bytes after decryption, in iOS. Both fail to decrypt in Android.
iOS:
+ (NSData*)decryptData:(NSData*)data key:(NSData*)key iv:(NSData*)iv;
{
int FBENCRYPT_BLOCK_SIZE = kCCBlockSizeAES128;
int FBENCRYPT_KEY_SIZE = kCCKeySizeAES256;
// setup key
unsigned char cKey[FBENCRYPT_KEY_SIZE];
bzero(cKey, sizeof(cKey));
[key getBytes:cKey length:FBENCRYPT_KEY_SIZE];
// setup iv
char cIv[FBENCRYPT_BLOCK_SIZE];
bzero(cIv, FBENCRYPT_BLOCK_SIZE);
if (iv) {
[iv getBytes:cIv length:FBENCRYPT_BLOCK_SIZE];
}
NSData* Result = nil;
// setup output buffer
size_t bufferSize = [data length] + FBENCRYPT_BLOCK_SIZE;
void *buffer = malloc(bufferSize);
// do decrypt
size_t decryptedSize = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
cKey,
kCCKeySizeAES256,
cIv,
[data bytes],
[data length],
buffer,
bufferSize,
&decryptedSize);
if (cryptStatus == kCCSuccess) {
result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
} else {
free(buffer);
NSLog(#"[ERROR] failed to decrypt| CCCryptoStatus: %d", cryptStatus);
}
return result;
}
Android:
byte[] decryptData(byte[] data, byte[] key, byte[] iv)
{
static int FBENCRYPT_BLOCK_SIZE = 16; //(kCCBlockSizeAES128)
static int FBENCRYPT_KEY_SIZE = 32; //(kCCKeySizeAES256)
// setup key
byte[] cKey = new byte[FBENCRYPT_KEY_SIZE];
Arrays.fill(cKey, (byte) 0x00);
int num = FBENCRYPT_KEY_SIZE;
if (key.length<num)
num = key.length;
System.arraycopy(key, 0, cKey, 0, num);
// setup iv
byte[] cIv = new byte[FBENCRYPT_BLOCK_SIZE];
Arrays.fill(cIv, (byte) 0x00);
if (iv!=null) {
System.arraycopy(iv, 0, cIv, 0, iv.length);
}
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SecretKeySpec skeySpec = new SecretKeySpec(cKey, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(cIv);
aesCipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
byte[] byteCipherText = aesCipher.doFinal(data);
return byteCipherText;
}
Server C#:
public enum AESBitCounts
{
AES64Bit = 8,
AES128Bit = 16,
AES256Bit = 32
}
public static byte[] Encrypt(byte[] RawPayload, byte[] key, AESBitCounts AESBitCount)
{
Symmetric sym = new Symmetric(Symmetric.Provider.Rijndael, false);
//sym.mcrypto.Padding = System.Security.Cryptography.PaddingMode.None;
sym.IntializationVector = new Data(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 });
Data deckey = new Data();
deckey.MinBytes = (Int32)AESBitCount;
deckey.MaxBytes = (Int32)AESBitCount;
deckey.Bytes = key;
Byte fred = deckey.Bytes[0];
Data encrypted = sym.Encrypt(new Data(RawPayload), deckey);
return encrypted.Bytes;
}
It turns out that there is one crucial differance between CCCrypt and Cipher, CCCrypt will return whatever data it is able to decrypt, while Cipher, once it get's a padding error, will not return any data. It seems that my client's server side script was mangling the padding block so that any message shorter than 16 bytes was being decrypted on iOS as null, and in the case of the main message was simply dropping the padding block. I was able to duplicate the result of the iOS code by chopping the last 16 bytes before decoding, returning null if no bytes are left, and switching from using Cipher.doFinal to Cipher.update, which doesn't expect a final padding block to be present.
Edit: I actually didn't need to eliminate the last 16 bytes. Just switching from doFinal to update does the trick.
I'm trying do some encrypt something using 3des on the iOS that must match the results from java and .NET.
Java code is :
public class EncryptionHelper {
// Encrypts string and encode in Base64
public static String encryptText(String plainText,String key, String IV) throws Exception {
// ---- Use specified 3DES key and IV from other source --------------
byte[] plaintext = plainText.getBytes();//input
byte[] tdesKeyData = key.getBytes();// your encryption key
byte[] myIV = IV.getBytes();// initialization vector
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(myIV);
c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
byte[] cipherText = c3des.doFinal(plaintext);
String encryptedString = Base64.encodeToString(cipherText,
Base64.DEFAULT);
// return Base64Coder.encodeString(new String(cipherText));
return encryptedString;
}
}
and iOS code for the same is :
-(NSString*)new3DESwithoperand:(NSString*)plaintext encryptOrDecrypt:(CCOperation)encryptorDecrypt key:(NSString*)key initVec:(NSString*)initVec
{
NSData* data = [plaintext dataUsingEncoding:NSUTF8StringEncoding];
const void *vplainText = [data bytes];;
size_t plainTextBufferSize = [data length];
NSLog(#"%#, Length: %u",[data description],[data length]);
size_t bufferPtrSize = (plainTextBufferSize + kCCBlockSize3DES) & ~(kCCBlockSize3DES - 1);
NSLog(#"%zu, sizof of uint8_t: %zu",bufferPtrSize, sizeof(uint8_t));
size_t movedBytes = 0;
uint8_t *bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t));
NSLog(#"%zu",sizeof(bufferPtr));
memset((void*)bufferPtr, 0x0, bufferPtrSize);
NSLog(#"%zu",sizeof(bufferPtr));
const void * vkey = [[NSData base64DataFromString:key] bytes];
const void *vinitVec = [[NSData base64DataFromString:initVec] bytes];
NSLog(#"vinitvec: %#",[[NSData base64DataFromString:initVec] description]);
CCCryptorStatus ccStatus;
ccStatus = CCCrypt(encryptorDecrypt,
kCCAlgorithm3DES,
kCCOptionPKCS7Padding & kCCModeCBC,
vkey,
kCCKeySize3DES,
vinitVec,
vplainText,
plainTextBufferSize,
(void*)bufferPtr,
bufferPtrSize,
&movedBytes);
NSData* result = [NSData dataWithBytes:(const void*)bufferPtr length:(NSUInteger)movedBytes];
NSString* str = [NSString base64StringFromData:result length:result.length];
NSLog(#"%#",str);
return str;
}
This code successfully encrypts and decrypts a string. However, it does not match the results from .NET and java.
Thank you
Have found a solution for the above problem of encryption value generated different on iOS and .NET or Java.
Solution:
1. In Android and .NET you have to use a key of size 16 Bytes (eg: key="1234567890123456")
In iOS you need to use a key size of 24 bytes but the generation of key makes a little difference.
Use the same key as used in Android or .NET (16 bytes) and append it with the first 8 Bytes of the same key.
key16Byte = "1234567890123456" //Android and .NET key
key24Byte = key16Byte + "12345678" //ios and Java key, Replicated first 8 Bytes of 16Byte key
//new24ByteKey = "123456789012345612345678"
Remove "& kCCModeCBC" from CCypher Mode.
Some values require bytes in CCCrypt function which I have changed in the below mentioned code below. like keyData, encryptData.
Reason for different encryption generated:
Android and .NET - It takes 16Byte key and internally replicates, and generates a 24Byte key.
Java - It throws an Exception "Invalid key length", if you provide a 16Byte key value.
iOS - It generates encryption value with 16Byte and 24Byte both values without throwing any exception, so is the reason we get a different encryption generated in case of 16Byte key.
Java Code
public class EncryptionHelper {
// Encrypts string and encode in Base64
public static String encryptText(String plainText,String key, String IV) throws Exception {
// ---- Use specified 3DES key and IV from other source --------------
byte[] plaintext = plainText.getBytes();//input
byte[] tdesKeyData = key.getBytes();// your encryption key
byte[] myIV = IV.getBytes();// initialization vector
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(myIV);
c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
byte[] cipherText = c3des.doFinal(plaintext);
String encryptedString = Base64.encodeToString(cipherText,
Base64.DEFAULT);
// return Base64Coder.encodeString(new String(cipherText));
return encryptedString;
}
iOS Code:
- (NSString *)encrypt:(NSString *)encryptValue key:(NSString *)key24Byte IV:(NSString *)IV{
// first of all we need to prepare key
if([key length] != 24)
return #"Require 24 byte key, call function generate24ByteKeySameAsAndroidDotNet with a 16Byte key same as used in Android and .NET"; //temporary error message
NSData *keyData = [key24Byte dataUsingEncoding:NSUTF8StringEncoding];
// our key is ready, let's prepare other buffers and moved bytes length
NSData *encryptData = [encryptValue dataUsingEncoding:NSUTF8StringEncoding];
size_t resultBufferSize = [encryptData length] + kCCBlockSize3DES;
unsigned char resultBuffer[resultBufferSize];
size_t moved = 0;
// DES-CBC requires an explicit Initialization Vector (IV)
// IV - second half of md5 key
NSMutableData *ivData = [[IV dataUsingEncoding:NSUTF8StringEncoding]mutableCopy];
NSMutableData *iv = [NSMutableData dataWithData:ivData];
CCCryptorStatus cryptorStatus = CCCrypt(kCCEncrypt, kCCAlgorithm3DES,
kCCOptionPKCS7Padding , [keyData bytes],
kCCKeySize3DES, [iv bytes],
[encryptData bytes], [encryptData length],
resultBuffer, resultBufferSize, &moved);
if (cryptorStatus == kCCSuccess) {
return [[NSData dataWithBytes:resultBuffer length:moved] base64EncodedStringWithOptions:0];
} else {
return nil;
}
}
iOS
-(NSString *)generate24ByteKeySameAsAndroidDotNet:(NSString *)key{
NSString *new24ByteKey = key;
;
new24ByteKey = [new24ByteKey stringByAppendingString:[key substringWithRange:NSMakeRange(0, 8)]];
return new24ByteKey;
}
As #Jugal Desai mentioned, the key difference between iOS and Android/.Net is that the later ones automatically fill the key (with size 16 bytes) with another 8 bytes from the start of the key! You saved me :)
Here I provide the simple fix in Swift 3:
....
YOUR_KEY_SIZE_16 = YOUR_KEY_SIZE_16 + YOUR_KEY_SIZE_16[0...7]
....
Sample complete code (MD5 for key hash + ECB + PKCS7Padding) with base64 result:
func tripleDesEncrypt(keyString: String, pass: String) -> String{
let keyData = keyString.data(using: .utf8)!
var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
_ = digestData.withUnsafeMutableBytes {digestBytes in
keyData.withUnsafeBytes {messageBytes in
CC_MD5(messageBytes, CC_LONG(keyData.count), digestBytes)
}
}
digestData = digestData + digestData[0...7]
let data = pass.data(using: .utf8)!
let dataNS = data as NSData
let cryptData = NSMutableData(length: Int(dataNS.length) + kCCBlockSize3DES)!
let keyLength = size_t(kCCKeySize3DES)
let operation: CCOperation = UInt32(kCCEncrypt)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithm3DES)
let options: CCOptions = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)
var numBytesEncrypted :size_t = 0
let cryptStatus = CCCrypt(operation,
algoritm,
options,
(digestData as NSData).bytes,
keyLength,
nil,
dataNS.bytes,
dataNS.length,
cryptData.mutableBytes,
cryptData.length,
&numBytesEncrypted)
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.length = Int(numBytesEncrypted)
// Not all data is a UTF-8 string so Base64 is used
let base64cryptString = cryptData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength76Characters)
return base64cryptString
} else {
print("Error: \(cryptStatus)")
}
return ""
}
How serious in decrypted?
- (NSString *)encrypt:(NSString *)encryptValue key:(NSString *)key24Byte IV:(NSString *)IV{
// first of all we need to prepare key
if([key length] != 24)
return #"Require 24 byte key, call function generate24ByteKeySameAsAndroidDotNet with a 16Byte key same as used in Android and .NET"; //temporary error message
NSData *keyData = [key24Byte dataUsingEncoding:NSUTF8StringEncoding];
// our key is ready, let's prepare other buffers and moved bytes length
NSData *encryptData = [encryptValue dataUsingEncoding:NSUTF8StringEncoding];
size_t resultBufferSize = [encryptData length] + kCCBlockSize3DES;
unsigned char resultBuffer[resultBufferSize];
size_t moved = 0;
// DES-CBC requires an explicit Initialization Vector (IV)
// IV - second half of md5 key
NSMutableData *ivData = [[IV dataUsingEncoding:NSUTF8StringEncoding]mutableCopy];
NSMutableData *iv = [NSMutableData dataWithData:ivData];
CCCryptorStatus cryptorStatus = CCCrypt(kCCEncrypt, kCCAlgorithm3DES,
kCCOptionPKCS7Padding , [keyData bytes],
kCCKeySize3DES, [iv bytes],
[encryptData bytes], [encryptData length],
resultBuffer, resultBufferSize, &moved);
if (cryptorStatus == kCCSuccess) {
return [[NSData dataWithBytes:resultBuffer length:moved] base64EncodedStringWithOptions:0];
} else {
return nil;
}
}