Использование SecRandomCopyBytes в Swift

Я хочу генерировать случайные байты, используя SecRandomCopyBytes в Swift 3.0. Вот как я это сделал в 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
    }
}

В Swift 3 я пытался сделать это так, так как я знаю, что концепция unsafemutablebytes теперь другая, но это не позволяет мне вернуться. Если я закомментирую часть возврата, все равно будет написано 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
}

Кто-нибудь знает, как это исправить?

Спасибо


person hockeybro    schedule 02.10.2016    source источник


Ответы (3)


Вы были близки, но return внутри замыкания возвращается из замыкания, а не из внешней функции. Поэтому в замыкании следует вызывать только SecRandomCopyBytes(), а результат передавать обратно.

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
    }
}

Для «замыкания с одним выражением» тип замыкания может быть определен автоматически, поэтому его можно сократить до

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:

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
    }
}
person Martin R    schedule 02.10.2016
comment
@CodeOverRide: Спасибо за исправление проблемы с перекрывающимся доступом. Я немного упростил, копировать данные не нужно. Достаточно не использовать keyData.count внутри замыкания. - person Martin R; 26.04.2018
comment
да, только что обнаружил, что это не нужно. - person CodeOverRide; 26.04.2018
comment
Это больше не действует в Swift 5, так как теперь withUnsafeMutableBytes выставляет UnsafeMutableRawBufferPointer вместо UnsafeMutablePointer. - person vilanovi; 29.03.2019
comment
@MartinR, можем ли мы сгенерировать случайное число в диапазоне, например arc4random_uniform(<num>) с SecRandomCopyBytes ? - person Ankit Jayaswal; 12.04.2019
comment
@AnkitJayaswal: не готово, но его не так уж сложно реализовать (аналогично тому, как в stackoverflow.com/a/26550169/1187415 ). Но обратите внимание, что, начиная с Swift 4.2, стандартная библиотека Swift предоставляет генератор случайных чисел, который, согласно разработчику .apple.com/documentation/swift/ — криптографически безопасен. - person Martin R; 12.04.2019
comment
Безопасно ли принудительно разворачивать базовый адрес здесь? - person Vinay Hosamane; 02.05.2019
comment
@ ВинайХосамане Да. Если данные не пусты, то базовый адрес не равен нулю. - person Martin R; 02.05.2019
comment
@MartinR - быстрый вопрос, в версии Swift 5 вы используете $0.baseAddress для указания указателя на начало области памяти для переменной keyData (я думаю)? --- как работает этот синтаксис, я думал, что $0 - это ярлык для первого параметра замыкания, здесь это не имеет для меня смысла, так как замыкание не имеет параметров. - person Woodstock; 24.09.2020
comment
@Woodstock: закрытие withUnsafeMutableBytes имеет один параметр типа UnsafeMutableRawBufferPointer и $0 — это сокращенный синтаксис для этого параметра. «Подробная» версия будет keyData.withUnsafeMutableBytes { bufPtr in SecRandomCopyBytes(kSecRandomDefault, 32, bufPtr.baseAddress! ) - person Martin R; 24.09.2020
comment
@MartinR - большое спасибо, сэр. Хорошо, это имеет смысл, но как тогда keyData в конечном итоге заполняется случайными байтами, вместо этого должно быть bufPtr с областью только уровня закрытия, нет? или bufPtr избегает закрытия? Я имею в виду, что мы возвращаем keyData, а не bufPtr или $0. - person Woodstock; 24.09.2020
comment
@Woodstock: закрытие вызывается с bufPtr, установленным на адрес (и длину) хранилища элементов keyData. Таким образом, SecRandomCopyBytes вызывается с указателем на это хранилище элементов и заполняет байты keyData. - person Martin R; 24.09.2020

Это самый простой и «быстрый» способ реализовать вашу функцию с помощью 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()
}

Как правило, в Swift лучше всего использовать защитные операторы, а не операторы if/else, когда поток управления функцией зависит от успеха или неудачи выражения или наличия значения, отличного от nil.

person Cole Campbell    schedule 30.10.2019

Согласно документации Apple, это выглядит примерно так:

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
}

или как дополнительный инициализатор:

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
        }
    }
}
person cyanide    schedule 13.08.2018