I have two scenarios of RSA encryption:
Key pair generated by c# app. Public key sent to iOS. iOS app encrypts data with the key and sends it back. The c# app decrypts using the private key. --- Completely implemented and working.
Key pair generated by ios app. Public key sent to c# app. The c# app encrypts the data, base 64 encodes it and sends it to the ios app. The ios app then decrypts the data using the private key. --- Not working.
If I encrypt using the public and private key pair generated on ios it all works, but as soon as I replace it with the base 64 encoded string from c# it fails. I have tried all combinations of padding.
The code is as follows:
SecPadding padding = kSecPaddingPKCS1;
NSMutableArray *keys = [self generateRSAKeyWithKeySizeInBits:1024 publicKeyTag:#"RSA Public Key" privateKeyTag:#"RSA Private Key"];
SecKeyRef test_publicKey = (__bridge SecKeyRef)(keys[0]);
SecKeyRef test_privateKey = (__bridge SecKeyRef)(keys[1]);
const char *sampled_utf8 = "VnWBW/xRyJB48Uxjdl99apczCuS07zhnLvIjnqyZIQqbI4F7kyAezfD1MNlgeTefkHuCRuzogaQTamk2XRwXoBoGy3Agj4ocPK2Wa7vWNGip8X3FAo1eJL+xKoVoqre/ipDjnZNfEUbX91Ru+IqWkbZXD2POlFfuMaTatCl50+U=";
NSString *sampled = [[NSString alloc] initWithUTF8String:sampled_utf8];
Byte sampled_inputData [[sampled lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];//prepare a Byte[]
[[sampled dataUsingEncoding:NSUTF8StringEncoding] getBytes:sampled_inputData];//get the pointer of the data
size_t sampled_inputDataSize = (size_t)[sampled length];
size_t sampled_outputDataSize = EstimateBas64DecodedDataSize(sampled_inputDataSize);//calculate the decoded data size
Byte sampled_outputData[sampled_outputDataSize];//prepare a Byte[] for the decoded data
Base64DecodeData(sampled_inputData, sampled_inputDataSize, sampled_outputData, &sampled_outputDataSize);//decode the data
NSData *sampled_Data = [[NSData alloc] initWithBytes:sampled_outputData length:sampled_outputDataSize];//create a NSData
NSData *test_encrypted_Data = [self encryptString:encoded_toEncrypt RSAPublicKey:test_publicKey padding:padding];
size_t test_encrypted_inputDataSize = (size_t)[test_encrypted_Data length];
Byte *test_encrypted_inputData = (Byte*) malloc(test_encrypted_inputDataSize);
memcpy(test_encrypted_inputData,[test_encrypted_Data bytes],test_encrypted_inputDataSize);
size_t test_encrypted_outputDataSize = EstimateBas64EncodedDataSize(test_encrypted_inputDataSize);//calculate the encoded data size
char* test_encrypted_outputData[test_encrypted_outputDataSize];//prepare a char for the encoded data
Base64EncodeData(test_encrypted_inputData, test_encrypted_inputDataSize, test_encrypted_outputData, &test_encrypted_outputDataSize,false);//encode the data
NSData *test_encrypted_Encoded = [[NSData alloc] initWithBytes:test_encrypted_outputData length:test_encrypted_outputDataSize];//create a NSData object from the decoded data
size_t input_Size = (size_t)[test_encrypted_Encoded length];
Byte *input = (Byte*) malloc(input_Size);
memcpy(input,[test_encrypted_Encoded bytes],input_Size);
size_t output_Size = EstimateBas64DecodedDataSize(input_Size);
char* output[output_Size];
Base64DecodeData(input, input_Size, output, &output_Size);
NSData *res = [[NSData alloc] initWithBytes:output length:output_Size];
NSData *test_decryptedData = [self decryptString:sampled_Data RSAPrivateKey:test_privateKey padding:padding];
-(NSData*)decryptString:(NSData*)original RSAPrivateKey:(SecKeyRef)privateKey padding: (SecPadding)padding
{
#try
{
const unsigned char* original_String = (unsigned char *)[original bytes];
size_t decryptedLength = SecKeyGetBlockSize(privateKey);
uint8_t decrypted[decryptedLength];
OSStatus status = SecKeyDecrypt(privateKey,
padding,
original_String,
[original length],
decrypted,
&decryptedLength);
NSLog(#"result = %#", [self fetchStatus:status]);
if(status == noErr)
{
NSData* decryptedData = [[NSData alloc] initWithBytes:(const void*)decrypted length:decryptedLength];
return decryptedData;
}
else
return nil;
}
#catch (NSException * e)
{
//do nothing
NSLog(#"exception: %#", [e reason]);
}
return nil;
}
- (NSData*)encryptString:(NSString*)original RSAPublicKey:(SecKeyRef)publicKey1 padding:(SecPadding)padding
{
#try
{
Byte encrypt_inputData [[original lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];//prepare a Byte[]
[[original dataUsingEncoding:NSUTF8StringEncoding] getBytes:encrypt_inputData];//get the pointer of the data
size_t encrypt_inputDataSize = (size_t)[original length];
size_t encrypt_outputDataSize = EstimateBas64DecodedDataSize(encrypt_inputDataSize);//calculate the decoded data size
Byte encrypt_outputData[encrypt_outputDataSize];//prepare a Byte[] for the decoded data
Base64DecodeData(encrypt_inputData, encrypt_inputDataSize, encrypt_outputData, &encrypt_outputDataSize);//decode the data
NSData *encryption_Data = [[NSData alloc] initWithBytes:encrypt_outputData length:encrypt_outputDataSize];//create a NSData object from the decoded data
size_t encryptedLength = SecKeyGetBlockSize(publicKey1);
uint8_t* sample = (uint8_t*)[encryption_Data bytes];
size_t text_Size = [encryption_Data length];
uint8_t *encrypted_Data_Bytes;
encrypted_Data_Bytes = malloc(sizeof(uint8_t)*encryptedLength);
memset(encrypted_Data_Bytes,0,encryptedLength);
OSStatus status = SecKeyEncrypt(publicKey1,
padding,
sample,
text_Size,
&encrypted_Data_Bytes[0],
&encryptedLength);
if(status == noErr)
{
NSData* encryptedData = [[NSData alloc] initWithBytes:(const void*)encrypted_Data_Bytes length:encryptedLength];
return encryptedData;
}
else
return nil;
}
#catch (NSException * e)
{
//do nothing
NSLog(#"exception: %#", [e reason]);
}
return nil;
}
Related
I am working on an encryption method in the iPhone with the RSA encryption method, so far i could achieve getting the encryption string with this method, the string is successfully decrypted by the server.
SecKeyRef keyRef = [self addPublicKey:pubKey];
SecKeyAlgorithm algorithm = kSecKeyAlgorithmRSAEncryptionOAEPSHA256;
if (!keyRef) {
return nil;
}
BOOL canEncrypt = SecKeyIsAlgorithmSupported(keyRef, kSecKeyOperationTypeEncrypt, algorithm);
if (canEncrypt) {
CFErrorRef error = NULL;
NSData *encryptedData = (NSData *)CFBridgingRelease(
SecKeyCreateEncryptedData(keyRef, algorithm, (__bridge CFDataRef) content, &error)
);
if (encryptedData) {
return encryptedData;
}else{
NSError *err = CFBridgingRelease(error);
NSLog(#"Ocurrió un error %#", err.localizedDescription);
return nil;
}
}
This method works for ios 10 and newer, what i need is to know how to set the algorithm in prior ios versions, my code is the following
SecKeyRef keyRef = [self addPublicKey:pubKey];
if (!keyRef) {
return nil;
}
size_t cipherBufferSize = SecKeyGetBlockSize(keyRef);
uint8_t *cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
memset((void *)cipherBuffer, 0*0, cipherBufferSize);
NSData *plainTextBytes = content;
size_t blockSize = cipherBufferSize - 11;
size_t blockCount = (size_t)ceil([plainTextBytes length] / (double)blockSize);
NSMutableData *encryptedData = [NSMutableData dataWithCapacity:0];
for (int i=0; i<blockCount; i++) {
int bufferSize = (int)MIN(blockSize,[plainTextBytes length] - i * blockSize);
NSData *buffer = [plainTextBytes subdataWithRange:NSMakeRange(i * blockSize, bufferSize)];
OSStatus status = SecKeyEncrypt(keyRef,
kSecPaddingOAEP,
(const uint8_t *)[buffer bytes],
[buffer length],
cipherBuffer,
&cipherBufferSize);
if (status == noErr){
NSData *encryptedBytes = [NSData dataWithBytes:(const void *)cipherBuffer length:cipherBufferSize];
[encryptedData appendData:encryptedBytes];
}else{
if (cipherBuffer) {
free(cipherBuffer);
}
return nil;
}
}
if (cipherBuffer) free(cipherBuffer);
So far i can see that in the version of ios 10 you can set the algorithm with this line
SecKeyAlgorithm algorithm = kSecKeyAlgorithmRSAEncryptionOAEPSHA256;
my question is, how do i get that algorithm in the early version of ios, the second code i post can't be decrypted.
Thanks for your help
If you are using OAEP padding with SecKeyEncrypt, you can only use kSecPaddingOAEP, which is SHA1. Unfortunately you cannot use OAEP SHA256 with SecKeyEncrypt.
I'm not an expert about this at all, so I've found a wrapper on github called RSA and tried to use it.
What I need to do is simply encrypt a string using my private key.
This is the method used to do the encryption:
- (NSString *)rsaEncryptWithData:(NSData*)data usingPublicKey:(BOOL)yes {
if (yes) {
[self getKeyRefFor:publicTag];
} else {
[self getKeyRefFor:privateTag];
}
}
SecKeyRef key = self.publicKeyRef;
size_t cipherBufferSize = SecKeyGetBlockSize(key);
uint8_t *cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
memset((void *)cipherBuffer, 0*0, cipherBufferSize);
NSData *plainTextBytes = data;
size_t blockSize = cipherBufferSize - 11;
size_t blockCount = (size_t)ceil([plainTextBytes length] / (double)blockSize);
NSMutableData *encryptedData = [NSMutableData dataWithCapacity:0];
for (int i=0; i<blockCount; i++) {
int bufferSize = (int)MIN(blockSize,[plainTextBytes length] - i * blockSize);
NSData *buffer = [plainTextBytes subdataWithRange:NSMakeRange(i * blockSize, bufferSize)];
OSStatus status = SecKeyEncrypt(key,
kSecPaddingPKCS1,
(const uint8_t *)[buffer bytes],
[buffer length],
cipherBuffer,
&cipherBufferSize);
if (status == noErr){
NSData *encryptedBytes = [NSData dataWithBytes:(const void *)cipherBuffer length:cipherBufferSize];
[encryptedData appendData:encryptedBytes];
}else{
if (cipherBuffer) {
free(cipherBuffer);
}
return nil;
}
}
if (cipherBuffer) free(cipherBuffer);
return [encryptedData base64EncodedStringWithOptions:0];
}
If I use my public key to encrypt, everything is working fine, but if I try to use my private key then the OSStatus variable returns a -4 (Function or operation not implemented.).
Any help would be really appreciated, since I really don't know what to do.
Thanks.
I ended up using the MIHCrypto framework to create a key pair and to sign my messages.
Is this easy:
- (NSData *)signMessageData:(NSData *)data withKey:(MIHRSAPrivateKey *)privKey {
NSError *signingError = nil;
NSData *signatureData = [privKey signWithSHA256:data error:&signingError];
if (signingError != nil) {
NSLog(#"Error: %#", signingError.localizedDescription);
}
return signatureData;
}
I'm trying to encrypt data with an RSA public key using openssl.
I have the Java implementation of what I need to do in Objective-C.
Here's what I have so far:
- (RSA *)rsaFromExponent:(NSString *)exponent modulus:(NSString *)modulus
{
RSA *rsa_pub = RSA_new();
const char *N = [modulus UTF8String];
const char *E = [exponent UTF8String];
if (!BN_hex2bn(&rsa_pub->n, N))
{
// TODO
}
printf("N: %s\n", N);
printf("n: %s\n", BN_bn2dec(rsa_pub->n));
if (!BN_hex2bn(&rsa_pub->e, E))
{
// TODO
}
printf("E: %s\n", E);
printf("e: %s\n", BN_bn2dec(rsa_pub->e));
return rsa_pub;
}
- (NSString *)cleanString:(NSString *)input
{
NSString *output = input;
output = [output stringByReplacingOccurrencesOfString:#"<" withString:#""];
output = [output stringByReplacingOccurrencesOfString:#">" withString:#""];
output = [output stringByReplacingOccurrencesOfString:#" " withString:#""];
return output;
}
// main code
NSString *exponentB64 = #"AQAB";
NSString *modulusB64 = #"AKDbnFpblq7LHfWDfGTR48B34MKaHQosMwVu8cCc6fH2pZ8Ypx/OgzG6VJlKHXeELtlo5tddBSJpwnkEQdvkkmwuOpCkacTTLon6EHqX4WwFW+waqHxmj419SxiDDlo9tsbg7vfFIMpKyGzq1zvTAN3TroW+MxogZfZD3/N6dNTzvBoXe/Ca1e/zVwYXKbiegLMjNwsruz/WvuMiNKTK4U3GEmb0gIODd1shAH10ube8Nrz/e1u9kr25VQ+7kZAFjnkPTp2AvNGYHQt35m1TRMQhTylVwTZqFkHC/jMt7WxuS8q7ftjM828wa1fEWTgWYrdkzmqZSK5CHBYSys/N1Ws=";
// 1. decode base64 (http://projectswithlove.com/projects/NSData_Base64.zip)
NSData *exponent = [NSData dataFromBase64String:exponentB64];
NSData *modulus = [NSData dataFromBase64String:modulusB64];
NSString *exponentHex = [self cleanString:[exponent description]];
NSString *modulusHex = [self cleanString:[modulus description]];
// 2. create RSA public key
RSA *rsa_pub = [self rsaFromExponent:exponentHex modulus:modulusHex];
NSString *user = #"TEST";
// 3. encode base 64
NSData *userData = [user dataUsingEncoding:NSASCIIStringEncoding];
NSString *userB64String = [userData base64EncodedString];
// 4. encrypt
const unsigned char *from = (const unsigned char *)[userB64String cStringUsingEncoding:NSASCIIStringEncoding];
int flen = strlen((const char *)from);
unsigned char *to = (unsigned char *) malloc(RSA_size(rsa_pub));
int padding = RSA_PKCS1_PADDING;
int result = RSA_public_encrypt(flen, from, to, rsa_pub, padding);
if (-1 == result)
{
NSLog(#"WAT?");
}
else
{
NSLog(#"from: %s", from); // echo VEVTVA==
NSLog(#"to: %s", to); // echo something strange with characters like: ~™Ÿû—...
}
// 5. encode base 64
NSString *cipherString = [NSString stringWithCString:(const char *)to
encoding:NSASCIIStringEncoding];
NSData *cipherData = [cipherString dataUsingEncoding:NSASCIIStringEncoding];
NSString *cipherDataB64 = [cipherData base64EncodedString];
NSLog(#"user encrypted b64: %#", cipherDataB64); // echo null :-(
In Java, I have no problem to base64 encode the encrypted data.
I'm sure I'm doing something wrong but I don't know where because it's not something I do everyday.
Or if you know another way to do this with iOS frameworks like Security.framework.
Thanks in advance.
Someone else helped me figure it out. I don't know why but I was assuming that the output buffer from RSA_public_encrypt function would be an ascii string. Though it's just bytes as the documentation says too. The char * type often leads me to think it's gonna store a string (it's so wrong I think it's the last time I make this kind of error).
So from step 5:
// 5. encode base 64
NSData *cipherData = [NSData dataWithBytes:(const void *)to length:result];
NSString *cipherDataB64 = [cipherData base64EncodedString];
NSLog(#"user encrypted b64: %#", cipherDataB64); // now echo the expected value
I implemented a category method on the NSData class which returns a signature of the data using an SHA-1 hash and subsequent encryption with a private key as follows:
- (NSData *)signatureWithKey:(SecKeyRef)keyRef {
if (keyRef == NULL) {
return nil;
}
NSData *sha1Digest = [self dataWithSHA1Digest];
size_t maxLength = SecKeyGetBlockSize(keyRef) - 11;
if ([sha1Digest length] > maxLength) {
NSString *reason = [NSString stringWithFormat:#"Digest is too long to sign with this key, max length is %ld and actual length is %ld", maxLength, (unsigned long)[self length]];
NSException *ex = [NSException exceptionWithName:#"BMInvalidArgumentException" reason:reason userInfo:nil];
#throw ex;
}
#if TARGET_OS_IPHONE
OSStatus status = noErr;
uint8_t *plainBuffer = (uint8_t *)[sha1Digest bytes];
size_t plainBufferSize = [sha1Digest length];
size_t cipherBufferSize = SecKeyGetBlockSize(keyRef);
uint8_t *cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
status = SecKeyRawSign(keyRef,
kSecPaddingPKCS1SHA1,
plainBuffer,
plainBufferSize,
&cipherBuffer[0],
&cipherBufferSize
);
if (status == noErr) {
return [NSData dataWithBytesNoCopy:cipherBuffer length:cipherBufferSize freeWhenDone:YES];
}
free(cipherBuffer);
return nil;
#else
CFErrorRef error = NULL;
SecTransformRef signer = NULL;
CFTypeRef signature = NULL;
if ((signer = SecSignTransformCreate(keyRef, &error))) {
if (SecTransformSetAttribute(
signer,
kSecTransformInputAttributeName,
(CFDataRef)sha1Digest,
&error)) {
signature = SecTransformExecute(signer, &error);
}
}
if (error) {
LogWarn(#"Could not sign: %#", error);
CFRelease(error);
}
if (signer) {
CFRelease(signer);
}
if (signature) {
NSData *data = [NSData dataWithData:(NSData *)signature];
CFRelease(signature);
return data;
} else {
return nil;
}
#endif
}
Now the strange thing is that with the same private key (loaded from a p12 file) I get two different results for iOS and MacOSX when signing the same data. I am completely puzzled by this. You may notice the method above uses a different implementation for MacOSX using security transforms, but even if I use the iOS implementation on MacOSX (which gives a compile warning but works fine) I get the same result.
The method used for loading the private key from file is below:
+ (SecKeyRef)newPrivateKeyRefWithPassword:(NSString *)password fromData:(NSData *)data {
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
SecKeyRef privateKeyRef = NULL;
// Set the public key query dictionary
//change to your .pfx password here
[options setObject:password forKey:(id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((CFDataRef)data,
(CFDictionaryRef)options, &items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp =
(SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
if (securityError != noErr) {
privateKeyRef = NULL;
}
}
[options release];
if (items) CFRelease(items);
return privateKeyRef;
}
And this is the test case I use. Notice that two different strings are printed on iOS and MacOSX:
NSString *test = #"bla";
NSData *testData = [test dataUsingEncoding:NSUTF8StringEncoding];
NSString *p12Path= [[NSBundle mainBundle] pathForResource:#"private_key" ofType:#"p12"];
NSData *p12Data = [NSData dataWithContentsOfFile:p12Path];
SecKeyRef keyRef = [BMSecurityHelper newPrivateKeyRefWithPassword:#"xxxxxxxx" fromData:p12Data];
NSData *signatureData = [testData signatureWithKey:keyRef];
NSString *signatureString = [BMEncodingHelper base64EncodedStringForData:signatureData withLineLength:0];
if (keyRef) CFRelease(keyRef);
NSLog(#"signatureString: %#", signatureString);
It's always nice if you can answer your own question. I missed the following: under MacOSX the security transform also calculates the SHA-1 hash automatically, in contrast with the iOS implementation.
I fixed the problem by adding the following in the MacOSX implementation:
SecTransformSetAttribute(signer, kSecInputIsAttributeName, kSecInputIsDigest, &error)
I am trying encrypt data on an iPhone and send up the encrypted text to a web service for them to decrypt it. If the decryption works then it returns the First name in the xml as a confirmation things worked. Here is my Xcode
Note: The 'key' is the same in both xcode and web service
The information I want encrypted:
NSString *fnameencrypted = [[NSString alloc] AES256EncryptWithKey:f_name.text withKey:key]];
NSString *lnameencrypted = [[NSString alloc] AES256EncryptWithKey:l_name.text withKey:key]];
The NSString method
-(NSString *)AES256EncryptWithKey:(NSString *)plaintext withKey:(NSString *)key{
NSData *plainData = [plaintext dataUsingEncoding:NSASCIIStringEncoding];
NSData *encryptedData = [plainData AES256EncryptWithKey:key];
NSString *encryptedString = [encryptedData base64Encoding];
return encryptedString;
}
EDIT
The encryption method
-(NSData *)AES256EncryptWithKey:(NSString *)key{
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:NSASCIIStringEncoding];
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, kCCOptionECBMode + 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;
}
Here is my web service code
private static string Decrypt(string encryptedText, string completeEncodedKey, int keySize)
{
RijndealManaged aesEncryption = new RijndealManaged();
aesEncryption.KeySize = keySize; //keySize is 256
aesEncryption.BlockSize = 128;
aesEncryption.Mode = CipherMode.ECB;
aesEncryption.Padding = PaddingMode.PKCS7;
aesEncryption.IV = Convert.FromBase64String(ASCIIEncoding.ACSII.GetString(Convert.FromBase64String(completeEncodedString)).Split(',')[0]);
aesEncryption.Key = Convert.FromBase64String(ASCIIEncoding.ACSII.GetString(Convert.FromBase64String(completeEncodedString)).Split(',')[1]);
ICryptoTransform decrypto = aesEncryption.CreateDecryptor();
byte[] encryptedBytes = Convert.FromBase64CharArray(encryptedText.ToCharArray(), 0, encryptedText.Length);
return ASCIIEncoding.ASCII.GetString(decrypto.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
}
This code does not work because it returns
<response><return>0</return></response>