5

I have been using UUIDString as an encrption key for the files stored on my iPAD, but the security review done on my app by a third party suggested the following.

With the launch of the application, a global database key is generated and stored in the keychain. During generation, the method UUIDString of the class NSUUID provided by the iOS is used. This function generates a random string composed of letters A to F, numbers and hyphens and unnecessarily restricts the key space, resulting in a weakening of the entropy. Since the key is used only by application logic and does not have to be read, understood or processed by an individual, there is no need to restrict the key space to readable characters. Therefore, a random 256-bit key generated via SecRandomCopyBytes () should be used as the master key.

Now I have searched a lot and tried some code implementation but havent found the exact thing. What I have tried:

NSMutableData* data = [NSMutableData dataWithLength:32];
int result = SecRandomCopyBytes(kSecRandomDefault, 32, data.mutableBytes);
NSLog(@"Description %d",result);

My understanding is that this should give me an integer and I should convert it to an NSString and use this as my key, but I am pretty sure that this is not what is required here and also the above method always gives the result as 0. I am completely lost here and any help is appreciated.

Thanks.

Ankit Srivastava
  • 12,347
  • 11
  • 63
  • 115
  • Encryption is an operation on bytes, not characters including the key. Not all byte values are valid characters. There is a low likelihood that an array of random bytes would be a valid NSUTF8String or a printable string in any encoding. If you want a string convert the bytes either to Base64 or hex. A UUID trades off randomness for global uniqueness in time and space, usually by incorporating known unique elements such as time and MAC address as part of the generation process. This reduces the randomness of the value. An array of random bytes does not have this reduction is security. – zaph Nov 19 '14 at 14:17

5 Answers5

7

The result of SecRandomCopyBytes should always be 0, unless there is some error (which I can't imagine why that might happen) and then the result would be -1. You're not going to convert that into a NSString.

The thing you're trying to get are the random bytes which are being written into the mutable bytes section, and that's what you'll be using as your "master key" instead of the UUID string.

The way I would do it would be:

uint8_t randomBytes[16];
int result = SecRandomCopyBytes(kSecRandomDefault, 16, randomBytes);
if(result == 0) {
    NSMutableString *uuidStringReplacement = [[NSMutableString alloc] initWithCapacity:16*2];
    for(NSInteger index = 0; index < 16; index++)
    {
        [uuidStringReplacement appendFormat: @"%02x", randomBytes[index]];
    }
    NSLog(@"uuidStringReplacement is %@", uuidStringReplacement);
} else {
    NSLog(@"SecRandomCopyBytes failed for some reason");
}

Using a UUIDString feels secure enough to me, but it sounds like your third party security audit firm is trying really hard to justify their fees.

EDITED: since I'm now starting to collect downvotes because of Vlad's alternative answer and I can't delete mine (as it still has the accepted checkmark), here's another version of my code. I'm doing it with 16 random bytes (which gets doubled in converting to Hex).

Michael Dautermann
  • 88,797
  • 17
  • 166
  • 215
  • Thanks a lot... I guess they are trying really hard. – Ankit Srivastava Nov 19 '14 at 08:29
  • 1
    This never gives me any string as I think it is just random bytes and thus may not transform to NSUTF8String Encoded string. Do you have any other ideas here..? How I can use this.? – Ankit Srivastava Nov 19 '14 at 10:27
  • is "`uuidStringReplacement`" coming up as nil? if not, then it's a valid string (just all kinds of ascii and non-ascii garbage characters). – Michael Dautermann Nov 19 '14 at 10:40
  • I tried to put the contents in a cstring pointer using :-> const char *cString = [data bytes]; and got the cstring description as (const char *) cString = 0x072244a0 "\xad\xdb\x17\xcdS\xb5\xdc\xcd\x87\x8d\xba\x18\xb6\xac\\xe but when I am trying to put this in an NSString the NSString pointer is nil. (I am using this :-> NSString *myNSString = [NSString stringWithUTF8String:cString];) – Ankit Srivastava Nov 19 '14 at 10:45
  • Though I am getting an NSString generated for other forms of Encoding like NSUTF16StringEncoding and NSASCIIStringEncoding. So thanks in any case , I guess use ASCII and it should be enough for the reviewers. – Ankit Srivastava Nov 19 '14 at 10:55
  • Note the name: `SecRandomCopyBytes`, not characters. There is a low likelihood that an array of random bytes would be a valid `NSUTF8String` or a printable string in any encoding. If you want a string convert the bytes either to Base64 or hex. – zaph Nov 19 '14 at 13:38
  • Thanks for the downvote @Vlad... your result with doing Base64 encoding is certainly superior (and human readable), so I'll plus one yours. – Michael Dautermann Jan 05 '16 at 04:22
  • I voted up your new answer. It is good answer the previous one would fail in converting to UTF. The only issue with your answer and mine is they are not truly 256bit as they loose entropy from the hashing method. Since you are effectually doubling 128bits and I am hashing 192bits. – Vlad Jan 06 '16 at 00:57
  • This is fine, except it generates a string with 128-bit randomness rather than the requested 256-bit. I suggest updating the answer replacing 16 with 32. To explain, 16 bytes mean (2^8)^16 = 2^128 combinations, while we want (2^8)^32 = 2^256 combinations. Also, you could use `(sizeof randomBytes)/(sizeof randomBytes[0])` to avoid repeating the number of bytes. – yonilevy Jun 30 '19 at 15:23
4

The NSData generated does not guarantee UTF16 chars.

This method will generate 32byte UTF string which is equivalent to 256bit. (Advantage is this is plain text and can be sent in GET requests ext.)

Since the length of Base64 hash is = (3/4) x (length of input string) we can work out input length required to generate 32byte hash is 24 bytes long. Note: Base64 may pad end with one, two or no '=' chars if not divisible.

With OSX 10.9 & iOS 7 you can use:

-[NSData base64EncodedDataWithOptions:]

This method can be used to generate your UUID:

+ (NSString*)generateSecureUUID {
    NSMutableData *data = [NSMutableData dataWithLength:24];

    int result = SecRandomCopyBytes(NULL, 24, data.mutableBytes);

    NSAssert(result == 0, @"Error generating random bytes: %d", result);

    NSString *base64EncodedData = [data base64EncodedStringWithOptions:0];

    return base64EncodedData;
}
Vlad
  • 5,727
  • 3
  • 38
  • 59
2

A UUID is a 16 bytes (128 bits) unique identifier, so you aren't using a 256 bits key here. Also, as @zaph pointed out, UUIDs use hardware identifiers and other inputs to guarantee uniqueness. These factors being predictable are definitely not cryptographically secure.

You don't have to use a UUID as an encryption key, instead I would go for a base 64 or hexadecimal encoded data of 32 bytes, so you'll have your 256 bit cryptographically secure key:

/** Generates a 256 bits cryptographically secure key.
 * The output will be a 44 characters base 64 string (32 bytes data
 * before the base 64 encoding).
 * @return A base 64 encoded 256 bits secure key.
 */
+ (NSString*)generateSecureKey
{
    NSMutableData *data = [NSMutableData dataWithLength:32];
    int result = SecRandomCopyBytes(kSecRandomDefault, 32, data.mutableBytes);
    if (result != noErr) {
        return nil;
    }
    return [data base64EncodedStringWithOptions:kNilOptions];
}

To answer the part about generate UUID-like (secure) random numbers, here's a good way, but remember these will be 128 bits only keys:

/** Generates a 128 bits cryptographically secure key, formatted as a UUID.
 * Keep that you won't have the same guarantee for uniqueness
 * as you have with regular UUIDs.
 * @return A cryptographically secure UUID.
 */
+ (NSString*)generateCryptoSecureUUID
{
    unsigned char bytes[16];
    int result = SecRandomCopyBytes(kSecRandomDefault, 16, bytes);
    if (result != noErr) {
        return nil;
    }
    return [[NSUUID alloc] initWithUUIDBytes:bytes].UUIDString;
}

Cryptography is great, but doing it right is really hard (it's easy to leave security breaches). I cannot recommend you more the use of RNCryptor, which will push you through the use of good encryption standards, will make sure you're not unsafely reusing the same keys, will derivate encryption keys from passwords correctly, etc.

Micha Mazaheri
  • 3,481
  • 1
  • 21
  • 26
0

And i try this code for length 16 and bytes 16 :

uint8_t randomBytes[16];
NSMutableString *ivStr;
int result = SecRandomCopyBytes(kSecRandomDefault, 16, randomBytes);
if(result == 0) {
    ivStr = [[NSMutableString alloc] initWithCapacity:16];
    for(NSInteger index = 0; index < 8; index++)
    {
        [ivStr appendFormat: @"%02x", randomBytes[index]];
    }
        NSLog(@"uuidStringReplacement is %@", ivStr);
} else {
    NSLog(@"SecRandomCopyBytes failed for some reason");
}

Successful

reza_khalafi
  • 6,230
  • 7
  • 56
  • 82
0

Since the Key usually needs to be UTF-8 encoded and "readable" - i.e. with no UTF-8 control characters- I decided to filter the randomly generated bytes generated using SecRandomCopyBytes so it'd only have characters from the Basic Latin Unicode block.

/*!
 * @brief Generates NSData from a randomly generated byte array with a specific number of bits
 * @param numberOfBits the number of bits the generated data must have
 * @return the randomly generated NSData
 */
+ (NSData *)randomKeyDataGeneratorWithNumberBits:(int)numberOfBits {
    int numberOfBytes = numberOfBits/8;
    uint8_t randomBytes[numberOfBytes];
    int result = SecRandomCopyBytes(kSecRandomDefault, numberOfBytes, randomBytes);
    if(result == 0) {
        return [NSData dataWithBytes:randomBytes length:numberOfBytes];
    } else {
        return nil;
    }
}

/*!
 * @brief Generates UTF-8 NSData from a randomly generated byte array with a specific number of bits
 * @param numberOfBits the number of bits the generated data must have
 * @return the randomly generated NSData
 */
+ (NSData *)randomKeyUTF8DataGeneratorWithNumberBits:(int)numberOfBits {
    NSMutableData *result = [[NSMutableData alloc] init];
    int numberOfBytes = numberOfBits/8;
    while (result.length < numberOfBytes) {
        // Creates a random byte
        NSData *byte = [self randomKeyDataGeneratorWithNumberBits:8];
        int asciiValue = [[[NSString alloc] initWithData:byte encoding:NSUTF8StringEncoding] characterAtIndex:0];
        // Checks if the byte is UTF-8
        if (asciiValue > 32 && asciiValue < 127) {
            [result appendData:byte];
        }
    }
    return result;
}

If you want to make your key a little more "readable" you can try and make it Base64 URL Safe

/*!
 * @brief Encodes a String Base 64 with URL and Filename Safe Alphabet
 * @discussion Base64url Encoding  The URL- and filename-safe Base64 encoding described in RFC 4648 [RFC4648] (https://tools.ietf.org/html/rfc4648)
 * @discussion Section 5 (https://tools.ietf.org/html/rfc4648#section-5)
 * @param string the string to be enconded
 * @return the encoded string
 */
+ (NSString *)base64URLandFilenameSafeString:(NSString *)string {
    NSString *base64String = string;
    base64String = [base64String stringByReplacingOccurrencesOfString:@"/"
                                                           withString:@"_"];
    base64String = [base64String stringByReplacingOccurrencesOfString:@"+"
                                                           withString:@"-"];
    return base64String;
}

Generate a UTF-8 256 bits key:

NSData *key = [self randomKeyUTF8DataGeneratorWithNumberBits:256];
NSString *UTF8String = [[NSString alloc] initWithBytes:[key bytes] length:data.length encoding:NSUTF8StringEncoding];
NSString *base64URLSafeString = [self base64URLandFilenameSafeString:UTF8String];
Boris
  • 11,373
  • 2
  • 33
  • 35