Skip to content

Instantly share code, notes, and snippets.

@vzsg
Created June 26, 2019 14:55
Show Gist options
  • Save vzsg/ec21f7812622be17b51599ed7ac51a9c to your computer and use it in GitHub Desktop.
Save vzsg/ec21f7812622be17b51599ed7ac51a9c to your computer and use it in GitHub Desktop.
UUIDv5 using Vapor3's Crypto
// This is a direct port of https://github.com/download13/uuidv5 to Swift
import Foundation
import Crypto
public extension UUID {
enum Namespace {
case dns
case url
case oid
case x500
case null
case custom(UUID)
public var uuid: UUID {
switch self {
case .dns:
return UUID(uuidString: "6ba7b810-9dad-11d1-80b4-00c04fd430c8")!
case .url:
return UUID(uuidString: "6ba7b811-9dad-11d1-80b4-00c04fd430c8")!
case .oid:
return UUID(uuidString: "6ba7b812-9dad-11d1-80b4-00c04fd430c8")!
case .x500:
return UUID(uuidString: "6ba7b814-9dad-11d1-80b4-00c04fd430c8")!
case .null:
return UUID(uuidString: "00000000-0000-0000-0000-000000000000")!
case .custom(let uuid):
return uuid
}
}
}
init(namespace: Namespace, name: String) throws {
let rawNamespace = namespace.uuid.uuid
var c = Data([
rawNamespace.0,
rawNamespace.1,
rawNamespace.2,
rawNamespace.3,
rawNamespace.4,
rawNamespace.5,
rawNamespace.6,
rawNamespace.7,
rawNamespace.8,
rawNamespace.9,
rawNamespace.10,
rawNamespace.11,
rawNamespace.12,
rawNamespace.13,
rawNamespace.14,
rawNamespace.15
])
c.append(name.data(using: .utf8) ?? Data())
let digest = try SHA1.hash(c)
let result: uuid_t = (
digest[0],
digest[1],
digest[2],
digest[3],
digest[4],
digest[5],
(digest[6] & 0x0f) | 0x50, // version, 4 most significant bits are set to version 5 (0101)
digest[7],
(digest[8] & 0x3f) | 0x80, // clock_seq_hi_and_reserved, 2msb are set to 10
digest[9],
digest[10],
digest[11],
digest[12],
digest[13],
digest[14],
digest[15]
)
self.init(uuid: result)
}
}
import App
import XCTest
final class UUIDv5Tests: XCTestCase {
func testUUIDv5() throws {
XCTAssertEqual(try UUID(namespace: .dns, name: "www.example.org"), UUID("74738ff5-5367-5958-9aee-98fffdcd1876"))
XCTAssertEqual(try UUID(namespace: .url, name: "http://example.org/page"), UUID("6b19973b-8154-5782-bca0-15e6b730ca00"))
XCTAssertEqual(try UUID(namespace: .oid, name: "1.3.6.1.2.1"), UUID("2a7086de-bbf9-5cb9-9a66-a9e928c26504"))
XCTAssertEqual(try UUID(namespace: .x500, name: "uid=user,ou=people,dc=example,dc=com"), UUID("5324d252-c0de-5365-a19c-389275462a9c"))
}
static let allTests = [
("testUUIDv5", testUUIDv5)
]
}
@valeriomazzeo
Copy link

valeriomazzeo commented Jun 26, 2019

import Foundation
import Crypto

extension UUID {

    public init?(_ version: Version = .v5, namespace: String, name: String) {
        guard let namespace = UUID(uuidString: namespace) else {
            return nil
        }

        self.init(version, namespace: namespace, name: name)
    }

    public init?(_ version: Version = .v5, namespace: UUID, name: String) {

        let uuid = namespace.uuid
        let namespaceData = Data([
            uuid.0, uuid.1, uuid.2, uuid.3,
            uuid.4, uuid.5, uuid.6, uuid.7,
            uuid.8, uuid.9, uuid.10, uuid.11,
            uuid.12, uuid.13, uuid.14, uuid.15
            ])

        let nameData = Data(name.utf8)

        let data = namespaceData + nameData

        let digest: Data?

        switch version {
        case .v3:
            digest = try? MD5.hash(data)
        case .v5:
            digest = try? SHA1.hash(data)
        }

        guard let someDigest = digest else {
            return nil
        }

        let result: uuid_t = (
            someDigest[0],
            someDigest[1],
            someDigest[2],
            someDigest[3],
            someDigest[4],
            someDigest[5],
            (someDigest[6] & 0x0f) | version.rawValue, // version, 4 most significant bits are set to version
            someDigest[7],
            (someDigest[8] & 0x3f) | 0x80, // clock_seq_hi_and_reserved, 2msb are set to 10
            someDigest[9],
            someDigest[10],
            someDigest[11],
            someDigest[12],
            someDigest[13],
            someDigest[14],
            someDigest[15]
        )

        self.init(uuid: result)
    }
}

// MARK: - Version

extension UUID {

    public enum Version: UInt8 {
        case v3 = 0x30
        case v5 = 0x50
    }
}

// MARK: - Namespace

extension UUID {

    /// Defined in RFC4122  https://tools.ietf.org/html/rfc4122#appendix-C
    public static let dns = UUID(uuidString: "6BA7B810-9DAD-11D1-80B4-00C04FD430C8")!

    /// Defined in RFC4122  https://tools.ietf.org/html/rfc4122#appendix-C
    public static let url = UUID(uuidString: "6BA7B811-9DAD-11D1-80B4-00C04FD430C8")!

    /// Defined in RFC4122  https://tools.ietf.org/html/rfc4122#appendix-C
    public static let oid = UUID(uuidString: "6BA7B812-9DAD-11D1-80B4-00C04FD430C8")!

    /// Defined in RFC4122  https://tools.ietf.org/html/rfc4122#appendix-C
    public static let x500 = UUID(uuidString: "6BA7B814-9DAD-11D1-80B4-00C04FD430C8")!

    /// /// Defined in RFC4122  https://tools.ietf.org/html/rfc4122#appendix-C
    public static let null = UUID(uuidString: "00000000-0000-0000-0000-000000000000")!
}

@vzsg
Copy link
Author

vzsg commented Jun 26, 2019

Nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment