Skip to content

Instantly share code, notes, and snippets.

@mokagio
Created February 27, 2023 01:55
Show Gist options
  • Save mokagio/69965c42df77b79f21096d0a82410938 to your computer and use it in GitHub Desktop.
Save mokagio/69965c42df77b79f21096d0a82410938 to your computer and use it in GitHub Desktop.
Two methods to create random strings in Swift
extension String {
enum RandomStringGenerationError: Error {
case secRandomCopyBytesFailed(status: Int32)
}
/// Returns a cryptographically secure string generated with characters from the given `Set<Character>` and with length
/// `length`.
///
/// - Complexity: O(n) where n is the given `length`.
static func secureRandomString(using characters: Set<Character>, withLength length: Int) throws -> String {
let allowedCharactersCount = UInt32(characters.count)
var randomBytes = [UInt8](repeating: 0, count: length)
// Use `SecRandomCopyBytes` to generate cryptographically secure random bytes to use as
// offsets to create the random string.
let status = SecRandomCopyBytes(kSecRandomDefault, length, &randomBytes)
guard status == errSecSuccess else {
throw RandomStringGenerationError.secRandomCopyBytesFailed(status: status)
}
return randomBytes.reduce("") { accumulator, randomByte in
let randomOffset = Int(randomByte) % Int(allowedCharactersCount)
let randomCharacter = characters[characters.index(characters.startIndex, offsetBy: randomOffset)]
return accumulator + String(randomCharacter)
}
}
/// Returns a random string generated with characters from the given `Set<Character>` and with length `length`.
///
/// - Complexity: O(n) where n is the given `length`.
static func randomString(using characters: Set<Character>, withLength length: Int) -> String {
let allowedCharactersCount = characters.count
let startIndex = characters.startIndex
return (0..<length).reduce("") { accumulator, _ in
let randomOffset = Int.random(in: 0..<allowedCharactersCount)
let randomCharacter = characters[characters.index(startIndex, offsetBy: randomOffset)]
return accumulator + String(randomCharacter)
}
}
}
import XCTest
class StringRandomTests: XCTestCase {
func testSecureRandomStringLengthIsAsRequested() throws {
XCTAssertEqual(
try XCTUnwrap(String.secureRandomString(using: Set("abc"), withLength: 10)).count,
10
)
}
func testSecureRandomStringUsesGivenCharactersOnly() throws {
// Using length 300 with 3 characters be relatively sure sure we'll get all of the
// characters at least once.
let randomString = try XCTUnwrap(String.secureRandomString(using: Set("abc"), withLength: 30))
XCTAssertEqual(
CharacterSet(charactersIn: randomString),
CharacterSet(charactersIn: "abc")
)
}
func testSecureRandomStringsAreDifferent() throws {
let characters = Set("abcdefghijklmnopqrstuvwxyz")
let length = 100
// It is _possible_ for the strings to be equal. However, it should be so unlikely that a
// test failure will most definitely mean an error in the code, rather than a legit case of
// the same random string being generated twice.
XCTAssertNotEqual(
try String.secureRandomString(using: characters, withLength: length),
try String.secureRandomString(using: characters, withLength: length)
)
}
// MARK: -
func testRandomStringLengthIsAsRequested() {
XCTAssertEqual(
String.randomString(using: Set("abc"), withLength: 10).count,
10
)
}
func testRandomStringUsesGivenCharactersOnly() {
// Using length 300 with 3 characters be relatively sure sure we'll get all of the
// characters at least once.
let randomString = String.randomString(using: Set("abc"), withLength: 30)
XCTAssertEqual(
CharacterSet(charactersIn: randomString),
CharacterSet(charactersIn: "abc")
)
}
func testRandomStringsAreDifferent() {
let characters = Set("abcdefghijklmnopqrstuvwxyz")
let length = 100
// It is _possible_ for the strings to be equal. However, it should be so unlikely that a
// test failure will most definitely mean an error in the code, rather than a legit case of
// the same random string being generated twice.
XCTAssertNotEqual(
String.randomString(using: characters, withLength: length),
String.randomString(using: characters, withLength: length)
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment