id_shuffler 0.0.2 → 0.0.3

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.
Files changed (2) hide show
  1. data/ext/id_shuffler/id_shuffler.c +144 -76
  2. metadata +1 -1
@@ -1,70 +1,156 @@
1
1
  #include <ruby.h>
2
+ #include <stdint.h>
3
+
4
+ /*
5
+ * the algorithm used here is based on greg rose's implementation of skip32
6
+ * (https://opensource.qualcomm.com/assets/dotc/skip32.c) and modified as
7
+ * follows:
8
+ *
9
+ * - using 30 bit numbers instead of 32 (so we get nice round 6-digit base-32
10
+ * outputs, rather than having 7-digit base-32 numbers that never use the
11
+ * upper 3 bits. when we need more than 30 bits, we should switch up to 40,
12
+ * 50, or 60 bits to keep the output string range fully utilized)
13
+ *
14
+ * - the feistel network round function returns 15 bits instead of 16
15
+ *
16
+ * - use uint32_t explicitely where necessary to ensure 32-bit numbers
17
+ * on 64-bit platforms
18
+ *
19
+ * - use 6-digits of 'crockford' base-32 armor on the shuffled ids. this not
20
+ * only prevents them from looking completely like numbers but also ensures
21
+ * they are a constant length, which is very handy for the sake of future
22
+ * compatibility since you can just say any 6-digit shuffle id is "version 1"
23
+ * id, and a different length implies a different shuffler.
24
+ *
25
+ * note i am most certainly not a cryptographer. i assume my modifications have
26
+ * made the algorithm all the weaker. this should be considered an obfuscation
27
+ * tool, not a cryptographic tool. please assume a mildly determined attacker
28
+ * could decode the shuffled ids, especially if they can generate or otherwise
29
+ * order them sequentially which is not too hard for an end-user in most
30
+ * production systems.
31
+ *
32
+ */
33
+
34
+ const char ftable[256] = {
35
+ 0xa3,0xd7,0x09,0x83,0xf8,0x48,0xf6,0xf4,0xb3,0x21,0x15,0x78,0x99,0xb1,0xaf,0xf9,
36
+ 0xe7,0x2d,0x4d,0x8a,0xce,0x4c,0xca,0x2e,0x52,0x95,0xd9,0x1e,0x4e,0x38,0x44,0x28,
37
+ 0x0a,0xdf,0x02,0xa0,0x17,0xf1,0x60,0x68,0x12,0xb7,0x7a,0xc3,0xe9,0xfa,0x3d,0x53,
38
+ 0x96,0x84,0x6b,0xba,0xf2,0x63,0x9a,0x19,0x7c,0xae,0xe5,0xf5,0xf7,0x16,0x6a,0xa2,
39
+ 0x39,0xb6,0x7b,0x0f,0xc1,0x93,0x81,0x1b,0xee,0xb4,0x1a,0xea,0xd0,0x91,0x2f,0xb8,
40
+ 0x55,0xb9,0xda,0x85,0x3f,0x41,0xbf,0xe0,0x5a,0x58,0x80,0x5f,0x66,0x0b,0xd8,0x90,
41
+ 0x35,0xd5,0xc0,0xa7,0x33,0x06,0x65,0x69,0x45,0x00,0x94,0x56,0x6d,0x98,0x9b,0x76,
42
+ 0x97,0xfc,0xb2,0xc2,0xb0,0xfe,0xdb,0x20,0xe1,0xeb,0xd6,0xe4,0xdd,0x47,0x4a,0x1d,
43
+ 0x42,0xed,0x9e,0x6e,0x49,0x3c,0xcd,0x43,0x27,0xd2,0x07,0xd4,0xde,0xc7,0x67,0x18,
44
+ 0x89,0xcb,0x30,0x1f,0x8d,0xc6,0x8f,0xaa,0xc8,0x74,0xdc,0xc9,0x5d,0x5c,0x31,0xa4,
45
+ 0x70,0x88,0x61,0x2c,0x9f,0x0d,0x2b,0x87,0x50,0x82,0x54,0x64,0x26,0x7d,0x03,0x40,
46
+ 0x34,0x4b,0x1c,0x73,0xd1,0xc4,0xfd,0x3b,0xcc,0xfb,0x7f,0xab,0xe6,0x3e,0x5b,0xa5,
47
+ 0xad,0x04,0x23,0x9c,0x14,0x51,0x22,0xf0,0x29,0x79,0x71,0x7e,0xff,0x8c,0x0e,0xe2,
48
+ 0x0c,0xef,0xbc,0x72,0x75,0x6f,0x37,0xa1,0xec,0xd3,0x8e,0x62,0x8b,0x86,0x10,0xe8,
49
+ 0x08,0x77,0x11,0xbe,0x92,0x4f,0x24,0xc5,0x32,0x36,0x9d,0xcf,0xf3,0xa6,0xbb,0xac,
50
+ 0x5e,0x6c,0xa9,0x13,0x57,0x25,0xb5,0xe3,0xbd,0xa8,0x3a,0x01,0x05,0x59,0x2a,0x46
51
+ };
52
+
53
+ uint32_t g(char key[10], int k, uint32_t w)
54
+ {
55
+ uint8_t g1, g2, g3, g4, g5, g6;
56
+
57
+ g1 = (w>>7)&0xff; /* shift 7 not 8, as w is actually 15-bit. so g1's LSB will be g2's MSB */
58
+ g2 = w&0xff;
59
+
60
+ g3 = ftable[g2 ^ key[(4*k)%10]] ^ g1;
61
+ g4 = ftable[g3 ^ key[(4*k+1)%10]] ^ g2;
62
+ g5 = ftable[g4 ^ key[(4*k+2)%10]] ^ g3;
63
+ g6 = ftable[g5 ^ key[(4*k+3)%10]] ^ g4;
64
+
65
+ return (((g5<<8) + g6) & 32767); /* clip result to 15 bits */
66
+ }
67
+
68
+ uint32_t skip30(char key[10], uint32_t num, int encrypt)
69
+ {
70
+ int k; /* round number */
71
+ int i; /* round counter */
72
+ int kstep;
73
+ uint32_t wl, wr;
74
+
75
+ /* sort out direction */
76
+ if (encrypt)
77
+ kstep = 1, k = 0;
78
+ else
79
+ kstep = -1, k = 23;
80
+
81
+ /* pack into 15-bit words */
82
+ wl = (num >> 15) & 32767;
83
+ wr = num & 32767;
84
+
85
+ /* 24 feistel rounds, doubled up */
86
+ for (i = 0; i < 24/2; ++i) {
87
+ wr ^= g(key, k, wl) ^ k;
88
+ k += kstep;
89
+ wl ^= g(key, k, wr) ^ k;
90
+ k += kstep;
91
+ }
92
+
93
+ return (wr << 15) + (wl);
94
+ }
2
95
 
3
96
  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.
97
+ /* takes a number and a key string as input and returns a base-32 encoded
98
+ * shuffled id. */
6
99
 
7
- // convert from ruby objects to local long/string
8
- unsigned long original;
9
- original = NUM2ULONG(original_num);
100
+ /* convert from ruby objects */
101
+ unsigned long original_ulong;
102
+ original_ulong = NUM2ULONG(original_num);
10
103
  char *key = StringValuePtr(key_str);
11
- // bounds check the id
12
- if (original >= (1 << 30)) {
13
- return rb_str_new2("");
104
+
105
+ /* bounds check the id */
106
+ if (original_ulong >= (1 << 30)) {
107
+ return rb_str_new2("input integer exceeds 30-bit maximum");
14
108
  }
15
- // initialize the feistel network and lfsr
16
- unsigned int keypos, key_len;
17
- unsigned long l, r, r_orig;
18
- unsigned long lfsr, lfsr_check_bit, lfsr_out_bit, lfsr_round;
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 current key character as the taps mask for several
24
- // iterations of a 32-bit lfsr
25
- for (keypos = 0; keypos < key_len; keypos++) {
26
- r_orig = r;
27
- lfsr = r + 255; // ensure nonzero starting condition in the lfsr
28
- for (lfsr_round = 0; lfsr_round < 15; lfsr_round++) {
29
- lfsr_out_bit = 0;
30
- for (lfsr_check_bit = 0; lfsr_check_bit < 8; lfsr_check_bit++) {
31
- if ((key[keypos] & (1 << lfsr_check_bit)) > 0) {
32
- lfsr_out_bit = lfsr_out_bit ^ ((lfsr & (1 << lfsr_check_bit)) >> lfsr_check_bit);
33
- }
34
- }
35
- lfsr = (lfsr << 1) + lfsr_out_bit;
36
- }
37
- lfsr = lfsr & 32767;
38
- r = (l ^ lfsr);
39
- l = r_orig;
109
+ if (RSTRING_LEN(key_str) < 10) {
110
+ /* the algorithm uses an 80-bit key. the key string must be at least 10
111
+ * chars long, and anything beyond that is ignored. */
112
+ return rb_str_new2("invalid key length");
40
113
  }
41
- // base32 encode the result
42
- unsigned long shuffled;
43
- shuffled = (l << 15) + r;
114
+
115
+ /* encrypt */
116
+ uint32_t original = original_ulong;
117
+ uint32_t shuffled = skip30(key, original, 1);
118
+
119
+ /* base-32 encode the result using "crockford 32" alphabet */
44
120
  char buf[6];
45
- char *digits = "0123456789abcdefghijklmnopqrstuv";
121
+ char *digits = "0123456789abcdefghjkmnpqrstvwxyz";
46
122
  int cpos;
47
123
  for (cpos = 0; cpos < 6; cpos ++) {
48
124
  buf[cpos] = digits[((shuffled >> (5*(5-cpos))) & 31)];
49
125
  }
50
- // return a ruby string
51
- return rb_str_new2(buf);
126
+
127
+ /* return a ruby string */
128
+ return rb_str_new(buf,6);
52
129
  }
53
130
 
54
131
  static VALUE r_base32_unshuffle(VALUE self, VALUE shuffled_str, VALUE key_str) {
55
- // take the a base-32 encoded ruby string and a key string, decodes and
56
- // unshuffles it, producing the original number.
132
+ /* take the a base-32 encoded ruby string and a key string, decodes and
133
+ * unshuffles it, producing the original number. */
57
134
 
58
- // convert from ruby string to local buf
135
+ /* TODO: take advantage of crockford 32 and perform corrections on digits that
136
+ * may have been mistyped or case munged, for instance "l" -> 1, "o" -> 0, "A"
137
+ * -> "a" etc. */
138
+
139
+ /* convert from ruby string to char buf */
59
140
  char *b32 = StringValuePtr(shuffled_str);
60
- char *key = StringValuePtr(key_str);
61
- // remove base32 encoding
62
- unsigned int shuffled = 0;
63
- char *found;
64
- char *digits = "0123456789abcdefghijklmnopqrstuv";
65
- if (strlen(b32) != 6) {
141
+ if (RSTRING_LEN(shuffled_str) != 6) {
142
+ /* must be 6 base-32 digits */
66
143
  return INT2NUM(-1);
67
144
  }
145
+ char *key = StringValuePtr(key_str);
146
+ if (RSTRING_LEN(key_str) < 10) {
147
+ return rb_str_new2("invalid key length");
148
+ }
149
+
150
+ /* remove base32 encoding */
151
+ uint32_t shuffled = 0;
152
+ char *found;
153
+ char *digits = "0123456789abcdefghjkmnpqrstvwxyz";
68
154
  int cpos = 0;
69
155
  for (cpos = 0; cpos < 6; cpos++) {
70
156
  found = strchr(digits,b32[cpos]);
@@ -72,41 +158,23 @@ static VALUE r_base32_unshuffle(VALUE self, VALUE shuffled_str, VALUE key_str) {
72
158
  int pos = (found - digits);
73
159
  shuffled = shuffled + (pos << (5 * (5-cpos)));
74
160
  } else {
161
+ /* not valid "crockford" base-32 */
75
162
  return INT2NUM(-1);
76
163
  }
77
164
  }
78
- // unwind
79
- int keypos,key_len;
80
- unsigned long l, r, l_orig;
81
- unsigned long lfsr, lfsr_check_bit, lfsr_out_bit, lfsr_round;
82
- l = (shuffled >> 15) & 32767;
83
- r = shuffled & 32767;
84
- key_len = strlen(key);
85
- for (keypos = (key_len - 1); keypos >= 0; keypos--) {
86
- l_orig = l;
87
- lfsr = l + 255;
88
- for (lfsr_round = 0; lfsr_round < 15; lfsr_round++) {
89
- lfsr_out_bit = 0;
90
- for (lfsr_check_bit = 0; lfsr_check_bit < 8; lfsr_check_bit++) {
91
- if ((key[keypos] & (1 << lfsr_check_bit)) > 0) {
92
- lfsr_out_bit = lfsr_out_bit ^ ((lfsr & (1 << lfsr_check_bit)) >> lfsr_check_bit);
93
- }
94
- }
95
- lfsr = (lfsr << 1) + lfsr_out_bit;
96
- }
97
- lfsr = lfsr & 32767;
98
- l = (r ^ lfsr);
99
- r = l_orig;
100
- }
101
- // return the resulting original number as a ruby fixnum
102
- unsigned long original;
103
- original = (l << 15) + r;
165
+
166
+ /* decrypt */
167
+ uint32_t original = skip30(key, shuffled, 0);
168
+
169
+ /* return a ruby number */
104
170
  return ULONG2NUM(original);
105
171
  }
106
172
 
107
173
  void Init_id_shuffler(void) {
108
- // add two class methods to our IdShuffler class: shuffle and unshuffle, each
109
- // of which take the object to be [unshuffled] and the key.
174
+ /* add two class methods to our IdShuffler class: shuffle and unshuffle, each
175
+ * of which take the object to be [unshuffled] and the key. the ruby module
176
+ * in id_shuffler.rb should be the only one to ever call these, clients
177
+ * should use the module's interface */
110
178
  VALUE klass = rb_define_class("IdShuffler", rb_cObject);
111
179
  rb_define_singleton_method(klass, "shuffle_with_raw_key", r_base32_shuffle, 2);
112
180
  rb_define_singleton_method(klass, "unshuffle_with_raw_key", r_base32_unshuffle, 2);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: id_shuffler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: