id_shuffler 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ext/id_shuffler/extconf.rb +4 -0
- data/ext/id_shuffler/id_shuffler.c +110 -0
- data/lib/id_shuffler.rb +28 -0
- metadata +79 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
static VALUE r_base32_shuffle(VALUE self, VALUE original_num, VALUE key_str) {
|
4
|
+
// takes a number and a key string as input and returns a base-32 encoded
|
5
|
+
// shuffled id.
|
6
|
+
|
7
|
+
// convert from ruby objects to local long/string
|
8
|
+
unsigned long original;
|
9
|
+
original = NUM2ULONG(original_num);
|
10
|
+
char *key = StringValuePtr(key_str);
|
11
|
+
// bounds check the id
|
12
|
+
if (original >= (1 << 30)) {
|
13
|
+
return rb_str_new2("");
|
14
|
+
}
|
15
|
+
// initialize the feistel network and lfsr
|
16
|
+
unsigned int keypos, key_len;
|
17
|
+
unsigned long l, r, r_orig;
|
18
|
+
unsigned long lfsr_bit, lfsr_outbit, lfsr_num;
|
19
|
+
l = (original >> 15) & 32767;
|
20
|
+
r = original & 32767;
|
21
|
+
key_len = strlen(key);
|
22
|
+
// apply the feistel network in a loop over each char in key. the round
|
23
|
+
// function uses the key char as the taps mask for a single iteration of an
|
24
|
+
// lfsr.
|
25
|
+
for (keypos = 0; keypos < key_len; keypos++) {
|
26
|
+
r_orig = r;
|
27
|
+
lfsr_num = r+key[keypos];
|
28
|
+
lfsr_outbit = 0;
|
29
|
+
for (lfsr_bit = 0; lfsr_bit < 15; lfsr_bit++) {
|
30
|
+
if ((key[keypos] & (1 >> lfsr_bit)) > 0) {
|
31
|
+
lfsr_outbit = lfsr_outbit ^ ((lfsr_num & (1 >> lfsr_bit)) >> lfsr_bit);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
lfsr_num = (lfsr_num >> 1) + (lfsr_outbit << 14);
|
35
|
+
lfsr_num = lfsr_num & 32767;
|
36
|
+
r = (l ^ lfsr_num);
|
37
|
+
l = r_orig;
|
38
|
+
}
|
39
|
+
// base32 encode the result
|
40
|
+
unsigned long shuffled;
|
41
|
+
shuffled = (l << 15) + r;
|
42
|
+
char buf[6];
|
43
|
+
char *digits = "0123456789abcdefghijklmnopqrstuv";
|
44
|
+
int cpos;
|
45
|
+
for (cpos = 0; cpos < 6; cpos ++) {
|
46
|
+
buf[cpos] = digits[((shuffled >> (5*(5-cpos))) & 31)];
|
47
|
+
}
|
48
|
+
// return a ruby string
|
49
|
+
return rb_str_new2(buf);
|
50
|
+
}
|
51
|
+
|
52
|
+
static VALUE r_base32_unshuffle(VALUE self, VALUE shuffled_str, VALUE key_str) {
|
53
|
+
// take the a base-32 encoded ruby string and a key string, decodes and
|
54
|
+
// unshuffles it, producing the original number.
|
55
|
+
|
56
|
+
// convert from ruby string to local buf
|
57
|
+
char *b32 = StringValuePtr(shuffled_str);
|
58
|
+
char *key = StringValuePtr(key_str);
|
59
|
+
// remove base32 encoding
|
60
|
+
unsigned int shuffled = 0;
|
61
|
+
char *found;
|
62
|
+
char *digits = "0123456789abcdefghijklmnopqrstuv";
|
63
|
+
if (strlen(b32) != 6) {
|
64
|
+
return INT2NUM(-1);
|
65
|
+
}
|
66
|
+
int cpos = 0;
|
67
|
+
for (cpos = 0; cpos < 6; cpos++) {
|
68
|
+
found = strchr(digits,b32[cpos]);
|
69
|
+
if (found) {
|
70
|
+
int pos = (found - digits);
|
71
|
+
shuffled = shuffled + (pos << (5 * (5-cpos)));
|
72
|
+
} else {
|
73
|
+
return INT2NUM(-1);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
// iterate the feistel network in reverse
|
77
|
+
int keypos,key_len;
|
78
|
+
unsigned long l, r, l_orig;
|
79
|
+
unsigned long lfsr_bit, lfsr_outbit, lfsr_num;
|
80
|
+
l = (shuffled >> 15) & 32767;
|
81
|
+
r = shuffled & 32767;
|
82
|
+
key_len = strlen(key);
|
83
|
+
for (keypos = (key_len - 1); keypos >= 0; keypos--) {
|
84
|
+
l_orig = l;
|
85
|
+
lfsr_num = l+key[keypos];
|
86
|
+
lfsr_outbit = 0;
|
87
|
+
for (lfsr_bit = 0; lfsr_bit < 15; lfsr_bit++) {
|
88
|
+
if ((key[keypos] & (1 >> lfsr_bit)) > 0) {
|
89
|
+
lfsr_outbit = lfsr_outbit ^ ((lfsr_num & (1 >> lfsr_bit)) >> lfsr_bit);
|
90
|
+
}
|
91
|
+
}
|
92
|
+
lfsr_num = (lfsr_num >> 1) + (lfsr_outbit << 14);
|
93
|
+
lfsr_num = lfsr_num & 32767;
|
94
|
+
l = (r ^ lfsr_num);
|
95
|
+
r = l_orig;
|
96
|
+
}
|
97
|
+
// return the resulting original number as a ruby fixnum
|
98
|
+
unsigned long original;
|
99
|
+
original = (l << 15) + r;
|
100
|
+
return ULONG2NUM(original);
|
101
|
+
}
|
102
|
+
|
103
|
+
void Init_id_shuffler(void) {
|
104
|
+
// add two class methods to our IdShuffler class: shuffle and unshuffle, each
|
105
|
+
// of which take the object to be [unshuffled] and the key.
|
106
|
+
VALUE klass = rb_define_class("IdShuffler", rb_cObject);
|
107
|
+
rb_define_singleton_method(klass, "shuffle", r_base32_shuffle, 2);
|
108
|
+
rb_define_singleton_method(klass, "unshuffle", r_base32_unshuffle, 2);
|
109
|
+
}
|
110
|
+
|
data/lib/id_shuffler.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# load the c extension
|
2
|
+
require 'id_shuffler/id_shuffler'
|
3
|
+
|
4
|
+
# key strings are hashed via md5 to make them a good length (the shuffle algo
|
5
|
+
# iterates once per character in the key) as well as to provide good spread of
|
6
|
+
# bits per character (whearas a plain text ascii key would only use a narrow
|
7
|
+
# range of bits)
|
8
|
+
require 'digest/md5'
|
9
|
+
|
10
|
+
class IdShuffler
|
11
|
+
|
12
|
+
def initialize(key)
|
13
|
+
@key = Digest::MD5.digest(key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def key=(key)
|
17
|
+
@key = Digest::MD5.digest(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def shuffle(i)
|
21
|
+
self.class.shuffle(i.to_i, @key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def unshuffle(s)
|
25
|
+
self.class.unshuffle(s.to_s, @key)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: id_shuffler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Whiting
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-20 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! '
|
15
|
+
|
16
|
+
An efficient solution to use when it is undesirable to expose internal database
|
17
|
+
|
18
|
+
ids, IdShuffler converts integers like 123 into strings like ''q34nr1'', and
|
19
|
+
|
20
|
+
vice-versa, using a very lightweight integer scrambling algorithm plus
|
21
|
+
|
22
|
+
''Crockford 32'' encoding. It is built as a native C extension and so is very
|
23
|
+
|
24
|
+
fast. The algorithm takes a string key as a seed, so you can use different
|
25
|
+
|
26
|
+
keys for different id spaces and thus obtain different slugs for the same
|
27
|
+
|
28
|
+
initial integer. This is not a security solution and I am not a cryptographer;
|
29
|
+
|
30
|
+
it should be assumed a determined individual can unshuffle the ids without
|
31
|
+
|
32
|
+
knowing the key used to generate them. Also note these are 30-bit ids, so the
|
33
|
+
|
34
|
+
library can only represent values up to approximately 1 billion
|
35
|
+
|
36
|
+
(1,073,741,823).
|
37
|
+
|
38
|
+
|
39
|
+
This gem is still under development in so far as I have not written tests or
|
40
|
+
|
41
|
+
documentation for it.
|
42
|
+
|
43
|
+
|
44
|
+
'
|
45
|
+
email: josh@protagonistlabs.com
|
46
|
+
executables: []
|
47
|
+
extensions:
|
48
|
+
- ext/id_shuffler/extconf.rb
|
49
|
+
extra_rdoc_files: []
|
50
|
+
files:
|
51
|
+
- lib/id_shuffler.rb
|
52
|
+
- ext/id_shuffler/id_shuffler.c
|
53
|
+
- ext/id_shuffler/extconf.rb
|
54
|
+
homepage: http://rubygems.org/gems/id_shuffler
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.23
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Turns private database ids into lightly-encrypted public-facing strings
|
79
|
+
test_files: []
|