id_shuffler 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|