xxtea 1.1.0 → 1.2.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 +8 -0
- data/README.md +17 -4
- data/ext/xxtea/xxtea.c +101 -7
- data/lib/xxtea/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89a73616644886a2d6c15b0567b6a123ba8029a410affd9aa7c76213358ba5b9
|
|
4
|
+
data.tar.gz: 181fff72b4214e44978ccd5ad26209d1362b0dcccaa949944bcf8e29c14b3577
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5dc86b066d959b3d9bbb013fa9f3ba7cd772608cc5dcb434889185c8a0fccb4ab0b01f210fa3c628745d6efd9e760a29e175d469febb2d4618d61e62e13b0ad8
|
|
7
|
+
data.tar.gz: 804d0e50a19dcfcfbfb087376612e4d7f92309b9192463e324b92e133cf291466f0812127b6dabe22927bb48bb160032290beea8e596f3be4b66f25374c2c42e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
- Add the `:length_word_suffix` and `:length_word_prefix` padding schemes, matching all five padding modes of [Python xxtea](https://github.com/ifduyue/xxtea) 6.2.0.
|
|
6
|
+
- `:length_word_suffix`: zero-pads to a 4-byte boundary, then appends one little-endian `uint32` with the original length (Cocos Creator JSC files).
|
|
7
|
+
- `:length_word_prefix`: prepends one little-endian `uint32` with the original length, then zero-pads the data to a 4-byte boundary.
|
|
8
|
+
- Both support empty input (padded to XXTEA's 2-word minimum) and raise `ArgumentError` on decrypt when the zero padding or length word is inconsistent.
|
|
9
|
+
- Add `XXTEA::LENGTH_WORD_SUFFIX` and `XXTEA::LENGTH_WORD_PREFIX` constant aliases.
|
|
10
|
+
|
|
3
11
|
## 1.1.0
|
|
4
12
|
|
|
5
13
|
- Add named padding schemes: `:pkcs7_4_min8` (default, also `true`), `:pkcs7_8`, and `:none` (also `false`).
|
data/README.md
CHANGED
|
@@ -91,6 +91,7 @@ c = XXTEA.new(key) # rounds=0, padding=:pkcs7_4_min8
|
|
|
91
91
|
c = XXTEA.new(key, rounds: 64) # override rounds
|
|
92
92
|
c = XXTEA.new(key, padding: false) # disable padding
|
|
93
93
|
c = XXTEA.new(key, padding: :pkcs7_8) # 8-byte PKCS#7
|
|
94
|
+
c = XXTEA.new(key, padding: :length_word_suffix) # length word + zero padding
|
|
94
95
|
c = XXTEA.new(key, padding: false, rounds: 42)
|
|
95
96
|
```
|
|
96
97
|
|
|
@@ -110,9 +111,12 @@ s == XXTEA.decrypt([hexenc].pack("H*"), key) # => true
|
|
|
110
111
|
| --- | --- |
|
|
111
112
|
| `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
113
|
| `:pkcs7_8` | Standard **8-byte** PKCS#7, compatible with Python xxteang |
|
|
114
|
+
| `:length_word_suffix` | Zero-pad to a 4-byte boundary, then append one little-endian `uint32` with the original length. Cocos Creator JSC files using this layout can be decrypted. Compatible with Python xxtea 6.2.0 |
|
|
115
|
+
| `:length_word_prefix` | Prepend one little-endian `uint32` with the original length, then zero-pad the data to a 4-byte boundary. Compatible with Python xxtea 6.2.0 |
|
|
113
116
|
| `false` or `:none` | No padding (raw XXTEA) |
|
|
114
117
|
|
|
115
|
-
`XXTEA::PKCS7_4_MIN8
|
|
118
|
+
`XXTEA::PKCS7_4_MIN8`, `XXTEA::PKCS7_8`, `XXTEA::LENGTH_WORD_PREFIX`, and
|
|
119
|
+
`XXTEA::LENGTH_WORD_SUFFIX` are aliases for the symbols.
|
|
116
120
|
|
|
117
121
|
The default `:pkcs7_4_min8` scheme uses pad byte value `4 - (data.bytesize & 3)`
|
|
118
122
|
(range 1–4), plus an extra 4 bytes when the input is shorter than 4 bytes
|
|
@@ -123,12 +127,21 @@ byte, encrypting an 8-byte input produces a 12-byte ciphertext.
|
|
|
123
127
|
8-byte PKCS#7 uses pad byte value `8 - (data.bytesize & 7)` (range 1–8).
|
|
124
128
|
Encrypting an 8-byte input produces a 16-byte ciphertext.
|
|
125
129
|
|
|
130
|
+
The length-word schemes store the original byte length in a little-endian
|
|
131
|
+
`uint32` word, so the plaintext length must fit in 32 bits (`RangeError`
|
|
132
|
+
otherwise). The data is zero-padded to a 4-byte boundary around that word:
|
|
133
|
+
the length word is the last word for `:length_word_suffix` and the first
|
|
134
|
+
word for `:length_word_prefix`. Because of XXTEA's 2-word minimum, empty
|
|
135
|
+
input produces an 8-byte ciphertext in either scheme.
|
|
136
|
+
|
|
126
137
|
```ruby
|
|
127
138
|
XXTEA.decrypt_hex(XXTEA.encrypt_hex("", key), key) # => ""
|
|
128
139
|
XXTEA.decrypt_hex(XXTEA.encrypt_hex(" ", key), key) # => " "
|
|
129
140
|
|
|
130
|
-
XXTEA.encrypt("12345678", key).bytesize
|
|
131
|
-
XXTEA.encrypt("12345678", key, padding: :pkcs7_8).bytesize
|
|
141
|
+
XXTEA.encrypt("12345678", key).bytesize # => 12 (:pkcs7_4_min8)
|
|
142
|
+
XXTEA.encrypt("12345678", key, padding: :pkcs7_8).bytesize # => 16 (:pkcs7_8)
|
|
143
|
+
XXTEA.encrypt("12345678", key, padding: :length_word_suffix).bytesize # => 12
|
|
144
|
+
XXTEA.encrypt("12345678", key, padding: :length_word_prefix).bytesize # => 12
|
|
132
145
|
```
|
|
133
146
|
|
|
134
147
|
You can disable padding by setting `padding: false`.
|
|
@@ -209,7 +222,7 @@ XXTEA.new("k" * 16, rounds: 2**32)
|
|
|
209
222
|
|
|
210
223
|
## Compatibility
|
|
211
224
|
|
|
212
|
-
- Compatible with [Python xxtea](https://github.com/ifduyue/xxtea) (
|
|
225
|
+
- Compatible with [Python xxtea](https://github.com/ifduyue/xxtea) 6.2.0: all five padding schemes (`:pkcs7_4_min8`, `:pkcs7_8`, `:length_word_prefix`, `:length_word_suffix`, `:none`), endianness, and rounds.
|
|
213
226
|
- `padding: :pkcs7_8` is compatible with [Python xxteang](https://github.com/ifduyue/xxteang).
|
|
214
227
|
- 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.
|
|
215
228
|
|
data/ext/xxtea/xxtea.c
CHANGED
|
@@ -26,7 +26,9 @@
|
|
|
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
28
|
* padding: :pkcs7_8 uses 8-byte PKCS#7, compatible with Python xxteang
|
|
29
|
-
* (https://github.com/ifduyue/xxteang)
|
|
29
|
+
* (https://github.com/ifduyue/xxteang); :length_word_prefix and
|
|
30
|
+
* :length_word_suffix prepend/append a little-endian uint32 length word
|
|
31
|
+
* with zero padding, matching Python xxtea 6.2.0.
|
|
30
32
|
*/
|
|
31
33
|
|
|
32
34
|
#include "ruby.h"
|
|
@@ -50,6 +52,8 @@ static ID id_rounds;
|
|
|
50
52
|
static ID id_none;
|
|
51
53
|
static ID id_pkcs7_4_min8;
|
|
52
54
|
static ID id_pkcs7_8;
|
|
55
|
+
static ID id_length_word_prefix;
|
|
56
|
+
static ID id_length_word_suffix;
|
|
53
57
|
static VALUE cXXTEA;
|
|
54
58
|
|
|
55
59
|
typedef struct {
|
|
@@ -127,20 +131,47 @@ bytes2longs(const char *in, long inlen, uint32_t *out, int padding)
|
|
|
127
131
|
long i, nwords;
|
|
128
132
|
int pad;
|
|
129
133
|
const unsigned char *s = (const unsigned char *)in;
|
|
134
|
+
uint32_t *dst = out;
|
|
135
|
+
|
|
136
|
+
if (padding == 2) {
|
|
137
|
+
/* uint32 store, not memcpy(&inlen): 64-bit would copy the high half. */
|
|
138
|
+
out[0] = (uint32_t)inlen;
|
|
139
|
+
dst = out + 1;
|
|
140
|
+
}
|
|
130
141
|
|
|
131
142
|
nwords = inlen >> 2;
|
|
132
143
|
for (i = 0; i < nwords; i++) {
|
|
133
144
|
#if XXTEA_LITTLE_ENDIAN
|
|
134
|
-
memcpy(&
|
|
145
|
+
memcpy(&dst[i], s + 4 * i, 4);
|
|
135
146
|
#else
|
|
136
147
|
const unsigned char *p = s + 4 * i;
|
|
137
|
-
|
|
148
|
+
dst[i] = (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
|
138
149
|
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
|
139
150
|
#endif
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
i = nwords << 2;
|
|
143
154
|
|
|
155
|
+
if (padding == 1 || padding == 2) {
|
|
156
|
+
/* XXTEA needs two words, so empty input gets an extra zero word. */
|
|
157
|
+
uint32_t w = 0;
|
|
158
|
+
int shift = 0;
|
|
159
|
+
for (; i < inlen; i++, shift += 8) {
|
|
160
|
+
w |= (uint32_t)s[i] << shift;
|
|
161
|
+
}
|
|
162
|
+
if ((inlen & 3) != 0) {
|
|
163
|
+
dst[nwords] = w;
|
|
164
|
+
nwords++;
|
|
165
|
+
}
|
|
166
|
+
if (nwords < 1) {
|
|
167
|
+
dst[nwords++] = 0;
|
|
168
|
+
}
|
|
169
|
+
if (padding == 1) {
|
|
170
|
+
dst[nwords] = (uint32_t)inlen;
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
144
175
|
if (padding == 8) {
|
|
145
176
|
/*
|
|
146
177
|
* 8-byte PKCS#7 (xxteang): pad = 8 - (len & 7), range 1-8.
|
|
@@ -216,8 +247,41 @@ longs2bytes(const uint32_t *in, long inlen, char *out, int padding)
|
|
|
216
247
|
|
|
217
248
|
outlen = inlen * 4;
|
|
218
249
|
|
|
250
|
+
if (padding == 1 || padding == 2) {
|
|
251
|
+
int prefix = padding == 2;
|
|
252
|
+
long n = outlen - 4;
|
|
253
|
+
long leftover, pad_from, pad_to;
|
|
254
|
+
uint32_t m32;
|
|
255
|
+
if (prefix) {
|
|
256
|
+
m32 = (uint32_t)s[0] | ((uint32_t)s[1] << 8) |
|
|
257
|
+
((uint32_t)s[2] << 16) | ((uint32_t)s[3] << 24);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
m32 = (uint32_t)s[n] | ((uint32_t)s[n + 1] << 8) |
|
|
261
|
+
((uint32_t)s[n + 2] << 16) | ((uint32_t)s[n + 3] << 24);
|
|
262
|
+
}
|
|
263
|
+
if ((size_t)m32 > (size_t)n) {
|
|
264
|
+
return -1;
|
|
265
|
+
}
|
|
266
|
+
leftover = n - (long)m32;
|
|
267
|
+
pad_from = prefix ? 4 + (long)m32 : (long)m32;
|
|
268
|
+
pad_to = prefix ? outlen : n;
|
|
269
|
+
/* leftover 4 is the empty 2-word case; otherwise 0-3 zero-pad bytes. */
|
|
270
|
+
if (leftover > 3 && !(m32 == 0 && leftover == 4)) {
|
|
271
|
+
return -1;
|
|
272
|
+
}
|
|
273
|
+
for (i = pad_from; i < pad_to; i++) {
|
|
274
|
+
if (s[i] != 0) {
|
|
275
|
+
return -1;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (prefix && m32 != 0) {
|
|
279
|
+
memmove(s, s + 4, (size_t)m32);
|
|
280
|
+
}
|
|
281
|
+
outlen = (long)m32;
|
|
282
|
+
}
|
|
219
283
|
/* PKCS#7-style unpadding (4-byte or 8-byte; pad values 1-8). */
|
|
220
|
-
if (padding) {
|
|
284
|
+
else if (padding) {
|
|
221
285
|
pad = s[outlen - 1];
|
|
222
286
|
outlen -= pad;
|
|
223
287
|
|
|
@@ -292,7 +356,8 @@ parse_rounds(VALUE obj)
|
|
|
292
356
|
return NUM2UINT(obj);
|
|
293
357
|
}
|
|
294
358
|
|
|
295
|
-
/* 0 = none,
|
|
359
|
+
/* 0 = none, 1 = :length_word_suffix, 2 = :length_word_prefix,
|
|
360
|
+
* 4 = :pkcs7_4_min8 (default), 8 = :pkcs7_8. */
|
|
296
361
|
static int
|
|
297
362
|
parse_padding(VALUE obj)
|
|
298
363
|
{
|
|
@@ -309,12 +374,19 @@ parse_padding(VALUE obj)
|
|
|
309
374
|
}
|
|
310
375
|
if (!SYMBOL_P(obj)) {
|
|
311
376
|
rb_raise(rb_eTypeError,
|
|
312
|
-
"padding must be true, false, :none, :pkcs7_4_min8,
|
|
377
|
+
"padding must be true, false, :none, :pkcs7_4_min8, "
|
|
378
|
+
":pkcs7_8, :length_word_prefix, or :length_word_suffix");
|
|
313
379
|
}
|
|
314
380
|
id = SYM2ID(obj);
|
|
315
381
|
if (id == id_none) {
|
|
316
382
|
return 0;
|
|
317
383
|
}
|
|
384
|
+
if (id == id_length_word_suffix) {
|
|
385
|
+
return 1;
|
|
386
|
+
}
|
|
387
|
+
if (id == id_length_word_prefix) {
|
|
388
|
+
return 2;
|
|
389
|
+
}
|
|
318
390
|
if (id == id_pkcs7_4_min8) {
|
|
319
391
|
return 4;
|
|
320
392
|
}
|
|
@@ -322,7 +394,8 @@ parse_padding(VALUE obj)
|
|
|
322
394
|
return 8;
|
|
323
395
|
}
|
|
324
396
|
rb_raise(rb_eArgError,
|
|
325
|
-
"unknown padding %+"PRIsVALUE" (expected :none, :pkcs7_4_min8,
|
|
397
|
+
"unknown padding %+"PRIsVALUE" (expected :none, :pkcs7_4_min8, "
|
|
398
|
+
":pkcs7_8, :length_word_prefix, or :length_word_suffix)",
|
|
326
399
|
obj);
|
|
327
400
|
}
|
|
328
401
|
|
|
@@ -384,6 +457,21 @@ encrypt_impl(VALUE data, const char key[16], int padding, unsigned int rounds)
|
|
|
384
457
|
else if (padding == 4) {
|
|
385
458
|
alen = data_len < 4 ? 2 : (data_len >> 2) + 1;
|
|
386
459
|
}
|
|
460
|
+
else if (padding == 1 || padding == 2) {
|
|
461
|
+
/* The length word is a uint32, so the plaintext length must fit. */
|
|
462
|
+
#if LONG_MAX > 2147483647L
|
|
463
|
+
if (data_len > 0xFFFFFFFFL) {
|
|
464
|
+
rb_raise(rb_eRangeError, "data too large");
|
|
465
|
+
}
|
|
466
|
+
#endif
|
|
467
|
+
if (data_len > LONG_MAX - 4) {
|
|
468
|
+
rb_raise(rb_eRangeError, "data too large");
|
|
469
|
+
}
|
|
470
|
+
alen = (data_len >> 2) + ((data_len & 3) != 0 ? 1 : 0) + 1;
|
|
471
|
+
if (alen < 2) {
|
|
472
|
+
alen = 2;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
387
475
|
else {
|
|
388
476
|
alen = data_len >> 2;
|
|
389
477
|
}
|
|
@@ -628,6 +716,8 @@ xxtea_inspect(VALUE self)
|
|
|
628
716
|
return rb_sprintf("#<%s:%p padding=%s rounds=%u>",
|
|
629
717
|
rb_obj_classname(self), (void *)self,
|
|
630
718
|
cipher->padding == 0 ? "false" :
|
|
719
|
+
cipher->padding == 1 ? "length_word_suffix" :
|
|
720
|
+
cipher->padding == 2 ? "length_word_prefix" :
|
|
631
721
|
cipher->padding == 4 ? "pkcs7_4_min8" : "pkcs7_8",
|
|
632
722
|
cipher->rounds);
|
|
633
723
|
}
|
|
@@ -642,6 +732,8 @@ Init_xxtea(void)
|
|
|
642
732
|
id_none = rb_intern("none");
|
|
643
733
|
id_pkcs7_4_min8 = rb_intern("pkcs7_4_min8");
|
|
644
734
|
id_pkcs7_8 = rb_intern("pkcs7_8");
|
|
735
|
+
id_length_word_prefix = rb_intern("length_word_prefix");
|
|
736
|
+
id_length_word_suffix = rb_intern("length_word_suffix");
|
|
645
737
|
|
|
646
738
|
cXXTEA = rb_define_class("XXTEA", rb_cObject);
|
|
647
739
|
rb_define_alloc_func(cXXTEA, xxtea_alloc);
|
|
@@ -660,4 +752,6 @@ Init_xxtea(void)
|
|
|
660
752
|
|
|
661
753
|
rb_define_const(cXXTEA, "PKCS7_4_MIN8", ID2SYM(id_pkcs7_4_min8));
|
|
662
754
|
rb_define_const(cXXTEA, "PKCS7_8", ID2SYM(id_pkcs7_8));
|
|
755
|
+
rb_define_const(cXXTEA, "LENGTH_WORD_PREFIX", ID2SYM(id_length_word_prefix));
|
|
756
|
+
rb_define_const(cXXTEA, "LENGTH_WORD_SUFFIX", ID2SYM(id_length_word_suffix));
|
|
663
757
|
}
|
data/lib/xxtea/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: xxtea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yue Du
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
XXTEA implemented as a Ruby C extension. Ciphertext is compatible with the
|