6

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.

9
  • 1
    Is there any limit to the length of the string? Commented Sep 22, 2020 at 14:52
  • 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. Commented Sep 22, 2020 at 15:55
  • 3
    You could read How to ask a good question 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. Commented Sep 22, 2020 at 16:29
  • 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). Commented Sep 22, 2020 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. Commented Sep 23, 2020 at 7:37

3 Answers 3

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 
Sign up to request clarification or add additional context in comments.

1 Comment

Amazing! Thank you very much.
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.

1 Comment

Oh, I see. Is there a risk of clash (same value with different strings) with SHA-1?
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') 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.