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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8eca82a1c887086264d8807bf55a1980dc28f35f956c02a5bf66ebef33ab8f6
4
- data.tar.gz: d6707b17d68788035c7d8f2d90210f15408ba3a1d7a0e3c4dc0636d2b519dd10
3
+ metadata.gz: 52dd181dad48d0089d4f84d4b715b0c7231ea004c35928e4ec5c154b5c19a569
4
+ data.tar.gz: b60c3915ae8573e6d234cd6e9315f48ccb893c535d78ed3defd5f61428b4c825
5
5
  SHA512:
6
- metadata.gz: e829728fcc9f4296e74f9bee45568aab867679971c866ca992146a715a62444fea17602bf21322041c82bed745d7ba7a7f10905e7df4ecf194547b41164ac686
7
- data.tar.gz: 3a4224238e8e3d062dfc83671981834e1ab571c2e293bb90095db8ad46de86921c94d87972dcd0f3180ab6c562d9913e1dd1f2f4692dd07815752ffb12b8349e
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
- Ciphertext is compatible with the [Python xxtea](https://github.com/ifduyue/xxtea) package.
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:** This implementation uses a **non-standard** 4-byte block PKCS#7
25
- > padding instead of the conventional 8-byte or 16-byte block. For inputs
26
- > shorter than 4 bytes, a non-standard hack pads an extra 4 bytes (producing
27
- > pad values 5–8) to satisfy XXTEA's 2-word minimum. This means the output is
28
- > **NOT** compatible with other XXTEA implementations. Pass `padding: false`
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) # rounds=0, padding=true
91
- c = XXTEA.new(key, rounds: 64) # override rounds
92
- c = XXTEA.new(key, padding: false) # disable padding
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
- Padding is enabled by default, using a **non-standard 4-byte block PKCS#7**
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
- Because padding always adds at least one byte, encrypting an 8-byte input
112
- produces a 12-byte ciphertext. This is incompatible with other XXTEA
113
- implementations that use a standard block size or skip padding altogether.
114
- Use `padding: false` for raw, unpadded XXTEA.
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) (same padding, endianness, and rounds).
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
- * Ciphertext is compatible with the Python xxtea package
25
+ * Default ciphertext is compatible with the Python xxtea package
26
26
  * (https://github.com/ifduyue/xxtea): little-endian 32-bit words and
27
- * non-standard 4-byte PKCS#7 padding (pad+4 for inputs shorter than 4 bytes).
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
- * Assemble the final partial word (0-3 leftover data bytes plus
139
- * padding) in a local and store it with a single write, so every
140
- * output byte is written exactly once and the caller does not need
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
- i = nwords << 2;
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
- /* 4-byte PKCS#7-style unpadding. */
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 = 1;
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 = RTEST(kwvals[0]);
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
- alen = data_len < 4 ? 2 : (data_len >> 2) + padding;
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 = 1;
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 ? "true" : "false", cipher->rounds);
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class XXTEA
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xxtea
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yue Du