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.
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+
3
+ create_makefile('id_shuffler/id_shuffler')
4
+
@@ -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
+
@@ -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: []