xxtea 1.0.0 → 1.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +33 -19
- data/ext/xxtea/xxtea.c +96 -17
- data/lib/xxtea/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52dd181dad48d0089d4f84d4b715b0c7231ea004c35928e4ec5c154b5c19a569
|
|
4
|
+
data.tar.gz: b60c3915ae8573e6d234cd6e9315f48ccb893c535d78ed3defd5f61428b4c825
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a49001c4bb5c6e53a495bf3a96ecc61d084bb822a77dfda6370dd432b5f50ae54321ffbec3736cc1773fc68059465cb42aeedb3db2511b54c86f44ba390976f0
|
|
7
|
+
data.tar.gz: a9671360c531507c76ddec41561817312e4f0c961ef1bc6865b6e2ae5ff1ac39eab35d990ca1cab69317992e815e34277e039b60cab7b6b692aa20c550f51731
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
- Add named padding schemes: `:pkcs7_4_min8` (default, also `true`), `:pkcs7_8`, and `:none` (also `false`).
|
|
6
|
+
- `:pkcs7_4_min8` is 4-byte PKCS#7-like with an 8-byte minimum (pad values 5–8 for short inputs), compatible with Python xxtea.
|
|
7
|
+
- `:pkcs7_8` is standard 8-byte PKCS#7, compatible with Python [xxteang](https://github.com/ifduyue/xxteang).
|
|
8
|
+
|
|
3
9
|
## 1.0.0
|
|
4
10
|
|
|
5
11
|
- Rewrite as a Ruby C extension compatible with Python [xxtea](https://github.com/ifduyue/xxtea) 5.3.3.
|
data/README.md
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
[XXTEA](https://en.wikipedia.org/wiki/XXTEA) implemented as a Ruby C extension, licensed under 2-clause BSD.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Default ciphertext is compatible with the [Python xxtea](https://github.com/ifduyue/xxtea) package.
|
|
9
|
+
`padding: :pkcs7_8` is compatible with [Python xxteang](https://github.com/ifduyue/xxteang).
|
|
9
10
|
|
|
10
11
|
The XXTEA algorithm takes a 128-bit key and operates on an array of 32-bit
|
|
11
12
|
integers (at least 2 integers), but it doesn't define the conversions between
|
|
@@ -21,12 +22,11 @@ integers, which is required by the XXTEA algorithm). As a result of these
|
|
|
21
22
|
measures, you can encrypt not only texts, but also any binary bytes of any
|
|
22
23
|
length.
|
|
23
24
|
|
|
24
|
-
> **Note:**
|
|
25
|
-
>
|
|
26
|
-
>
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
> for raw XXTEA (requires data length ≥ 8 and multiple of 4).
|
|
25
|
+
> **Note:** The default (`:pkcs7_4_min8`) is **not** standard 4-byte PKCS#7.
|
|
26
|
+
> For inputs shorter than 4 bytes it pads an extra 4 bytes (pad values 5–8)
|
|
27
|
+
> to satisfy XXTEA's 2-word minimum. Pass `padding: :pkcs7_8` for standard
|
|
28
|
+
> 8-byte PKCS#7 (compatible with Python xxteang), or `padding: false` for
|
|
29
|
+
> raw XXTEA (requires data length ≥ 8 and multiple of 4).
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
@@ -87,9 +87,10 @@ They are stored on the object and used by every `encrypt`, `decrypt`,
|
|
|
87
87
|
`encrypt_hex`, and `decrypt_hex` call:
|
|
88
88
|
|
|
89
89
|
```ruby
|
|
90
|
-
c = XXTEA.new(key)
|
|
91
|
-
c = XXTEA.new(key, rounds: 64)
|
|
92
|
-
c = XXTEA.new(key, padding: false)
|
|
90
|
+
c = XXTEA.new(key) # rounds=0, padding=:pkcs7_4_min8
|
|
91
|
+
c = XXTEA.new(key, rounds: 64) # override rounds
|
|
92
|
+
c = XXTEA.new(key, padding: false) # disable padding
|
|
93
|
+
c = XXTEA.new(key, padding: :pkcs7_8) # 8-byte PKCS#7
|
|
93
94
|
c = XXTEA.new(key, padding: false, rounds: 42)
|
|
94
95
|
```
|
|
95
96
|
|
|
@@ -103,19 +104,31 @@ s == XXTEA.decrypt([hexenc].pack("H*"), key) # => true
|
|
|
103
104
|
|
|
104
105
|
## Padding
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
scheme. The pad byte value is `4 - (data.bytesize & 3)` (range 1–4), plus an
|
|
108
|
-
extra 4 bytes when the input is shorter than 4 bytes to meet XXTEA's 2-word
|
|
109
|
-
minimum (producing pad values 5–8).
|
|
107
|
+
`padding` accepts a scheme name, so more paddings can be added later:
|
|
110
108
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
| Value | Meaning |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `true` or `:pkcs7_4_min8` (default) | 4-byte PKCS#7-like, padded to at least 8 bytes. Compatible with Python xxtea. Not standard 4-byte PKCS#7 |
|
|
112
|
+
| `:pkcs7_8` | Standard **8-byte** PKCS#7, compatible with Python xxteang |
|
|
113
|
+
| `false` or `:none` | No padding (raw XXTEA) |
|
|
114
|
+
|
|
115
|
+
`XXTEA::PKCS7_4_MIN8` and `XXTEA::PKCS7_8` are aliases for the symbols.
|
|
116
|
+
|
|
117
|
+
The default `:pkcs7_4_min8` scheme uses pad byte value `4 - (data.bytesize & 3)`
|
|
118
|
+
(range 1–4), plus an extra 4 bytes when the input is shorter than 4 bytes
|
|
119
|
+
to meet XXTEA's 2-word minimum (producing pad values 5–8). Standard 4-byte
|
|
120
|
+
PKCS#7 never uses pad values 5–8. Because padding always adds at least one
|
|
121
|
+
byte, encrypting an 8-byte input produces a 12-byte ciphertext.
|
|
122
|
+
|
|
123
|
+
8-byte PKCS#7 uses pad byte value `8 - (data.bytesize & 7)` (range 1–8).
|
|
124
|
+
Encrypting an 8-byte input produces a 16-byte ciphertext.
|
|
115
125
|
|
|
116
126
|
```ruby
|
|
117
127
|
XXTEA.decrypt_hex(XXTEA.encrypt_hex("", key), key) # => ""
|
|
118
128
|
XXTEA.decrypt_hex(XXTEA.encrypt_hex(" ", key), key) # => " "
|
|
129
|
+
|
|
130
|
+
XXTEA.encrypt("12345678", key).bytesize # => 12 (:pkcs7_4_min8)
|
|
131
|
+
XXTEA.encrypt("12345678", key, padding: :pkcs7_8).bytesize # => 16 (:pkcs7_8)
|
|
119
132
|
```
|
|
120
133
|
|
|
121
134
|
You can disable padding by setting `padding: false`.
|
|
@@ -196,7 +209,8 @@ XXTEA.new("k" * 16, rounds: 2**32)
|
|
|
196
209
|
|
|
197
210
|
## Compatibility
|
|
198
211
|
|
|
199
|
-
- Compatible with [Python xxtea](https://github.com/ifduyue/xxtea) (
|
|
212
|
+
- Compatible with [Python xxtea](https://github.com/ifduyue/xxtea) (default `:pkcs7_4_min8` padding, endianness, and rounds).
|
|
213
|
+
- `padding: :pkcs7_8` is compatible with [Python xxteang](https://github.com/ifduyue/xxteang).
|
|
200
214
|
- The `XXTEA.encrypt(data, key)` / `XXTEA.decrypt(data, key)` class methods remain compatible with gem 0.0.1 for valid ciphertext. `XXTEA` is now a class rather than a module, and invalid padding raises `ArgumentError` instead of returning stripped bytes.
|
|
201
215
|
|
|
202
216
|
## Releasing
|
data/ext/xxtea/xxtea.c
CHANGED
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
23
23
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
25
|
+
* Default ciphertext is compatible with the Python xxtea package
|
|
26
26
|
* (https://github.com/ifduyue/xxtea): little-endian 32-bit words and
|
|
27
|
-
*
|
|
27
|
+
* :pkcs7_4_min8 padding (4-byte PKCS#7-like, pad+4 for inputs shorter than 4 bytes).
|
|
28
|
+
* padding: :pkcs7_8 uses 8-byte PKCS#7, compatible with Python xxteang
|
|
29
|
+
* (https://github.com/ifduyue/xxteang).
|
|
28
30
|
*/
|
|
29
31
|
|
|
30
32
|
#include "ruby.h"
|
|
@@ -45,6 +47,9 @@
|
|
|
45
47
|
|
|
46
48
|
static ID id_padding;
|
|
47
49
|
static ID id_rounds;
|
|
50
|
+
static ID id_none;
|
|
51
|
+
static ID id_pkcs7_4_min8;
|
|
52
|
+
static ID id_pkcs7_8;
|
|
48
53
|
static VALUE cXXTEA;
|
|
49
54
|
|
|
50
55
|
typedef struct {
|
|
@@ -134,24 +139,45 @@ bytes2longs(const char *in, long inlen, uint32_t *out, int padding)
|
|
|
134
139
|
#endif
|
|
135
140
|
}
|
|
136
141
|
|
|
142
|
+
i = nwords << 2;
|
|
143
|
+
|
|
144
|
+
if (padding == 8) {
|
|
145
|
+
/*
|
|
146
|
+
* 8-byte PKCS#7 (xxteang): pad = 8 - (len & 7), range 1-8.
|
|
147
|
+
* Completes the partial word, then adds a whole extra pad word
|
|
148
|
+
* unless the length is 4 mod 8.
|
|
149
|
+
*/
|
|
150
|
+
uint32_t w = 0;
|
|
151
|
+
int r = (int)(inlen & 3);
|
|
152
|
+
int shift = 0;
|
|
153
|
+
uint32_t pw;
|
|
154
|
+
for (; i < inlen; i++, shift += 8) {
|
|
155
|
+
w |= (uint32_t)s[i] << shift;
|
|
156
|
+
}
|
|
157
|
+
pad = 8 - (int)(inlen & 7);
|
|
158
|
+
pw = (uint32_t)pad * 0x01010101u;
|
|
159
|
+
w |= pw & (~0u << (8 * r));
|
|
160
|
+
out[nwords] = w;
|
|
161
|
+
if ((inlen & 4) == 0) {
|
|
162
|
+
out[nwords + 1] = pw;
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
137
167
|
/*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* to zero the buffer first. Inputs shorter than 4 bytes are padded
|
|
142
|
-
* to two words, which also guarantees the minimum XXTEA block size.
|
|
168
|
+
* :pkcs7_4_min8 (default): 4-byte PKCS#7-like, but inputs shorter
|
|
169
|
+
* than 4 bytes are padded to two words (pad values 5-8) for XXTEA's
|
|
170
|
+
* 2-word minimum. Not standard PKCS#7.
|
|
143
171
|
*/
|
|
144
|
-
|
|
145
|
-
if (padding || (inlen & 3) != 0) {
|
|
172
|
+
if (padding == 4 || (inlen & 3) != 0) {
|
|
146
173
|
uint32_t w = 0;
|
|
147
174
|
int r = (int)(inlen & 3);
|
|
148
175
|
int shift = 0;
|
|
149
176
|
for (; i < inlen; i++, shift += 8) {
|
|
150
177
|
w |= (uint32_t)s[i] << shift;
|
|
151
178
|
}
|
|
152
|
-
if (padding) {
|
|
179
|
+
if (padding == 4) {
|
|
153
180
|
pad = 4 - r;
|
|
154
|
-
/* Ensure XXTEA always has at least two 32-bit words. */
|
|
155
181
|
if (inlen < 4) {
|
|
156
182
|
pad += 4;
|
|
157
183
|
}
|
|
@@ -190,7 +216,7 @@ longs2bytes(const uint32_t *in, long inlen, char *out, int padding)
|
|
|
190
216
|
|
|
191
217
|
outlen = inlen * 4;
|
|
192
218
|
|
|
193
|
-
/*
|
|
219
|
+
/* PKCS#7-style unpadding (4-byte or 8-byte; pad values 1-8). */
|
|
194
220
|
if (padding) {
|
|
195
221
|
pad = s[outlen - 1];
|
|
196
222
|
outlen -= pad;
|
|
@@ -266,13 +292,47 @@ parse_rounds(VALUE obj)
|
|
|
266
292
|
return NUM2UINT(obj);
|
|
267
293
|
}
|
|
268
294
|
|
|
295
|
+
/* 0 = none, 4 = :pkcs7_4_min8 (default), 8 = :pkcs7_8. */
|
|
296
|
+
static int
|
|
297
|
+
parse_padding(VALUE obj)
|
|
298
|
+
{
|
|
299
|
+
ID id;
|
|
300
|
+
|
|
301
|
+
if (obj == Qfalse) {
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
if (obj == Qtrue) {
|
|
305
|
+
return 4;
|
|
306
|
+
}
|
|
307
|
+
if (RB_TYPE_P(obj, T_STRING)) {
|
|
308
|
+
obj = rb_str_intern(obj);
|
|
309
|
+
}
|
|
310
|
+
if (!SYMBOL_P(obj)) {
|
|
311
|
+
rb_raise(rb_eTypeError,
|
|
312
|
+
"padding must be true, false, :none, :pkcs7_4_min8, or :pkcs7_8");
|
|
313
|
+
}
|
|
314
|
+
id = SYM2ID(obj);
|
|
315
|
+
if (id == id_none) {
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
if (id == id_pkcs7_4_min8) {
|
|
319
|
+
return 4;
|
|
320
|
+
}
|
|
321
|
+
if (id == id_pkcs7_8) {
|
|
322
|
+
return 8;
|
|
323
|
+
}
|
|
324
|
+
rb_raise(rb_eArgError,
|
|
325
|
+
"unknown padding %+"PRIsVALUE" (expected :none, :pkcs7_4_min8, or :pkcs7_8)",
|
|
326
|
+
obj);
|
|
327
|
+
}
|
|
328
|
+
|
|
269
329
|
static void
|
|
270
330
|
parse_opts(VALUE opts, int *padding, unsigned int *rounds)
|
|
271
331
|
{
|
|
272
332
|
ID kwids[2];
|
|
273
333
|
VALUE kwvals[2];
|
|
274
334
|
|
|
275
|
-
*padding =
|
|
335
|
+
*padding = 4;
|
|
276
336
|
*rounds = 0;
|
|
277
337
|
if (NIL_P(opts)) {
|
|
278
338
|
return;
|
|
@@ -283,7 +343,7 @@ parse_opts(VALUE opts, int *padding, unsigned int *rounds)
|
|
|
283
343
|
rb_get_kwargs(opts, kwids, 0, 2, kwvals);
|
|
284
344
|
|
|
285
345
|
if (kwvals[0] != Qundef) {
|
|
286
|
-
*padding =
|
|
346
|
+
*padding = parse_padding(kwvals[0]);
|
|
287
347
|
}
|
|
288
348
|
if (kwvals[1] != Qundef) {
|
|
289
349
|
*rounds = parse_rounds(kwvals[1]);
|
|
@@ -315,7 +375,18 @@ encrypt_impl(VALUE data, const char key[16], int padding, unsigned int rounds)
|
|
|
315
375
|
"Data length must be a multiple of 4 bytes and must not be less than 8 bytes");
|
|
316
376
|
}
|
|
317
377
|
|
|
318
|
-
|
|
378
|
+
if (padding == 8) {
|
|
379
|
+
if (data_len > LONG_MAX - 8) {
|
|
380
|
+
rb_raise(rb_eRangeError, "data too large");
|
|
381
|
+
}
|
|
382
|
+
alen = ((data_len & ~7L) + 8) >> 2;
|
|
383
|
+
}
|
|
384
|
+
else if (padding == 4) {
|
|
385
|
+
alen = data_len < 4 ? 2 : (data_len >> 2) + 1;
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
alen = data_len >> 2;
|
|
389
|
+
}
|
|
319
390
|
if (alen > INT_MAX || alen > LONG_MAX / 4) {
|
|
320
391
|
rb_raise(rb_eRangeError, "data too large");
|
|
321
392
|
}
|
|
@@ -501,7 +572,7 @@ xxtea_alloc(VALUE klass)
|
|
|
501
572
|
xxtea_cipher_t *cipher;
|
|
502
573
|
VALUE obj = TypedData_Make_Struct(klass, xxtea_cipher_t, &xxtea_cipher_type, cipher);
|
|
503
574
|
memset(cipher, 0, sizeof(*cipher));
|
|
504
|
-
cipher->padding =
|
|
575
|
+
cipher->padding = 4;
|
|
505
576
|
cipher->rounds = 0;
|
|
506
577
|
return obj;
|
|
507
578
|
}
|
|
@@ -556,7 +627,9 @@ xxtea_inspect(VALUE self)
|
|
|
556
627
|
xxtea_cipher_t *cipher = xxtea_get(self);
|
|
557
628
|
return rb_sprintf("#<%s:%p padding=%s rounds=%u>",
|
|
558
629
|
rb_obj_classname(self), (void *)self,
|
|
559
|
-
cipher->padding
|
|
630
|
+
cipher->padding == 0 ? "false" :
|
|
631
|
+
cipher->padding == 4 ? "pkcs7_4_min8" : "pkcs7_8",
|
|
632
|
+
cipher->rounds);
|
|
560
633
|
}
|
|
561
634
|
|
|
562
635
|
void
|
|
@@ -566,6 +639,9 @@ Init_xxtea(void)
|
|
|
566
639
|
|
|
567
640
|
id_padding = rb_intern("padding");
|
|
568
641
|
id_rounds = rb_intern("rounds");
|
|
642
|
+
id_none = rb_intern("none");
|
|
643
|
+
id_pkcs7_4_min8 = rb_intern("pkcs7_4_min8");
|
|
644
|
+
id_pkcs7_8 = rb_intern("pkcs7_8");
|
|
569
645
|
|
|
570
646
|
cXXTEA = rb_define_class("XXTEA", rb_cObject);
|
|
571
647
|
rb_define_alloc_func(cXXTEA, xxtea_alloc);
|
|
@@ -581,4 +657,7 @@ Init_xxtea(void)
|
|
|
581
657
|
rb_define_method(cXXTEA, "encrypt_hex", xxtea_encrypt_hex, 1);
|
|
582
658
|
rb_define_method(cXXTEA, "decrypt_hex", xxtea_decrypt_hex, 1);
|
|
583
659
|
rb_define_method(cXXTEA, "inspect", xxtea_inspect, 0);
|
|
660
|
+
|
|
661
|
+
rb_define_const(cXXTEA, "PKCS7_4_MIN8", ID2SYM(id_pkcs7_4_min8));
|
|
662
|
+
rb_define_const(cXXTEA, "PKCS7_8", ID2SYM(id_pkcs7_8));
|
|
584
663
|
}
|
data/lib/xxtea/version.rb
CHANGED