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.
- data/ext/id_shuffler/id_shuffler.c +144 -76
- 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
|
-
|
5
|
-
|
97
|
+
/* takes a number and a key string as input and returns a base-32 encoded
|
98
|
+
* shuffled id. */
|
6
99
|
|
7
|
-
|
8
|
-
unsigned long
|
9
|
-
|
100
|
+
/* convert from ruby objects */
|
101
|
+
unsigned long original_ulong;
|
102
|
+
original_ulong = NUM2ULONG(original_num);
|
10
103
|
char *key = StringValuePtr(key_str);
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
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 = "
|
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
|
-
|
51
|
-
return
|
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
|
-
|
56
|
-
|
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
|
-
|
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
|
-
|
61
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
109
|
-
|
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);
|