3

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.

Morpheus
  • 1,189
  • 2
  • 11
  • 33
  • Are you looking for something like this: UUID.init(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F") ? – Anjali Aggarwal Sep 22 '20 at 14:32
  • 1
    Is there any limit to the length of the string? – Rob Napier Sep 22 '20 at 14:52
  • @AnjaliAggarwal no, I'm looking to create the UUID by "coding" a random string – Morpheus Sep 22 '20 at 15:54
  • 2
    @JoakimDanielson I don't see the point of your comment. Isn't SO made to ask question? I tried different things with NSUUID but couldn't find any solution. – Morpheus Sep 22 '20 at 15:55
  • @RobNapier no, but if the solution has to limit the length of the string it would still helps a lot. – Morpheus Sep 22 '20 at 15:56
  • 3
    You could read [How to ask a good question](https://stackoverflow.com/help/how-to-ask) but the main point is that you should show us your effort at solving this or explain your research, otherwise it just comes out more like a request than a question. – Joakim Danielson Sep 22 '20 at 16:29
  • @JoakimDanielson sorry about that, sometimes when research leads me nowhere I tend to think (wrongly) that it won't be of any use – Morpheus Sep 22 '20 at 16:39
  • 3
    I'm not certain why this question has raised such objections. It's clearly asked, has a definitive but non-obvious answer, shows research, and the Java method has no direct equivalent in Swift (so this isn't just a language conversion question). – Rob Napier Sep 22 '20 at 16:42
  • 1
    @Coconuts if you feel that certain comments are unfriendly, then you can flag them as such. I just did so, for a comment on this page. – Bart van Kuik Sep 23 '20 at 07:37

3 Answers3

12

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
Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
2

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

You are describing a hash value, not a UUID. The U in UUID stands for unique; you get a different one every time. An SHA-1 hash (for example) is identical for identical strings and different for different strings.

matt
  • 515,959
  • 87
  • 875
  • 1,141
0

UUIDKit by @Nicolas Bachschmidt has a full UUID v5 implementation in Swift.

It also has support for all the namespaces in RFC 1808 such as NameSpace_URL:

import UUIDKit
UUID.v5(name: "http://example.com/index.html", namespace: .url)

Because it follows the spec, it uses SHA-1 and the output matches other platform implementations so the above line outputs the same value as this python code:

import uuid
uuid.uuid5(uuid.NAMESPACE_URL, 'http://example.com/index.html')
user3772393
  • 343
  • 1
  • 3
  • 5