Search code examples
iosswiftstringuuid

Generate the same UUID from the same String


I would like to generate a UUID string from a random string, so that the same input string generates the same UUID.

I don't care about getting the input string back from the UUID. I need this to deterministically convert keys in a database as part of a migration, so that different clients acting in parallel converge to the same result.

The accepted answer of this post has the answer in Java, I would need the Swift version.


Solution

  • The official way to do this is with a version 5 UUID (RFC 4122 Section 4.3):

    4.3 Algorithm for Creating a Name-Based UUID

    The version 3 or 5 UUID is meant for generating UUIDs from "names" that are drawn from, and unique within, some "name space".

    The process is to hash your string, and then insert that into a UUID. I'm going to carefully follow the spec here, but I'll mark the parts you could ignore and it'll still work correctly.

    As matt notes, you can just use a SHA directly if you just need a hash. But if your system actually requires a UUID, this is how you do it.

    • Define a namespace (this isn't fully necessary, but it will make sure your UUIDs are globally unique):
    let namespace = "com.example.mygreatsystem:"
    
    • Combine that with your string
    let inputString = "arandomstring"
    let fullString = namespace + inputString
    
    • Hash the values. The UUID v5 spec specifically calls for SHA-1, but feel free to use SHA-256 (SHA-2) here instead. There's no actual security concern with using SHA-1 here, but it's good practice to move to SHA-2 whenever you can.
    import CryptoKit
    
    let hash = Insecure.SHA1.hash(data: Data(fullString.utf8)) // SHA-1 by spec
    

    or

    let hash = SHA256.hash(data: Data(fullString.utf8)) // SHA-2 is generally better
    
    • Take the top 128-bits of data. (It is safe to extract any subset of bits from a SHA-1 or SHA-2 hash. Each bit is "effectively random.")
    var truncatedHash = Array(hash.prefix(16))
    
    • Correctly set the version and variant bits. This doesn't really matter for most uses. I've never encountered a system that actually parses the UUID metadata. But it's part of the spec. And if you use v4 random UUIDs for future records (which are the "normal" UUIDs for almost every system today), then this would allow you to distinguish which were created by hashing and which were random if that mattered for any reason. See UUIDTools for a nice visual introduction to the format.
    truncatedHash[6] &= 0x0F    // Clear version field
    truncatedHash[6] |= 0x50    // Set version to 5
    
    truncatedHash[8] &= 0x3F    // Clear variant field
    truncatedHash[8] |= 0x80    // Set variant to DCE 1.1
    
    • And finally, compute your UUID:
    let uuidString = NSUUID(uuidBytes: truncatedHash).uuidString