Random numbers in Swift: wherefore art thou arc4random_uniform (Xcode 7.1.1, iOS; update with info on SecRandomCopyBytes; update Xcode 8 beta 6, Swift 3)


** Jump to Swift 3 code **

Two years ago the case was closed, NSHipster in his wisdom told us to use arc4random_uniform for random numbers but then Apple go and remove it from the Darwin.C.stdlib header file in Xcode 7. While it still works, this makes it feel kind of dirty to use it. Has it been removed for a reason? Or is it simply waiting to be put back once bugs are ironed out? The issue has been identified on Apple's forums but no definitive response appears. (Thanks to @mofodox for drawing my attention to its absence and prompting this investigation.)

This prompted me to look around at the other available random functions in the stdlib, and here's some code that I started throwing together.
srand(1) // seed the starting point
rand()%100 // restrict the upper value

lrand48()
mrand48()
random()%100
random()%100 + 1 // set upper and lower values
var ccc = UInt16(10)
seed48(&ccc) // seeding a 48 function
lrand48()
mrand48()

For an explanation I turned to IBM: the 48 number in some of the functions means that 48-bit numbers are being returned, which sounds like it might provide an advantage over functions like arc4random(), which return UInt32 numbers ... but don't stop here!

Worth noting is that decipherable from the Darwin.C.stdlib header file is that some rand() functions depending on prefix return different types: double, Int, etc. But for practical implementation advice cplusplus.com is a better place to turn. There we learn, for example, about creating ranges:
let v3 = rand() % 30 + 1985   // v3 in the range 1985-2014 
It might seem odd at first, but really all we are doing here is generating a number between 0 and 29 then adding 1985 to it. The + operator is not magically doing something different to its normal role. It hasn't been mysteriously overloaded here.

But for all this experimentation, turning to Apple's docs, the final line discussing mrand(), lrand(), etc. stands out:
For a more powerful random number generator, see random(3).

Cryptographic level random number seeding

The random() and srandom() functions are explained in the IBM Knowledge Center. These return Int in Swift (while rand() returns Int32). IBM warns off using random() for its predictability. However, Apple has an additional seeding function:
The srandomdev() routine initializes a state array, using the random(4) random number device which returns good random numbers, suitable for cryptographic use. Note that this particular seeding procedure can generate states which are impossible to reproduce by calling srandom() with any value, since the succeeding terms in the state buffer are no longer derived from the LC algorithm applied to a fixed seed. (Apple)
which claims to avoid this. But wait a second, there's this post discussing srandomdev() that claims it was good but now it's not. The thing is, having read the comments, I'm not sure it has any real offering on whether to trust the seeding.

Sticking with arc4random()

More definitive than discussion of srandomdev() is the statement in Apple's manual pages that,
The arc4random() function uses the key stream generator employed by the arc4 cipher, which uses 8*8 8 bit S-Boxes. The S-Boxes can be in about (2**1700) states. The arc4random() function returns pseudo-random numbers in the range of 0 to (2**32)-1, and therefore has twice the range of rand(3) and random(3). (Apple)
And while we're here it's worth pointing out that while we can restrict arc4random with the same syntax as other random functions, e.g. using arc4random()%100 if we wish, Apple advises us not to do this over arc4random_uniform(100). For the following reason:
arc4random_uniform() is recommended over constructions like ``arc4random() % upper_bound'' as it avoids "modulo bias" when the upper bound is not a power of two.
It should also be noted that arc4random functions are self-seeding and initialising. Given all this my conclusion is simply that arc4random is taking a brief holiday from the header file and will be back.

There's more ...

Posts like this often result in more learning than teaching, and this is no exception.
Time is against me at the moment but I fully intend to return to SecRandomCopyBytes and to expand this post based on Joseph's advice. In the meantime here are some links:

Apple

Medium

James Carroll

StackOverflow

On RC4 vulnerability:

Threat Post

Security Week

Some days later ...

Reading up on SecRandomCopyBytes, it relies on random(4) and returns an array of bytes (i.e. [UInt8]). The (4) denotes a special file in man page specificationsJames Carroll provides code for extracting the random numbers from the generated arrays. This relies on the bit casting of an array of a number of bytes to a type equivalent to its length, and here I've adapted Carroll's code to create a Random type capable of returning UInt8, 16, 32, or 64 numbers. (Note: due to a bug in Xcode 7.1, the UInt64 function won't work as expected there but will in Xcode 7.1.1)
struct Random {
    
    static func randomBytes(numberOfBytes:Int) -> [UInt8] {
        var randomBytes = [UInt8](count: numberOfBytes, repeatedValue: 0) // array to hold randoms bytes
        SecRandomCopyBytes(kSecRandomDefault, numberOfBytes, &randomBytes)
        return randomBytes
        
    }
    static fun numberToRandom<T:IntegerLiteralConvertible>(var num:T) -> T {
        
        let randomBytes = Random.randomBytes(sizeof(T))
        // Turn bytes into data and pass data bytes into int
        NSData(bytes: randomBytes, length: sizeof(T)).getBytes(&num, length: sizeof(T))
        return num
    }
    static func number64bit() -> UInt64 {
        
        return Random.numberToRandom(UInt64())  // variable for random unsigned 64
    }
    
    static func number32bit() -> UInt32 {
        return Random.numberToRandom(UInt32())  // variable for random unsigned 32
    }
    
    static func number16bit() -> UInt16 {
        
        return Random.numberToRandom(UInt16())  // variable for random unsigned 16
    }
    
    static func number8bit() -> UInt8 {
        
        return Random.numberToRandom(UInt8())  // variable for random unsigned 8
    }
    
    static func int() -> Int {
        
        return Random.numberToRandom(Int())  // variable for random Int
    }
    static func double() -> Double {
        
        return Random.numberToRandom(Double())  // variable for random Double
    }
   static func float() -> Float {
        
        return Random.numberToRandom(Float())  // variable for random Double
    }

    
}

Random.number64bit()
Random.number32bit()
Random.number16bit()
Random.number8bit()
Random.int()
Random.double()
Random.float()
Carroll also provides code for returing longer random number strings.

Conclusion

It seems that Apple's manual pages might be yet to catch up with the arc4random_uniform() debacle, and that this method of random number generation might be in trouble for use in high-level cryptographic purposes if its security issues can't be resolved. However, if you're simply writing a game where you need to role a dice or something similar then of course it will do just fine to write arc4random_uniform(6) + 1 or, if you'd rather use a method that still exists in the Xcode header files, then:
srandomdev()
random()%6 + 1
For the more sophisticated stuff the best information I have at present is to utilize SecRandomCopyBytes(), although whether there is a difference between this and using srandomdev() paired with random() I don't know. Please feel free to add information in the comments if you have knowledge to share about this.

Swift 3 (Xcode 8 beta 6) – arc4random is back, and everything has changed

As you can see from the picture, Swift 3 now advises very clearly that arc4random() and arc4random_uniform() should be used in place of other options that were formerly available. So now you'll be writing code like this again:
arc4random()
arc4random_uniform(9) + 1985 // random number between 1985 and 1993

// seed48 still works but you retrieve the value using pointee rather than memory in Swift 3
var ccc = UInt16(10)
seed48(&ccc).pointee // seeding a 48 function
I also include here updated code for SecRandomCopyBytes:
struct Random {
    
    static func randomBytes(numberOfBytes:Int) -> [UInt8] {
        var randomBytes = [UInt8](repeating: 0, count: numberOfBytes) // array to hold randoms bytes
        SecRandomCopyBytes(kSecRandomDefault, numberOfBytes, &randomBytes)
        return randomBytes
        
    }
    static func numberToRandom<T:ExpressibleByIntegerLiteral>(num:T) -> T {
        
        var num = num
        let randomBytes = Random.randomBytes(numberOfBytes: MemoryLayout<T>.size)
    // Turn bytes into data and pass data bytes into int
    NSData(bytes: randomBytes, length: MemoryLayout<T>.size).getBytes(&num, length: MemoryLayout<T>.size)
    return num
    }
    static func number64bit() -> UInt64 {
        
        return Random.numberToRandom(num: UInt64())  // variable for random unsigned 64
    }
    
    static func number32bit() -> UInt32 {
        return Random.numberToRandom(num: UInt32())  // variable for random unsigned 32
    }
    
    static func number16bit() -> UInt16 {
        
        return Random.numberToRandom(num: UInt16())  // variable for random unsigned 16
    }
    
    static func number8bit() -> UInt8 {
        
        return Random.numberToRandom(num: UInt8())  // variable for random unsigned 8
    }
    
    static func int() -> Int {
        
        return Random.numberToRandom(num: Int())  // variable for random Int
    }
    static func double() -> Double {
        
        return Random.numberToRandom(num: Double())  // variable for random Double
    }
    static func float() -> Float {
        
        return Random.numberToRandom(num: Float())  // variable for random Double
    }
    
    
}

Random.number64bit()
Random.number32bit()
Random.number16bit()
Random.number8bit()
Random.int()
Random.double()
Random.float()

Comments