hohl / MIHCrypto

OpenSSL wrapper for Objective-C [cryptography]
MIT License
340 stars 66 forks source link

Question: RSA key in blocks #29

Open scastro opened 9 years ago

scastro commented 9 years ago

Hi,

I have to exchange keys with the server (in JAVA using bouncycastle) and my iOS app. So the server has the key size issue and send me the key in blocks (each one encrypted with my public key). The problem is that when I want to decrypt one of these blocks I get the error

"Size of data to encrypt must not exceed size of RSA key. ...."

But I don't really understand why because each block is smaller that the size of my key. Here is my code:

MIHRSAKeyFactory* factory = [[MIHRSAKeyFactory alloc] init];
factory.preferedKeySize = MIHRSAKey1024;
MIHKeyPair *keyPair = [factory generateKeyPair];
clientPrivateKey = keyPair.private;
clientPublicKey = keyPair.public;
...
[self postOperation:@"exchangekey" params:params onCompletion:^(NSDictionary *json)
 {
     NSError *error = nil;
     NSString* dataStr = [[[json objectForKey:@"chains"] objectAtIndex:0] objectForKey:@"chain"];
     NSData* data2 = [dataStr dataUsingEncoding:NSUTF8StringEncoding];
     [self log:[NSString stringWithFormat:@"Server response (%ld): %@",data2.length,data2]];
     //NSData* data = [NSData dataWithData:[[[json objectForKey:@"chains"] objectAtIndex:0] objectForKey:@"chain"]];
     [clientPrivateKey decrypt:data2 error:&error];
     [self log:[NSString stringWithFormat:@"Decript error: %@",error.description]];
 }];

And the log shows this

Server response (256): <37653734 38363964 65386134 36663930 66656530 35356230 65393937 64626432 66343032 65333061 35373437 62626361 34316133 61643262 39373037 36353230 65343765 66656266 33666433 63323537 62303634 39613461 30613034 37666161 32303862 34313331 30353239 30323163 35303966 39333833 30383332 64366662 65323433 33623862 33326532 61616330 62613239 63653161 33356230 39373831 65316430 33373961 33623838 30633532 37393732 39663235 63363332 34653732 32333031 64326662 35366264 33633835 61323533 37363235 35613836 30613863 65396533 33633835 61363330 35666333 33386263 63383962 30636632 30336535> Decript error: Error Domain=MIHOpenSSLErrorDomain Code=67522668 "Size of data to encrypt must not exceed size of RSA key. If you want to securly encrypt large blocks of data combine RSA with AES. (See https://github.com/hohl/MIHCrypto/issues/24 for more details about that topic.)" UserInfo=0x7fb4edb8f390 {NSLocalizedDescription=Size of data to encrypt must not exceed size of RSA key. If you want to securly encrypt large blocks of data combine RSA with AES. (See https://github.com/hohl/MIHCrypto/issues/24 for more details about that topic.)}

Any ideas on what I'm doing wrong?

Is my first try with this lib and this kind of encryption so apologies in advance if this is a simple newbie error.

hohl commented 9 years ago

(Due to my own experience BouncyCastle and MIHCrypto work very well together. So don't worry about that.)

Your server response is 256 bytes long (which is equal to 2048 bits) while the key you used is 1024 bits long. This means the server has encrypted your message with a longer key then clientPublicKey. Maybe you are using the wrong key at server side or something went wrong during the transmission of the public key.

hohl commented 9 years ago

Some other idea: Are you splitting your data into blocks of 1024 bytes before transmitting? Because that wouldn't work as expected. In RSA (at least in RSA_PKCS1_OAEP_PADDING mode, which is enabled per default) an random padding is added at the beginning and end of every encoded data package. So you loose some additional bytes. (For example when having a 1024 bits key, you can at maximum encrypt data of 983 bits, 41 bits are lost for padding.)

In addition to that, you need to know that the handling of long data is different in BouncyCastle and OpenSSL (which is the base for MIHCrypto). BouncyCastle will automatically split the data into packages of the maximum size and encrypt all of them, while OpenSSL stop and return an error.

Encrypting a lot of packages with the same key may be abused to leak the key itself. Because of this in general you will use RSA for key exchange only and use some symmetric cryptography to encrypt the data itself with the exchanged key.

scastro commented 9 years ago

Thanks @hohl for all the info.

I changed the approach and do this. Send the public key to the server, encrypt the "hello" word in the app and the server with the same public key, and compare the bytes arrays (NSData vs HexArray) and are different....

Any ideas why this could happen? It should be the same right?

scastro commented 9 years ago

Ok, I think we got it running. I will post the code later so it will help someone else with the same problem, basically a newbie like me :)

indiandragon commented 8 years ago

Hi @scastro , it would be great if you could post your code as many come here to look for implementation criteria regarding bouncy castle on java & MIH in iOS.

scastro commented 8 years ago

I totally forgot about this, thanks for the reminder @indiandragon!

I can't share the entire code do to NDA stuff but I will share as much as I can. I don't have access to the Java code in the server, so I will share as much as I can from the iOS app.

As I mentioned before my problem was to gather the "chunks" and build the keys to interact with the server. Send my public key and retrieve server public key from those chunks.

First, create all the keys needed:

    MIHRSAKeyFactory* factory = [[MIHRSAKeyFactory alloc] init];
    factory.preferedKeySize = MIHRSAKey1024;
    MIHKeyPair *keyPair = [factory generateKeyPair];
    MIHRSAPrivateKey* _clientPrivateKey = keyPair.private;
    MIHRSAPublicKey* _clientPublicKey = keyPair.public;

Then just get all the chunks from a JSON, this is basically read the chunks and make an NSArray. Each chunk is encrypted with my public key on the server and send it over to the app, so I have to decrypt each part to build the public server key.

To decrypt each chunk I do something like this:

    NSData* data = [self dataFromHexString:str];
    NSData *decryptedData = [_clientPrivateKey decrypt:data error:&error];
    if (error){
        NSLog(@"RSAManager - Error : %@",error);
        return nil;
    }
    NSString *decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];

Then must gather all this chunks and build the public server key. Suppose you make an NSArray of strings with all the decrypted chunks, so you have to do something like this:

    NSString* key = @"";
    for (NSString* item in chain) {
        key = [key stringByAppendingString:[item stringByReplacingOccurrencesOfString:@"\n" withString:@""]];
        key = [key stringByReplacingOccurrencesOfString:@"-----BEGIN PUBLIC KEY-----" withString:@""];
        key = [key stringByReplacingOccurrencesOfString:@"-----END PUBLIC KEY-----" withString:@""];
    }

I have to add that stringByReplacingOccurrencesOfString in order to get the keys working, because each chunk came with that data. Probably there is a better way of doing this but in my tests this was the only that worked without issues.

After this, you just need to build the server public key:

    NSData* pem = [NSData MIH_dataByBase64DecodingString:str];
    //[_serverPublicKey setRsaPadding:RSA_PKCS1_OAEP_PADDING];
    MIHRSAPublicKey* _serverPublicKey = [[MIHRSAPublicKey alloc] initWithData:pem];

Padding depends on your configuration and the encryption used in the server, I just put it there so you can change that in the app, pretty simple.

After this is just send and receive encrypted packages. In my case, the encryption is only needed when the app sends some data (the responses were always raw info and don't need the entire RSA process). So, if you want to encrypt an string and send it over to the server, I built a method that gets an string and return the array of chunks to send to the server:

- (NSArray *)encryptStringForServer:(NSString*)str {
    NSError *error = nil;
    NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
    NSData* encryptedData = [_serverPublicKey encrypt:data error:&error];
    if (error){
        NSLog(@"RSAManager - Error : %@",error);
        return nil;
    }

    NSUInteger length = [encryptedData length];
    NSUInteger chunkSize = 100;
    NSUInteger offset = 0;
    NSMutableArray* chunksArray = [NSMutableArray array];
    do {
        NSUInteger thisChunkSize = length - offset > chunkSize ? chunkSize : length - offset;
        NSData* chunk = [NSData dataWithBytesNoCopy:(char *)[encryptedData bytes] + offset
                                             length:thisChunkSize
                                       freeWhenDone:NO];
        offset += thisChunkSize;
        [chunksArray addObject:chunk];
    } while (offset < length);

    NSMutableArray* result = [NSMutableArray array];
    for (NSData* chunk in chunksArray) {
        NSData* encryptedData = [_clientPrivateKey encrypt:chunk error:&error];
        if (error){
            NSLog(@"RSAManager - Error : %@",error);
            return nil;
        }
        [result addObject:[self NSDataToHex:encryptedData]];
    }
    return result;
}

I'm a completely newbie on the subject so probably there are better ways to solve this, this is just how I resolve this on my first app with this kind of encryption. Let me know if I can help in any other way.

indiandragon commented 8 years ago

@scastro That's great of you to respond to my request immediately, I'll check with your implementation and revert if I need any clarifications from you.