25

I want to generate random bytes using SecRandomCopyBytes in Swift 3.0. Here is how I did it in Swift 2.2

private static func generateRandomBytes() -> String? {
    let data = NSMutableData(length: Int(32))

    let result = SecRandomCopyBytes(kSecRandomDefault, 32, UnsafeMutablePointer<UInt8>(data!.mutableBytes))
    if result == errSecSuccess {
        return data!.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
    } else {
        print("Problem generating random bytes")
        return nil
    }
}

In Swift 3, I tried to do it like this, since I know the concept of unsafemutablebytes is different now, but it doesn't allow me to return. If I comment out the return part, it still says Generic Parameter ResultType could not be inferred

fileprivate static func generateRandomBytes() -> String? {
    var keyData = Data(count: 32)
    _ = keyData.withUnsafeMutableBytes {mutableBytes in
        let result = SecRandomCopyBytes(kSecRandomDefault, keyData.count, mutableBytes)
        if result == errSecSuccess {
            return keyData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
        } else {
            print("Problem generating random bytes")
            return nil
        }
    }
    return nil
}

Does anyone know how to fix this?

Thanks

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
hockeybro
  • 981
  • 1
  • 13
  • 41

3 Answers3

53

You were close, but return inside the closure returns from the closure, not from the outer function. Therefore only SecRandomCopyBytes() should be called in the closure, and the result passed back.

func generateRandomBytes() -> String? {

    var keyData = Data(count: 32)
    let result = keyData.withUnsafeMutableBytes {
        (mutableBytes: UnsafeMutablePointer<UInt8>) -> Int32 in
        SecRandomCopyBytes(kSecRandomDefault, 32, mutableBytes)
    }
    if result == errSecSuccess {
        return keyData.base64EncodedString()
    } else {
        print("Problem generating random bytes")
        return nil
    }
}

For a "single-expression closure" the closure type can inferred automatically, so this can be shortened to

func generateRandomBytes() -> String? {

    var keyData = Data(count: 32)
    let result = keyData.withUnsafeMutableBytes {
        SecRandomCopyBytes(kSecRandomDefault, 32, $0)
    }
    if result == errSecSuccess {
        return keyData.base64EncodedString()
    } else {
        print("Problem generating random bytes")
        return nil
    }
}

Swift 5 update:

func generateRandomBytes() -> String? {

    var keyData = Data(count: 32)
    let result = keyData.withUnsafeMutableBytes {
        SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!)
    }
    if result == errSecSuccess {
        return keyData.base64EncodedString()
    } else {
        print("Problem generating random bytes")
        return nil
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    @CodeOverRide: Thanks for fixing the "overlapping access" issue. I have simplified a bit, copying the data is not necessary. It suffices not to use `keyData.count` inside the closure. – Martin R Apr 25 '18 at 21:34
  • yea, just found that that is not needed. – CodeOverRide Apr 25 '18 at 21:36
  • This is no longer valid in Swift 5 as now `withUnsafeMutableBytes` exposes a `UnsafeMutableRawBufferPointer` instead of a `UnsafeMutablePointer`. – vilanovi Mar 29 '19 at 09:21
  • @MartinR, Can we generate a random number in a range like `arc4random_uniform()` with `SecRandomCopyBytes` ? – Ankit Jayaswal Apr 12 '19 at 10:35
  • @AnkitJayaswal: Not out of the box, but it is not too difficult to implement (similarly as in https://stackoverflow.com/a/26550169/1187415). But note that starting with Swift 4.2, the Swift standard library provides a random number generator which – according to https://developer.apple.com/documentation/swift/systemrandomnumbergenerator – is cryptographically secure. – Martin R Apr 12 '19 at 11:09
  • is it safe to force unwrap the base address here ? – Vinay Hosamane May 01 '19 at 21:16
  • @VinayHosamane Yes. If the data is not empty then the base address is not nil. – Martin R May 01 '19 at 21:18
  • @MartinR - quick question, in the Swift 5 version, you use $0.baseAddress to refer to specify a pointer to the start of the memory location for the keyData variable (I think)? --- how does this syntax work, I thought $0 was a shortcut to the first "parameter" of the closure, this doesn't make sense to me here, as the closure has no params. – Woodstock Sep 24 '20 at 15:47
  • @Woodstock: The closure of [`withUnsafeMutableBytes`](https://developer.apple.com/documentation/swift/array/2633739-withunsafemutablebytes) has one parameter of type `UnsafeMutableRawBufferPointer` and `$0` is the shortcut syntax for that parameter. The “verbose” version would be `keyData.withUnsafeMutableBytes { bufPtr in SecRandomCopyBytes(kSecRandomDefault, 32, bufPtr.baseAddress! )` – Martin R Sep 24 '20 at 16:22
  • @MartinR - many thanks sir. OK that makes sense, but how then does `keyData` end up filled with the random bytes, instead it should be `bufPtr` with only closure level scope, no? or does `bufPtr` escape the closure? I mean we return `keyData` not `bufPtr` or `$0`. – Woodstock Sep 24 '20 at 16:27
  • @Woodstock: The closure is called with bufPtr set to the address (and length) of the element storage of keyData. So SecRandomCopyBytes is called with a pointer to that element storage, and fills the bytes of keyData. – Martin R Sep 24 '20 at 16:46
12

This is the simplest and "Swiftiest" way to implement your function using Swift 5:

func generateRandomBytes() -> String? {
    var bytes = [UInt8](repeating: 0, count: 32)
    let result = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)

    guard result == errSecSuccess else {
        print("Problem generating random bytes")
        return nil
    }

    return Data(bytes).base64EncodedString()
}

Generally it is best practice in Swift to use guard statements as opposed to if/else statements when the control flow of a function depends on the success or failure of an expression or the presence of a non-nil value.

Cole Campbell
  • 249
  • 3
  • 7
7

According to Apple Documentation it looks similar to this:

public func randomData(ofLength length: Int) throws -> Data {
    var bytes = [UInt8](repeating: 0, count: length)
    let status = SecRandomCopyBytes(kSecRandomDefault, length, &bytes)
    if status == errSecSuccess {
        return Data(bytes: bytes)
    }
    // throw an error
}

or as an additional initializer:

public extension Data {
    public init(randomOfLength length: Int) throws {
        var bytes = [UInt8](repeating: 0, count: length)
        let status = SecRandomCopyBytes(kSecRandomDefault, length, &bytes)
        if status == errSecSuccess {
            self.init(bytes: bytes)
        } else {
            // throw an error
        }
    }
}
cyanide
  • 3,885
  • 3
  • 25
  • 33