2

We are using this code to encrypt in Objective-C on the iPhone:

- (NSMutableData*) EncryptAES: (NSString *) key
{
    char keyPtr[kCCKeySizeAES128+1];
    bzero( keyPtr, sizeof(keyPtr) );

    [key getCString: keyPtr maxLength: sizeof(keyPtr) encoding: NSUTF8StringEncoding];
    size_t numBytesEncrypted = 0;

    NSUInteger dataLength = [self length];

    size_t bufferSize = dataLength + kCCBlockSizeAES128;
        void *buffer = malloc(bufferSize);


    NSMutableData *output = [[NSData alloc] init];


    CCCryptorStatus result = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                     keyPtr, kCCKeySizeAES128,
                                     NULL,
                                     [self mutableBytes], [self length],
                                     buffer, bufferSize,
                                     &numBytesEncrypted );

    output = [NSMutableData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
        if( result == kCCSuccess )
        {
                return output;
        }
    return NULL;
}

And trying to decrypt that using OpenSSL in Ruby as so:

aes = OpenSSL::Cipher::Cipher.new('AES-128-CBC')
aes.decrypt
aes.padding = 1
aes.iv = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0].pack('c*')
aes.key = key
result = aes.update(d) + aes.final

I'm willing to use ANY ruby gem. I can control both sides, but I haven't been able to get this to work with EzCrypto, openssl, or crypt.

Anyone know how to get these to work together?

ConsultUtah
  • 6,639
  • 3
  • 32
  • 51

4 Answers4

5

Your code is leaking the first allocation of output.

Besides that it looks mostly ok.

This is a complete end-to-end implementation using a SHA256 hash of the user's passphrase (in this case 'Salamander') and base64'ing the output. There is a PHP test implementation in the source that reconstructs the key then trims the PKCS7 padding before giving final output. A decryptor in Ruby follows, the PKCS7 padding removal happens automatically by the OpenSSL::Cipher.

Here you go:

// Crypto categories for iOS

#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonDigest.h>


@interface NSData( Crypto )

- (NSData *) aesEncryptedDataWithKey:(NSData *) key;
- (NSString *) base64Encoding;

@end


@interface NSString( Crypto )

- (NSData *) sha256;

@end


// --------


@implementation NSData( Crypto )

- (NSData *) aesEncryptedDataWithKey:(NSData *) key {
    unsigned char               *buffer = nil;
    size_t                      bufferSize;
    CCCryptorStatus             err;
    NSUInteger                  i, keyLength, plainTextLength;

    // make sure there's data to encrypt
    err = ( plainTextLength = [self length] ) == 0;

    // pass the user's passphrase through SHA256 to obtain 32 bytes
    // of key data.  Use all 32 bytes for an AES256 key or just the
    // first 16 for AES128.
    if ( ! err ) {
        switch ( ( keyLength = [key length] ) ) {
            case kCCKeySizeAES128:
            case kCCKeySizeAES256:                      break;

            // invalid key size
            default:                    err = 1;        break;
        }
    }

    // create an output buffer with room for pad bytes
    if ( ! err ) {
        bufferSize = kCCBlockSizeAES128 + plainTextLength + kCCBlockSizeAES128;     // iv + cipher + padding

        err = ! ( buffer = (unsigned char *) malloc( bufferSize ) );
    }

    // encrypt the data
    if ( ! err ) {
        srandomdev();

        // generate a random iv and prepend it to the output buffer.  the
        // decryptor needs to be aware of this.
        for ( i = 0; i < kCCBlockSizeAES128; ++i ) buffer[ i ] = random() & 0xff;

        err = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
            [key bytes], keyLength, buffer, [self bytes], plainTextLength,
            buffer + kCCBlockSizeAES128, bufferSize - kCCBlockSizeAES128, &bufferSize );
    }

    if ( err ) {
        if ( buffer ) free( buffer );

        return nil;
    }

    // dataWithBytesNoCopy takes ownership of buffer and will free() it
    // when the NSData object that owns it is released.
    return [NSData dataWithBytesNoCopy: buffer length: bufferSize + kCCBlockSizeAES128];
}

- (NSString *) base64Encoding {
    char                    *encoded, *r;
    const char              eTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    unsigned                i, l, n, t;
    UInt8                   *p, pad = '=';
    NSString                *result;

    p = (UInt8 *) [self bytes];
    if ( ! p || ( l = [self length] ) == 0 ) return @"";
    r = encoded = malloc( 4 * ( ( n = l / 3 ) + ( l % 3 ? 1 : 0 ) ) + 1 );

    if ( ! encoded ) return nil;

    for ( i = 0; i < n; ++i ) {
        t  = *p++ << 16;
        t |= *p++ << 8;
        t |= *p++;

        *r++ = eTable[ t >> 18 ];
        *r++ = eTable[ t >> 12 & 0x3f ];
        *r++ = eTable[ t >>  6 & 0x3f ];
        *r++ = eTable[ t       & 0x3f ];
    }

    if ( ( i = n * 3 ) < l ) {
        t = *p++ << 16;

        *r++ = eTable[ t >> 18 ];

        if ( ++i < l ) {
            t |= *p++ << 8;

            *r++ = eTable[ t >> 12 & 0x3f ];
            *r++ = eTable[ t >>  6 & 0x3f ];
        } else {
            *r++ = eTable[ t >> 12 & 0x3f ];
            *r++ = pad;
        }

        *r++ = pad;
    }

    *r = 0;

    result = [NSString stringWithUTF8String: encoded];

    free( encoded );

    return result;
}

@end


@implementation NSString( Crypto )

- (NSData *) sha256 {
    unsigned char               *buffer;

    if ( ! ( buffer = (unsigned char *) malloc( CC_SHA256_DIGEST_LENGTH ) ) ) return nil;

    CC_SHA256( [self UTF8String], [self lengthOfBytesUsingEncoding: NSUTF8StringEncoding], buffer );

    return [NSData dataWithBytesNoCopy: buffer length: CC_SHA256_DIGEST_LENGTH];
}

@end


// -----------------


@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    NSData              *plain = [@"This is a test of the emergency broadcast system." dataUsingEncoding: NSUTF8StringEncoding];
    NSData              *key = [NSData dataWithBytes: [[@"Salamander" sha256] bytes] length: kCCKeySizeAES128];
    NSData              *cipher = [plain aesEncryptedDataWithKey: key];
    NSString            *base64 = [cipher base64Encoding];

    NSLog( @"cipher: %@", base64 );

    // stuff the base64'ed cipher into decrypt.php:
    // http://localhost/~par/decrypt.php?cipher=<base64_output>

/*
<?php
    header( "content-type: text/plain" );

    if ( ! ( $cipher = $_GET[ 'cipher' ] ) ) {
        echo "no cipher parameter found";
        return;
    }

    echo "cipher: $cipher\n";

    $cipher = base64_decode( $cipher );
    $iv = substr( $cipher, 0, 16 );
    $cipher = substr( $cipher, 16 );

    // use the full key (all 32 bytes) for aes256
    $key = substr( hash( "sha256", "Salamander", true ), 0, 16 );

    $plainText = mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $key, $cipher, MCRYPT_MODE_CBC, $iv );
    $plainTextLength = strlen( $plainText );

    // strip pkcs7 padding
    $padding = ord( $plainText[ $plainTextLength - 1 ] );
    $plainText = substr( $plainText, 0, -$padding );

    printf( "plaintext: %s\n", $plainText );
?>
*/

    return YES;
}

@end

Decryption of the output of the above in Ruby:

require 'base64'
require 'openssl'

def decrypt( cipherBase64 )
    cipher = Base64.decode64( cipherBase64 )

    aes = OpenSSL::Cipher::Cipher.new( "aes-128-cbc" ).decrypt
    aes.iv = cipher.slice( 0, 16 )
    # don't slice the SHA256 output for AES256
    aes.key = ( Digest::SHA256.digest( 'Salamander' ) ).slice( 0, 16 )

    cipher = cipher.slice( 16..-1 )

    return aes.update( cipher ) + aes.final
end

text = '3o4ARWOxwmLEPgq3SJ3A2ws7sUSxMvWSKbbs+oABsOcywk+9qPBoDjhLAfAW/n28pbnsT2w5QMSye6pz3Lz8xmg5BYL8HdfKwbS9EpTbaUc='

print decrypt( text ) + "\n"
par
  • 17,361
  • 4
  • 65
  • 80
  • how can I prove if this encrypt/decrypt works, to prove in [link](http://www.tools4noobs.com/online_tools/decrypt/) tool online?. Because when I try with the text 3o4ARWOxwmLEPgq3SJ3A2ws7sUSxMvWSKbbs+oABsOcywk+9qPBoDjhLAfAW/n28pbnsT2w5QMSye6pz3Lz8xmg5BYL8HdfKwbS9EpTbaUc= , 'Salamander' as key, and method Rijndael-128 CBC, it doesn´t show a correct decrypt – Evaristoyok Apr 10 '12 at 18:29
  • Ok I made it, but when I decrypt in php with the code you give, the most of times live caracters junk, in php it looks like squares [] – Evaristoyok Apr 10 '12 at 21:10
  • Your inputs are correct (check the base64 box though) in your first comment, but it won't work using the online decryptor tool because of the way that page handles the key. My code assumes the average user will use a low-entropy key, e.g. "love", which at four characters gives us only 32 bits of key data (of which the high bit of each byte is zero, so really only 28 bits of entropy). AES/Rijndael-128 requires 128 bits of key data and AES-256 requires 256. To provide the necessary amount of key data I call SHA256 on the passphrase and use the bits it produces as the actual key. – par Apr 11 '12 at 01:50
  • Thanks for your answer, You are ok, I've made it and it works, about the base64 encode, I'm using the method - (NSString *) base64Encoding wich is in your code, for the implementation to NSData. But it give me somes caracters '/' and my partner told me that he needs the code without '/', I've check the base64 encode online and no one give '/'. do you know why? – Evaristoyok Apr 11 '12 at 15:30
  • I think you need to have your partner fix his Base64 decoder implementation. I wrote that base64 encoder years ago but it is correct. See https://en.wikipedia.org/wiki/Base64 - in particular the section that shows the Base64 index table. You'll see it corresponds 1:1 to the eTable (encoder table) member of my base64Encoding method. Apologies for the slowness of my replies, I'm deep in a project with a short deadline at the moment. – par Apr 13 '12 at 22:03
  • Alternatively you could modify the Base64 implementation to change the last two characters to something besides + and / (wikipedia lists numerous examples of variants), but you'll lose portability since you won't be compatible with the actual Base64 standard and you'll also have to implement a custom decoder. If this is acceptable for your project let me know and I'll post my base64Decoder function, then you can just modify the decoder table, as I say you'd lose portability but if that's ok it would save you some work :) – par Apr 13 '12 at 22:11
  • any idea on how this could be done in reverse i.e. encrypt in ruby and decrypt in iOS. The decrypt method in IOS would also be useful to check encrypt method in cases not decrypting in ruby – humblemenon Sep 05 '13 at 20:00
0

You could as well use the following libs providing in-built AES-256-CBC cipher and Base64 encoding that you can use across both platforms quickly:

Ruby

https://github.com/Gurpartap/aescrypt

Here's how you would use the AESCrypt Ruby gem:

message = "top secret message"
password = "p4ssw0rd"

# Encrypting
encrypted_data = AESCrypt.encrypt(message, password)

# Decrypting
message = AESCrypt.decrypt(encrypted_data, password)

Objective-C

https://github.com/Gurpartap/AESCrypt-ObjC

Here's how you would use the AESCrypt Objective-C class:

NSString *message = @"top secret message";
NSString *password = @"p4ssw0rd";

// Encrypting
NSString *encryptedData = [AESCrypt encrypt:message password:password];

// Decrypting
NSString *message = [AESCrypt decrypt:encryptedData password:password];

Hope it helps!

Community
  • 1
  • 1
Gurpartap Singh
  • 2,744
  • 1
  • 26
  • 30
  • it would be very helpful if you can post an example of encrypting in IOS and then decrypting that encrypted string in ruby. i found the encrypting decrypting working on each platform by itself but not when you want to do it cross platform, the way you have claimed here. thanks – humblemenon Sep 05 '13 at 23:21
  • @Gurpartap We are using your Ruby code to encrpty and obj-c code to decrypt, but I am trying to use your methods that use the IV but they do NOT work. Your solution works if you use the methods that nil out the IV. It won't work if you use the methods that use the IV. What am I doing wrong. – jdog Jun 27 '14 at 15:36
0

I haven't done what you are doing, at least not exactly, but the following could be worth a try.

require 'crypt/rijndael'
require 'Base64'
rijndael = Crypt::Rijndael.new("samepassword")
decryptedBlock = rijndael.decrypt_block(Base64.decode64("encrypted-b64-encoded-data"))
Jonas Elfström
  • 30,834
  • 6
  • 70
  • 106
0

I think Jonas might be correct about using Rijndael. I remember working with Crypt and it requiring the cypher to be specified.

Montana Harkin
  • 399
  • 5
  • 13