diff --git a/README_js.md b/README_js.md
index 4baf44c0..d105544c 100644
--- a/README_js.md
+++ b/README_js.md
@@ -8,7 +8,7 @@ Simple, fast generation of [RFC4122](http://www.ietf.org/rfc/rfc4122.txt) UUIDS.
Features:
-* Support for version 1, 4 and 5 UUIDs
+* Support for version 1, 3, 4 and 5 UUIDs
* Cross-platform
* Uses cryptographically-strong random number APIs (when available)
* Zero-dependency, small footprint (... but not [this small](https://gist.github.com/982883))
@@ -28,6 +28,25 @@ const uuidv1 = require('uuid/v1');
uuidv1(); // RESULT
```
+Version 3 (namespace):
+
+```javascript --context
+const uuidv3 = require('uuid/v3');
+
+// ... using predefined DNS namespace (for domain names)
+uuidv3('hello.example.com', uuidv3.DNS); // RESULT
+
+// ... using predefined URL namespace (for, well, URLs)
+uuidv3('http://example.com/hello', uuidv3.URL); // RESULT
+
+// ... using a custom namespace
+//
+// Note: Custom namespaces should be a UUID string specific to your application!
+// E.g. the one here was generated using this modules `uuid` CLI.
+const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
+uuidv3('Hello, World!', MY_NAMESPACE); // RESULT
+```
+
Version 4 (random):
```javascript --context
@@ -67,6 +86,15 @@ uuidv1(); // -> v1 UUID
```
+For version 3 uuids:
+
+```html
+
+
+```
+
For version 4 uuids:
```html
@@ -135,6 +163,40 @@ uuidv1(null, arr, 0); // RESULT
uuidv1(null, arr, 16); // RESULT
```
+### Version 3
+
+```javascript
+const uuidv3 = require('uuid/v3');
+
+// Incantations
+uuidv3(name, namespace);
+uuidv3(name, namespace, buffer);
+uuidv3(name, namespace, buffer, offset);
+```
+
+Generate and return a RFC4122 v3 UUID.
+
+* `name` - (String | Array[]) "name" to create UUID with
+* `namespace` - (String | Array[]) "namespace" UUID either as a String or Array[16] of byte values
+* `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written.
+* `offset` - (Number) Starting index in `buffer` at which to begin writing. Default = 0
+
+Returns `buffer`, if specified, otherwise the string form of the UUID
+
+Example:
+
+```javascript --run
+// Generate a unique namespace (typically you would do this once, outside of
+// your project, then bake this value into your code)
+const uuidv4 = require('uuid/v4');
+const uuidv3 = require('uuid/v3');
+const MY_NAMESPACE = uuidv4(); // RESULT
+
+// Generate a couple namespace uuids
+uuidv3('hello', MY_NAMESPACE); // RESULT
+uuidv3('world', MY_NAMESPACE); // RESULT
+```
+
### Version 4
```javascript
diff --git a/bin/uuid b/bin/uuid
index 2fd26d76..502626e6 100755
--- a/bin/uuid
+++ b/bin/uuid
@@ -5,6 +5,7 @@ function usage() {
console.log('Usage:');
console.log(' uuid');
console.log(' uuid v1');
+ console.log(' uuid v3 ');
console.log(' uuid v4');
console.log(' uuid v5 ');
console.log(' uuid --help');
@@ -25,6 +26,20 @@ switch (version) {
console.log(uuidV1());
break;
+ case 'v3':
+ var uuidV3 = require('../v3');
+
+ var name = args.shift();
+ var namespace = args.shift();
+ assert(name != null, 'v3 name not specified');
+ assert(namespace != null, 'v3 namespace not specified');
+
+ if (namespace == 'URL') namespace = uuidV3.URL;
+ if (namespace == 'DNS') namespace = uuidV3.DNS;
+
+ console.log(uuidV3(name, namespace));
+ break;
+
case 'v4':
var uuidV4 = require('../v4');
console.log(uuidV4());
diff --git a/lib/md5-browser.js b/lib/md5-browser.js
new file mode 100644
index 00000000..d7f82dff
--- /dev/null
+++ b/lib/md5-browser.js
@@ -0,0 +1,218 @@
+/*
+ * Browser-compatible JavaScript MD5
+ *
+ * Modification of JavaScript MD5
+ * https://github.com/blueimp/JavaScript-MD5
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ *
+ * Based on
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+'use strict;'
+
+function md5(bytes) {
+
+ if (typeof(bytes) == 'string') {
+ var msg = unescape(encodeURIComponent(bytes)); // UTF8 escape
+ bytes = new Array(msg.length);
+ for (var i = 0; i < msg.length; i++) bytes[i] = msg.charCodeAt(i);
+ }
+
+ return md5ToHexEncodedArray(
+ wordsToMd5(
+ bytesToWords(bytes)
+ , bytes.length * 8)
+ )
+}
+
+
+ /*
+* Convert an array of little-endian words to an array of bytes
+*/
+function md5ToHexEncodedArray(input) {
+ var i
+ var x
+ var output = []
+ var length32 = input.length * 32
+ var hexTab = '0123456789abcdef'
+ var hex
+
+ for (i = 0; i < length32; i += 8) {
+ x = (input[i >> 5] >>> (i % 32)) & 0xFF
+
+ hex = parseInt(hexTab.charAt((x >>> 4) & 0x0F) + hexTab.charAt(x & 0x0F), 16)
+
+ output.push(hex)
+ }
+ return output
+}
+
+ /*
+* Calculate the MD5 of an array of little-endian words, and a bit length.
+*/
+function wordsToMd5 (x, len) {
+ /* append padding */
+ x[len >> 5] |= 0x80 << (len % 32)
+ x[(((len + 64) >>> 9) << 4) + 14] = len
+
+ var i
+ var olda
+ var oldb
+ var oldc
+ var oldd
+ var a = 1732584193
+ var b = -271733879
+ var c = -1732584194
+
+ var d = 271733878
+
+ for (i = 0; i < x.length; i += 16) {
+ olda = a
+ oldb = b
+ oldc = c
+ oldd = d
+
+ a = md5ff(a, b, c, d, x[i], 7, -680876936)
+ d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)
+ c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)
+ b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)
+ a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)
+ d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)
+ c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)
+ b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)
+ a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)
+ d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)
+ c = md5ff(c, d, a, b, x[i + 10], 17, -42063)
+ b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)
+ a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)
+ d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)
+ c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)
+ b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)
+
+ a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)
+ d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)
+ c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)
+ b = md5gg(b, c, d, a, x[i], 20, -373897302)
+ a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)
+ d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)
+ c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)
+ b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)
+ a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)
+ d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)
+ c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)
+ b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)
+ a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)
+ d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)
+ c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)
+ b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)
+
+ a = md5hh(a, b, c, d, x[i + 5], 4, -378558)
+ d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)
+ c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)
+ b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)
+ a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)
+ d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)
+ c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)
+ b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)
+ a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)
+ d = md5hh(d, a, b, c, x[i], 11, -358537222)
+ c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)
+ b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)
+ a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)
+ d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)
+ c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)
+ b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)
+
+ a = md5ii(a, b, c, d, x[i], 6, -198630844)
+ d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)
+ c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)
+ b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)
+ a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)
+ d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)
+ c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)
+ b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)
+ a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)
+ d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)
+ c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)
+ b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)
+ a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)
+ d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)
+ c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)
+ b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)
+
+ a = safeAdd(a, olda)
+ b = safeAdd(b, oldb)
+ c = safeAdd(c, oldc)
+ d = safeAdd(d, oldd)
+ }
+ return [a, b, c, d]
+}
+
+ /*
+* Convert an array bytes to an array of little-endian words
+* Characters >255 have their high-byte silently ignored.
+*/
+function bytesToWords(input) {
+
+ var i
+ var output = []
+ output[(input.length >> 2) - 1] = undefined
+ for (i = 0; i < output.length; i += 1) {
+ output[i] = 0
+ }
+ var length8 = input.length * 8
+ for (i = 0; i < length8; i += 8) {
+ output[i >> 5] |= (input[(i / 8)] & 0xFF) << (i % 32)
+ }
+
+ return output
+}
+
+/*
+* Add integers, wrapping at 2^32. This uses 16-bit operations internally
+* to work around bugs in some JS interpreters.
+*/
+function safeAdd (x, y) {
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF)
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
+ return (msw << 16) | (lsw & 0xFFFF)
+}
+
+/*
+* Bitwise rotate a 32-bit number to the left.
+*/
+function bitRotateLeft (num, cnt) {
+ return (num << cnt) | (num >>> (32 - cnt))
+}
+
+/*
+* These functions implement the four basic operations the algorithm uses.
+*/
+function md5cmn (q, a, b, x, s, t) {
+ return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)
+}
+function md5ff (a, b, c, d, x, s, t) {
+ return md5cmn((b & c) | ((~b) & d), a, b, x, s, t)
+}
+function md5gg (a, b, c, d, x, s, t) {
+ return md5cmn((b & d) | (c & (~d)), a, b, x, s, t)
+}
+function md5hh (a, b, c, d, x, s, t) {
+ return md5cmn(b ^ c ^ d, a, b, x, s, t)
+}
+function md5ii (a, b, c, d, x, s, t) {
+ return md5cmn(c ^ (b | (~d)), a, b, x, s, t)
+}
+
+module.exports = md5;
\ No newline at end of file
diff --git a/lib/md5.js b/lib/md5.js
new file mode 100644
index 00000000..1645d6bb
--- /dev/null
+++ b/lib/md5.js
@@ -0,0 +1,21 @@
+'use strict';
+
+var crypto = require('crypto');
+
+function md5(bytes) {
+ // support modern Buffer API
+ if (typeof Buffer.from === 'function') {
+ if (Array.isArray(bytes)) bytes = Buffer.from(bytes);
+ else if (typeof bytes === 'string') bytes = Buffer.from(bytes, 'utf8');
+ }
+
+ // support pre-v4 Buffer API
+ else {
+ if (Array.isArray(bytes)) bytes = new Buffer(bytes);
+ else if (typeof bytes === 'string') bytes = new Buffer(bytes, 'utf8');
+ }
+
+ return crypto.createHash('md5').update(bytes).digest();
+}
+
+module.exports = md5;
diff --git a/lib/v35.js b/lib/v35.js
new file mode 100644
index 00000000..3e01871c
--- /dev/null
+++ b/lib/v35.js
@@ -0,0 +1,57 @@
+var bytesToUuid = require('./bytesToUuid');
+
+function uuidToBytes(uuid) {
+ // Note: We assume we're being passed a valid uuid string
+ var bytes = [];
+ uuid.replace(/[a-fA-F0-9]{2}/g, function(hex) {
+ bytes.push(parseInt(hex, 16));
+ });
+
+ return bytes;
+}
+
+function stringToBytes(str) {
+ str = unescape(encodeURIComponent(str)); // UTF8 escape
+ var bytes = new Array(str.length);
+ for (var i = 0; i < str.length; i++) {
+ bytes[i] = str.charCodeAt(i);
+ }
+ return bytes;
+}
+
+
+
+module.exports = function(name, version, hashfunc){
+
+ var generateUUID = function (value, namespace, buf, offset) {
+ var off = buf && offset || 0;
+
+ if (typeof(value) == 'string') value = stringToBytes(value);
+ if (typeof(namespace) == 'string') namespace = uuidToBytes(namespace);
+
+ if (!Array.isArray(value)) throw TypeError('value must be an array of bytes');
+ if (!Array.isArray(namespace) || namespace.length != 16) throw TypeError('namespace must be uuid string or an Array of 16 byte values');
+
+ // Per 4.3
+ var bytes = hashfunc(namespace.concat(value));
+ bytes[6] = (bytes[6] & 0x0f) | version;
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
+
+ if (buf) {
+ for (var idx = 0; idx < 16; ++idx) {
+ buf[off+idx] = bytes[idx];
+ }
+ }
+
+ return buf || bytesToUuid(bytes);
+ }
+
+ generateUUID.name = name
+
+ // Pre-defined namespaces, per Appendix C
+ generateUUID.DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
+ generateUUID.URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
+
+ return generateUUID;
+
+}
diff --git a/package.json b/package.json
index 220ec747..aa8786b6 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,8 @@
},
"browser": {
"./lib/rng.js": "./lib/rng-browser.js",
- "./lib/sha1.js": "./lib/sha1-browser.js"
+ "./lib/sha1.js": "./lib/sha1-browser.js",
+ "./lib/md5.js": "./lib/md5-browser.js"
},
"repository": {
"type": "git",
diff --git a/test/test.js b/test/test.js
index 56b13e3b..c35a1eea 100644
--- a/test/test.js
+++ b/test/test.js
@@ -9,21 +9,21 @@ var HASH_SAMPLES = [
{
input: '',
sha1: 'da39a3ee5e6b4b0d3255bfef95601890afd80709',
- md5: '',
+ md5: 'd41d8cd98f00b204e9800998ecf8427e',
},
// Extended ascii chars
{
input: '\t\b\f !\"#$%&\'()*+,-.\/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF',
sha1: 'ca4a426a3d536f14cfd79011e79e10d64de950a0',
- md5: '',
+ md5: 'e8098ec21950f841731d28749129d3ee',
},
// A sampling from the Unicode BMP
{
input: '\u00A5\u0104\u018F\u0256\u02B1o\u0315\u038E\u0409\u0500\u0531\u05E1\u05B6\u0920\u0903\u09A4\u0983\u0A20\u0A02\u0AA0\u0A83\u0B06\u0C05\u0C03\u1401\u16A0',
sha1: 'f2753ebc390e5f637e333c2a4179644a93ae9f65',
- md5: '',
+ md5: '231b309e277b6be8bb3d6c688b7f098b',
}
];
@@ -63,6 +63,46 @@ test('sha1 browser', function() {
});
});
+test('md5 node', function() {
+ var md5 = require('../lib/md5');
+
+ HASH_SAMPLES.forEach(function(sample) {
+ // Convert the sha1 Buffer to an Array here so we can call map() on it in hashToHex
+ assert.equal(hashToHex(Array.prototype.slice.apply(md5(sample.input))), sample.md5);
+ });
+});
+
+test('md5 browser', function() {
+ var md5 = require('../lib/md5-browser');
+
+ HASH_SAMPLES.forEach(function(sample) {
+ assert.equal(hashToHex(md5(sample.input)), sample.md5);
+ });
+});
+
+test('v3', function() {
+ var v3 = require('../v3');
+
+ // Expect to get the same results as http://tools.adjet.org/uuid-v3
+ assert.equal(v3('hello.example.com', v3.DNS), '9125a8dc-52ee-365b-a5aa-81b0b3681cf6');
+ assert.equal(v3('http://example.com/hello', v3.URL), 'c6235813-3ba4-3801-ae84-e0a6ebb7d138');
+ assert.equal(v3('hello', '0f5abcd1-c194-47f3-905b-2df7263a084b'), 'a981a0c2-68b1-35dc-bcfc-296e52ab01ec');
+
+ // test the buffer functionality
+ var buf = new Array(16);
+ var testBuf = [0x91, 0x25, 0xa8, 0xdc, 0x52, 0xee, 0x36, 0x5b, 0xa5, 0xaa, 0x81, 0xb0, 0xb3, 0x68, 0x1c, 0xf6];
+ v3('hello.example.com', v3.DNS, buf);
+ assert.ok(buf.length === testBuf.length && buf.every(function (elem, idx) { return elem === testBuf[idx]; }));
+
+ // test offsets as well
+ buf = new Array(19);
+ for (var i=0; i<3; ++i) buf[i] = 'landmaster';
+ v3('hello.example.com', v3.DNS, buf, 3);
+ assert.ok(buf.length === testBuf.length+3 && buf.every(function (elem, idx) {
+ return (idx >= 3) ? (elem === testBuf[idx-3]) : (elem === 'landmaster');
+ }), "hello");
+});
+
test('v5', function() {
var v5 = require('../v5');
diff --git a/v3.js b/v3.js
new file mode 100644
index 00000000..ee7e14c0
--- /dev/null
+++ b/v3.js
@@ -0,0 +1,4 @@
+var v35 = require('./lib/v35.js');
+var md5 = require('./lib/md5');
+
+module.exports = v35('v3', 0x30, md5);
\ No newline at end of file
diff --git a/v5.js b/v5.js
index 07eaf747..358f6570 100644
--- a/v5.js
+++ b/v5.js
@@ -1,50 +1,3 @@
-var sha1 = require('./lib/sha1-browser');
-var bytesToUuid = require('./lib/bytesToUuid');
-
-function uuidToBytes(uuid) {
- // Note: We assume we're being passed a valid uuid string
- var bytes = [];
- uuid.replace(/[a-fA-F0-9]{2}/g, function(hex) {
- bytes.push(parseInt(hex, 16));
- });
-
- return bytes;
-}
-
-function stringToBytes(str) {
- str = unescape(encodeURIComponent(str)); // UTF8 escape
- var bytes = new Array(str.length);
- for (var i = 0; i < str.length; i++) {
- bytes[i] = str.charCodeAt(i);
- }
- return bytes;
-}
-
-function v5(name, namespace, buf, offset) {
- var off = buf && offset || 0;
-
- if (typeof(name) == 'string') name = stringToBytes(name);
- if (typeof(namespace) == 'string') namespace = uuidToBytes(namespace);
-
- if (!Array.isArray(name)) throw TypeError('name must be an array of bytes');
- if (!Array.isArray(namespace) || namespace.length != 16) throw TypeError('namespace must be uuid string or an Array of 16 byte values');
-
- // Per 4.3
- var bytes = sha1(namespace.concat(name));
- bytes[6] = (bytes[6] & 0x0f) | 0x50;
- bytes[8] = (bytes[8] & 0x3f) | 0x80;
-
- if (buf) {
- for (var idx = 0; idx < 16; ++idx) {
- buf[off+idx] = bytes[idx];
- }
- }
-
- return buf || bytesToUuid(bytes);
-}
-
-// Pre-defined namespaces, per Appendix C
-v5.DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
-v5.URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
-
-module.exports = v5;
+var v35 = require('./lib/v35.js');
+var sha1 = require('./lib/sha1');
+module.exports = v35('v5', 0x50, sha1);
\ No newline at end of file