xxtea 0.0.1 → 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
- SHA1:
3
- metadata.gz: 40a67d849e79434013e6648f21ddc02e7e6e93cf
4
- data.tar.gz: 480a724d92d0ec28c64a264e7ababd1daf0a23ad
2
+ SHA256:
3
+ metadata.gz: 52dd181dad48d0089d4f84d4b715b0c7231ea004c35928e4ec5c154b5c19a569
4
+ data.tar.gz: b60c3915ae8573e6d234cd6e9315f48ccb893c535d78ed3defd5f61428b4c825
5
5
  SHA512:
6
- metadata.gz: f9344b00aae36904b61f0facd0900a734c462e036335021dce7494f083f3fe0949a3e9102ba3bfd5dd8cc0094bd7cb22a46995dac76f9a28f8bef8eddf1e92a3
7
- data.tar.gz: 844b8fea4b61f4a71863952af8f0117f0c1eb52da0335ecc1fb8098470cc810619cc65c8c5d0f162dad4ec5ae674c5fd6cc6d28f9961d20331d68cec53025644
6
+ metadata.gz: a49001c4bb5c6e53a495bf3a96ecc61d084bb822a77dfda6370dd432b5f50ae54321ffbec3736cc1773fc68059465cb42aeedb3db2511b54c86f44ba390976f0
7
+ data.tar.gz: a9671360c531507c76ddec41561817312e4f0c961ef1bc6865b6e2ae5ff1ac39eab35d990ca1cab69317992e815e34277e039b60cab7b6b692aa20c550f51731
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
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
+
9
+ ## 1.0.0
10
+
11
+ - Rewrite as a Ruby C extension compatible with Python [xxtea](https://github.com/ifduyue/xxtea) 5.3.3.
12
+ - Keep `XXTEA.encrypt` / `XXTEA.decrypt` (16-byte key, non-standard 4-byte PKCS#7 padding).
13
+ - Add `padding:` and `rounds:` keyword arguments.
14
+ - Add `encrypt_hex` / `decrypt_hex`.
15
+ - Add `XXTEA.new(key, padding: true, rounds: 0)` cipher objects.
16
+ - Validate PKCS#7 padding on decrypt; reject invalid padding with `ArgumentError`.
17
+ - Encode ciphertext as little-endian bytes on all architectures.
18
+ - Require Ruby >= 3.1.
19
+
20
+ ## 0.0.1 — 2015-03-08
21
+
22
+ - Initial release.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014-2026, Yue Du
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # xxtea
2
+
3
+ [![CI](https://github.com/ifduyue/ruby-xxtea/actions/workflows/test.yml/badge.svg)](https://github.com/ifduyue/ruby-xxtea/actions/workflows/test.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/xxtea.svg)](https://rubygems.org/gems/xxtea)
5
+
6
+ [XXTEA](https://en.wikipedia.org/wiki/XXTEA) implemented as a Ruby C extension, licensed under 2-clause BSD.
7
+
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).
10
+
11
+ The XXTEA algorithm takes a 128-bit key and operates on an array of 32-bit
12
+ integers (at least 2 integers), but it doesn't define the conversions between
13
+ bytes and array. Due to this reason, many XXTEA implementations out there are
14
+ not compatible with each other.
15
+
16
+ In this implementation, the conversions between bytes and array are taken care
17
+ of by `longs2bytes` and `bytes2longs`. A non-standard 4-byte block
18
+ [PKCS#7](https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7) padding is
19
+ used to make sure that the input bytes are padded to a multiple of 4-byte (the
20
+ size of a 32-bit integer) and at least 8-byte long (the size of two 32-bit
21
+ integers, which is required by the XXTEA algorithm). As a result of these
22
+ measures, you can encrypt not only texts, but also any binary bytes of any
23
+ length.
24
+
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
+
31
+ ## Installation
32
+
33
+ A C compiler is required. Then:
34
+
35
+ ```
36
+ $ gem install xxtea
37
+ ```
38
+
39
+ Or add to your Gemfile:
40
+
41
+ ```ruby
42
+ gem "xxtea"
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ This gem provides four class methods: `encrypt`, `decrypt`, `encrypt_hex`, and
48
+ `decrypt_hex`, plus an `XXTEA` type for reusable cipher objects.
49
+
50
+ ```ruby
51
+ require "xxtea"
52
+ require "securerandom"
53
+
54
+ key = SecureRandom.random_bytes(16) # Key must be a 16-byte string.
55
+ s = "xxtea is good"
56
+
57
+ enc = XXTEA.encrypt(s, key)
58
+ dec = XXTEA.decrypt(enc, key)
59
+ s == dec # => true
60
+
61
+ hexenc = XXTEA.encrypt_hex(s, key)
62
+ s == XXTEA.decrypt_hex(hexenc, key) # => true
63
+
64
+ enc.unpack1("H*") == hexenc # => true
65
+ ```
66
+
67
+ ## XXTEA Type
68
+
69
+ The `XXTEA` type holds a 16-byte key, rounds, and padding setting, so you can
70
+ encrypt and decrypt multiple times without passing them each call.
71
+
72
+ ```ruby
73
+ cipher = XXTEA.new(key, padding: false, rounds: 128)
74
+ cipher
75
+ # => #<XXTEA:0x... padding=false rounds=128>
76
+
77
+ enc = cipher.encrypt("12345678")
78
+ cipher.decrypt(enc) # => "12345678"
79
+
80
+ hexenc = cipher.encrypt_hex("12345678")
81
+ cipher.decrypt_hex(hexenc) # => "12345678"
82
+ ```
83
+
84
+ `rounds` defaults to `0` (auto), `padding` defaults to `true`.
85
+ `rounds: 0` means `6 + 52 / n`, where n is the number of 32-bit words in the data.
86
+ They are stored on the object and used by every `encrypt`, `decrypt`,
87
+ `encrypt_hex`, and `decrypt_hex` call:
88
+
89
+ ```ruby
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
94
+ c = XXTEA.new(key, padding: false, rounds: 42)
95
+ ```
96
+
97
+ `encrypt_hex` and `decrypt_hex` operate on ciphertext in a hexadecimal
98
+ representation. They are exactly equivalent to:
99
+
100
+ ```ruby
101
+ hexenc = XXTEA.encrypt(s, key).unpack1("H*")
102
+ s == XXTEA.decrypt([hexenc].pack("H*"), key) # => true
103
+ ```
104
+
105
+ ## Padding
106
+
107
+ `padding` accepts a scheme name, so more paddings can be added later:
108
+
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.
125
+
126
+ ```ruby
127
+ XXTEA.decrypt_hex(XXTEA.encrypt_hex("", key), key) # => ""
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)
132
+ ```
133
+
134
+ You can disable padding by setting `padding: false`.
135
+ In this case data will not be padded, so data length must be a multiple of 4
136
+ bytes and must not be less than 8 bytes. Otherwise `ArgumentError` will be
137
+ raised:
138
+
139
+ ```ruby
140
+ XXTEA.encrypt_hex("", key, padding: false)
141
+ # ArgumentError: Data length must be a multiple of 4 bytes and must not be less than 8 bytes
142
+
143
+ XXTEA.encrypt_hex("xxtea is good", key, padding: false)
144
+ # ArgumentError: Data length must be a multiple of 4 bytes and must not be less than 8 bytes
145
+
146
+ XXTEA.decrypt_hex(XXTEA.encrypt_hex("12345678", key, padding: false), key, padding: false)
147
+ # => "12345678"
148
+ ```
149
+
150
+ ## Rounds
151
+
152
+ By default xxtea manipulates the input data for `6 + 52 / n` rounds,
153
+ where n denotes how many 32-bit integers the input data can fit in.
154
+ We can change this by setting the `rounds` parameter.
155
+
156
+ Do note that the more rounds it is, the more time will be consumed.
157
+ `rounds` must fit in a 32-bit unsigned integer; values exceeding
158
+ `2**32 - 1` raise `RangeError`.
159
+
160
+ ```ruby
161
+ data = "0123456789"
162
+ key = "abcdefghijklmnop"
163
+ XXTEA.encrypt_hex(data, key)
164
+ # => "5b80b08a5d1923e4cd992dd5"
165
+ 6 + 52 / ((data.bytesize + 3) / 4) # 23
166
+ XXTEA.encrypt_hex(data, key, rounds: 23)
167
+ # => "5b80b08a5d1923e4cd992dd5"
168
+ XXTEA.encrypt_hex(data, key, rounds: 1024)
169
+ # => "1577bbf28c43ced93bd50720"
170
+ ```
171
+
172
+ ## Catching Exceptions
173
+
174
+ When calling these methods, an `ArgumentError`, `TypeError`, or `RangeError`
175
+ may be raised.
176
+
177
+ ```ruby
178
+ begin
179
+ XXTEA.decrypt("", key: "")
180
+ rescue => e
181
+ puts "#{e.class} : #{e.message}"
182
+ end
183
+ # ArgumentError : Need a 16-byte key.
184
+
185
+ XXTEA.decrypt("", " " * 16)
186
+ # ArgumentError : Data length must be a multiple of 4 bytes and must not be less than 8 bytes
187
+
188
+ XXTEA.decrypt(" " * 8, " " * 16)
189
+ # ArgumentError : Invalid data, illegal padding. Could be using a wrong key.
190
+
191
+ XXTEA.decrypt_hex(" " * 8, " " * 16)
192
+ # ArgumentError : Non-hexadecimal digit found
193
+
194
+ XXTEA.decrypt_hex("abc", " " * 16)
195
+ # ArgumentError : Odd-length string
196
+
197
+ XXTEA.decrypt_hex("abcd", " " * 16)
198
+ # ArgumentError : Data length must be a multiple of 4 bytes and must not be less than 8 bytes
199
+
200
+ XXTEA.encrypt("x", "k" * 16, rounds: 2**32)
201
+ # RangeError : rounds value too large
202
+
203
+ XXTEA.new("short")
204
+ # ArgumentError : Need a 16-byte key.
205
+
206
+ XXTEA.new("k" * 16, rounds: 2**32)
207
+ # RangeError : rounds value too large
208
+ ```
209
+
210
+ ## Compatibility
211
+
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).
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.
215
+
216
+ ## Releasing
217
+
218
+ Push a `v*` tag (for example `v1.0.0`). [`.github/workflows/build.yml`](.github/workflows/build.yml) builds the gem, publishes it to RubyGems.org via [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/), and creates a GitHub Release.
219
+
220
+ The trusted publisher on RubyGems.org must match:
221
+
222
+ - Repository owner: `ifduyue`
223
+ - Repository name: `ruby-xxtea`
224
+ - Workflow filename: `build.yml`
225
+ - Environment: `release`
226
+
227
+ ## License
228
+
229
+ BSD-2-Clause. See [LICENSE](LICENSE).
230
+
data/ext/xxtea/extconf.rb CHANGED
@@ -1,5 +1,5 @@
1
- require 'mkmf'
1
+ # frozen_string_literal: true
2
2
 
3
- $CFLAGS += ' -g -O3 -Wall '
3
+ require "mkmf"
4
4
 
5
- create_makefile('xxtea/xxtea')
5
+ create_makefile("xxtea/xxtea")
data/ext/xxtea/xxtea.c CHANGED
@@ -1,19 +1,89 @@
1
-
1
+ /*
2
+ * Copyright (c) 2014-2026, Yue Du
3
+ * All rights reserved.
4
+ *
5
+ * Redistribution and use in source and binary forms, with or without modification,
6
+ * are permitted provided that the following conditions are met:
7
+ *
8
+ * * Redistributions of source code must retain the above copyright notice,
9
+ * this list of conditions and the following disclaimer.
10
+ * * Redistributions in binary form must reproduce the above copyright notice,
11
+ * this list of conditions and the following disclaimer in the documentation
12
+ * and/or other materials provided with the distribution.
13
+ *
14
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
+ *
25
+ * Default ciphertext is compatible with the Python xxtea package
26
+ * (https://github.com/ifduyue/xxtea): little-endian 32-bit words and
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).
30
+ */
31
+
32
+ #include "ruby.h"
33
+ #include "ruby/encoding.h"
34
+
35
+ #include <limits.h>
2
36
  #include <stdint.h>
3
- #include <stdlib.h>
4
- #include <ruby.h>
5
-
6
-
7
- #define DELTA 0x9e3779b9
8
- #define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
37
+ #include <string.h>
38
+
39
+ #define DELTA 0x9e3779b9U
40
+ #define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
41
+
42
+ #if defined(WORDS_BIGENDIAN)
43
+ # define XXTEA_LITTLE_ENDIAN 0
44
+ #else
45
+ # define XXTEA_LITTLE_ENDIAN 1
46
+ #endif
47
+
48
+ static ID id_padding;
49
+ static ID id_rounds;
50
+ static ID id_none;
51
+ static ID id_pkcs7_4_min8;
52
+ static ID id_pkcs7_8;
53
+ static VALUE cXXTEA;
54
+
55
+ typedef struct {
56
+ char key[16];
57
+ unsigned int rounds;
58
+ int padding;
59
+ } xxtea_cipher_t;
60
+
61
+ static const rb_data_type_t xxtea_cipher_type = {
62
+ .wrap_struct_name = "XXTEA",
63
+ .function = {
64
+ .dmark = NULL,
65
+ .dfree = RUBY_DEFAULT_FREE,
66
+ .dsize = NULL,
67
+ },
68
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
69
+ };
70
+
71
+ static inline xxtea_cipher_t *
72
+ xxtea_get(VALUE obj)
73
+ {
74
+ xxtea_cipher_t *cipher;
75
+ TypedData_Get_Struct(obj, xxtea_cipher_t, &xxtea_cipher_type, cipher);
76
+ return cipher;
77
+ }
9
78
 
10
- static void btea(uint32_t *v, int n, uint32_t const key[4])
79
+ static void
80
+ btea(uint32_t *v, int n, uint32_t const key[4], unsigned int rounds)
11
81
  {
12
82
  uint32_t y, z, sum;
13
- unsigned p, rounds, e;
83
+ unsigned p, e;
14
84
 
15
85
  if (n > 1) { /* Coding Part */
16
- rounds = 6 + 52 / n;
86
+ rounds = rounds == 0 ? (unsigned)(6 + 52 / n) : rounds;
17
87
  sum = 0;
18
88
  z = v[n - 1];
19
89
 
@@ -21,26 +91,25 @@ static void btea(uint32_t *v, int n, uint32_t const key[4])
21
91
  sum += DELTA;
22
92
  e = (sum >> 2) & 3;
23
93
 
24
- for (p = 0; p < n - 1; p++) {
94
+ for (p = 0; p < (unsigned)(n - 1); p++) {
25
95
  y = v[p + 1];
26
96
  z = v[p] += MX;
27
97
  }
28
98
 
29
99
  y = v[0];
30
100
  z = v[n - 1] += MX;
31
- }
32
- while (--rounds);
101
+ } while (--rounds);
33
102
  }
34
103
  else if (n < -1) { /* Decoding Part */
35
104
  n = -n;
36
- rounds = 6 + 52 / n;
37
- sum = rounds * DELTA;
105
+ rounds = rounds == 0 ? (unsigned)(6 + 52 / n) : rounds;
106
+ sum = (uint32_t)(rounds * DELTA);
38
107
  y = v[0];
39
108
 
40
109
  do {
41
110
  e = (sum >> 2) & 3;
42
111
 
43
- for (p = n - 1; p > 0; p--) {
112
+ for (p = (unsigned)(n - 1); p > 0; p--) {
44
113
  z = v[p - 1];
45
114
  y = v[p] -= MX;
46
115
  }
@@ -48,182 +117,547 @@ static void btea(uint32_t *v, int n, uint32_t const key[4])
48
117
  z = v[n - 1];
49
118
  y = v[0] -= MX;
50
119
  sum -= DELTA;
51
- }
52
- while (--rounds);
120
+ } while (--rounds);
53
121
  }
54
122
  }
55
123
 
56
- static int bytes2longs(const char *in, int inlen, uint32_t *out, int padding)
124
+ static void
125
+ bytes2longs(const char *in, long inlen, uint32_t *out, int padding)
57
126
  {
58
- int i, pad;
59
- const unsigned char *s;
60
-
61
- s = (const unsigned char *)in;
62
-
63
- /* (i & 3) << 3 -> [0, 8, 16, 24] */
64
- for (i = 0; i < inlen; i++) {
65
- out[i >> 2] |= s[i] << ((i & 3) << 3);
127
+ long i, nwords;
128
+ int pad;
129
+ const unsigned char *s = (const unsigned char *)in;
130
+
131
+ nwords = inlen >> 2;
132
+ for (i = 0; i < nwords; i++) {
133
+ #if XXTEA_LITTLE_ENDIAN
134
+ memcpy(&out[i], s + 4 * i, 4);
135
+ #else
136
+ const unsigned char *p = s + 4 * i;
137
+ out[i] = (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
138
+ ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
139
+ #endif
66
140
  }
67
141
 
68
- /* PKCS#7 padding */
69
- if (padding) {
70
- pad = 4 - (inlen & 3);
71
- /* make sure lenght of out >= 2 */
72
- pad = (inlen < 4) ? pad + 4 : pad;
73
- for (i = inlen; i < inlen + pad; i++) {
74
- out[i >> 2] |= pad << ((i & 3) << 3);
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;
75
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;
76
165
  }
77
166
 
78
- /* Divided by 4, and then rounded up (ceil) to an integer.
79
- * Which is the number of how many longs we've got.
167
+ /*
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.
80
171
  */
81
- return ((i - 1) >> 2) + 1;
172
+ if (padding == 4 || (inlen & 3) != 0) {
173
+ uint32_t w = 0;
174
+ int r = (int)(inlen & 3);
175
+ int shift = 0;
176
+ for (; i < inlen; i++, shift += 8) {
177
+ w |= (uint32_t)s[i] << shift;
178
+ }
179
+ if (padding == 4) {
180
+ pad = 4 - r;
181
+ if (inlen < 4) {
182
+ pad += 4;
183
+ }
184
+ w |= (uint32_t)pad * 0x01010101u & (~0u << (8 * r));
185
+ if (inlen < 4) {
186
+ out[nwords + 1] = (uint32_t)pad * 0x01010101u;
187
+ }
188
+ }
189
+ out[nwords] = w;
190
+ }
82
191
  }
83
192
 
84
- static int longs2bytes(uint32_t *in, int inlen, char *out, int padding)
193
+ static long
194
+ longs2bytes(const uint32_t *in, long inlen, char *out, int padding)
85
195
  {
86
- int i, pad;
87
- unsigned char *s;
88
-
89
- s = (unsigned char *)out;
90
-
196
+ long i, outlen;
197
+ int pad;
198
+ unsigned char *s = (unsigned char *)out;
199
+
200
+ #if XXTEA_LITTLE_ENDIAN
201
+ /* Callers always pass the same buffer; words are already LE bytes. */
202
+ (void)in;
203
+ #else
204
+ /*
205
+ * Big endian: write each word as little-endian bytes. Snapshot the
206
+ * whole word first because in and out alias.
207
+ */
91
208
  for (i = 0; i < inlen; i++) {
92
- s[4 * i] = in[i] & 0xFF;
93
- s[4 * i + 1] = (in[i] >> 8) & 0xFF;
94
- s[4 * i + 2] = (in[i] >> 16) & 0xFF;
95
- s[4 * i + 3] = (in[i] >> 24) & 0xFF;
209
+ uint32_t word = in[i];
210
+ s[4 * i] = (unsigned char)(word & 0xFF);
211
+ s[4 * i + 1] = (unsigned char)((word >> 8) & 0xFF);
212
+ s[4 * i + 2] = (unsigned char)((word >> 16) & 0xFF);
213
+ s[4 * i + 3] = (unsigned char)((word >> 24) & 0xFF);
96
214
  }
215
+ #endif
97
216
 
98
- i *= 4;
217
+ outlen = inlen * 4;
99
218
 
100
- /* PKCS#7 unpadding */
219
+ /* PKCS#7-style unpadding (4-byte or 8-byte; pad values 1-8). */
101
220
  if (padding) {
102
- pad = s[i - 1];
103
- i -= pad;
221
+ pad = s[outlen - 1];
222
+ outlen -= pad;
223
+
224
+ if (pad < 1 || pad > 8) {
225
+ return -1;
226
+ }
227
+
228
+ if (outlen < 0) {
229
+ return -2;
230
+ }
231
+
232
+ for (i = outlen; i < inlen * 4; i++) {
233
+ if (s[i] != pad) {
234
+ return -3;
235
+ }
236
+ }
104
237
  }
105
238
 
106
- s[i] = '\0';
239
+ s[outlen] = '\0';
240
+ return outlen;
241
+ }
242
+
243
+ typedef struct {
244
+ const char *data;
245
+ long data_len;
246
+ char key[16];
247
+ uint32_t *buf;
248
+ int n;
249
+ int padding;
250
+ unsigned int rounds;
251
+ long rc;
252
+ } crypt_job;
253
+
254
+ static void *
255
+ encrypt_nogvl(void *ptr)
256
+ {
257
+ crypt_job *j = (crypt_job *)ptr;
258
+ uint32_t k[4];
259
+
260
+ bytes2longs(j->data, j->data_len, j->buf, j->padding);
261
+ bytes2longs(j->key, 16, k, 0);
262
+ btea(j->buf, j->n, k, j->rounds);
263
+ #if !XXTEA_LITTLE_ENDIAN
264
+ longs2bytes(j->buf, j->n, (char *)j->buf, 0);
265
+ #endif
266
+ return NULL;
267
+ }
107
268
 
108
- /* How many bytes we've got */
109
- return i;
269
+ static void *
270
+ decrypt_nogvl(void *ptr)
271
+ {
272
+ crypt_job *j = (crypt_job *)ptr;
273
+ uint32_t k[4];
274
+
275
+ bytes2longs(j->data, j->data_len, j->buf, 0);
276
+ bytes2longs(j->key, 16, k, 0);
277
+ btea(j->buf, -j->n, k, j->rounds);
278
+ j->rc = longs2bytes(j->buf, j->n, (char *)j->buf, j->padding);
279
+ return NULL;
110
280
  }
111
281
 
112
- VALUE xxtea_encrypt(VALUE mod, VALUE data, VALUE key)
282
+ static unsigned int
283
+ parse_rounds(VALUE obj)
113
284
  {
114
- int alen, dlen, klen;
115
- uint32_t *d, k[4];
116
- VALUE retval;
117
- char *retbuf;
285
+ if (!RB_INTEGER_TYPE_P(obj)) {
286
+ rb_raise(rb_eTypeError, "rounds must be an Integer");
287
+ }
288
+ if (RTEST(rb_funcall(obj, rb_intern("<"), 1, INT2FIX(0))) ||
289
+ RTEST(rb_funcall(obj, rb_intern(">"), 1, UINT2NUM(UINT_MAX)))) {
290
+ rb_raise(rb_eRangeError, "rounds value too large");
291
+ }
292
+ return NUM2UINT(obj);
293
+ }
118
294
 
119
- d = NULL;
120
- k[0] = k[1] = k[2] = k[3] = 0;
295
+ /* 0 = none, 4 = :pkcs7_4_min8 (default), 8 = :pkcs7_8. */
296
+ static int
297
+ parse_padding(VALUE obj)
298
+ {
299
+ ID id;
121
300
 
122
- Check_Type(data, T_STRING);
123
- Check_Type(key, T_STRING);
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
+ }
124
328
 
125
- dlen = RSTRING_LEN(data);
126
- klen = RSTRING_LEN(key);
329
+ static void
330
+ parse_opts(VALUE opts, int *padding, unsigned int *rounds)
331
+ {
332
+ ID kwids[2];
333
+ VALUE kwvals[2];
127
334
 
128
- if (klen != 16) {
129
- rb_raise(rb_eArgError, "Need a 16-byte key.");
130
- return Qnil;
335
+ *padding = 4;
336
+ *rounds = 0;
337
+ if (NIL_P(opts)) {
338
+ return;
131
339
  }
132
340
 
133
- alen = dlen < 4 ? 2 : (dlen >> 2) + 1;
134
- d = (uint32_t *)calloc(alen, sizeof(uint32_t));
341
+ kwids[0] = id_padding;
342
+ kwids[1] = id_rounds;
343
+ rb_get_kwargs(opts, kwids, 0, 2, kwvals);
344
+
345
+ if (kwvals[0] != Qundef) {
346
+ *padding = parse_padding(kwvals[0]);
347
+ }
348
+ if (kwvals[1] != Qundef) {
349
+ *rounds = parse_rounds(kwvals[1]);
350
+ }
351
+ }
135
352
 
136
- if (!d) {
137
- rb_raise(rb_eNoMemError, "calloc failed.");
138
- return Qnil;
353
+ static void
354
+ require_key(VALUE key, char out[16])
355
+ {
356
+ StringValue(key);
357
+ if (RSTRING_LEN(key) != 16) {
358
+ rb_raise(rb_eArgError, "Need a 16-byte key.");
139
359
  }
360
+ memcpy(out, RSTRING_PTR(key), 16);
361
+ }
362
+
363
+ static VALUE
364
+ encrypt_impl(VALUE data, const char key[16], int padding, unsigned int rounds)
365
+ {
366
+ crypt_job job;
367
+ VALUE retval;
368
+ long data_len, alen;
140
369
 
141
- bytes2longs(StringValuePtr(data), dlen, d, 1);
142
- bytes2longs(StringValuePtr(key), klen, k, 0);
143
- btea(d, alen, k);
370
+ StringValue(data);
371
+ data_len = RSTRING_LEN(data);
144
372
 
145
- retval = rb_str_new(NULL, (alen << 2));
373
+ if (!padding && (data_len < 8 || (data_len & 3) != 0)) {
374
+ rb_raise(rb_eArgError,
375
+ "Data length must be a multiple of 4 bytes and must not be less than 8 bytes");
376
+ }
146
377
 
147
- if (!retval) {
148
- free(d);
149
- return Qnil;
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;
150
386
  }
387
+ else {
388
+ alen = data_len >> 2;
389
+ }
390
+ if (alen > INT_MAX || alen > LONG_MAX / 4) {
391
+ rb_raise(rb_eRangeError, "data too large");
392
+ }
393
+
394
+ retval = rb_str_new(NULL, alen << 2);
395
+ rb_enc_associate(retval, rb_ascii8bit_encoding());
151
396
 
152
- retbuf = RSTRING_PTR(retval);
153
- longs2bytes(d, alen, retbuf, 0);
397
+ memset(&job, 0, sizeof(job));
398
+ job.data = RSTRING_PTR(data);
399
+ job.data_len = data_len;
400
+ memcpy(job.key, key, 16);
401
+ job.buf = (uint32_t *)RSTRING_PTR(retval);
402
+ job.n = (int)alen;
403
+ job.padding = padding;
404
+ job.rounds = rounds;
154
405
 
406
+ encrypt_nogvl(&job);
407
+ RB_GC_GUARD(data);
155
408
  return retval;
156
409
  }
157
410
 
158
- VALUE xxtea_decrypt(VALUE mod, VALUE data, VALUE key)
411
+ static VALUE
412
+ decrypt_impl(VALUE data, const char key[16], int padding, unsigned int rounds)
159
413
  {
160
- int alen, dlen, klen, rc;
161
- uint32_t *d, k[4];
414
+ crypt_job job;
162
415
  VALUE retval;
163
- char *retbuf;
416
+ long data_len, alen;
164
417
 
165
- d = NULL;
166
- k[0] = k[1] = k[2] = k[3] = 0;
418
+ StringValue(data);
419
+ data_len = RSTRING_LEN(data);
167
420
 
168
- Check_Type(data, T_STRING);
169
- Check_Type(key, T_STRING);
170
-
171
- dlen = RSTRING_LEN(data);
172
- klen = RSTRING_LEN(key);
173
-
174
- if (klen != 16) {
175
- rb_raise(rb_eArgError, "Need a 16-byte key.");
176
- return Qnil;
421
+ if (data_len & 3 || data_len < 8) {
422
+ rb_raise(rb_eArgError,
423
+ "Data length must be a multiple of 4 bytes and must not be less than 8 bytes");
177
424
  }
178
425
 
179
- /* not divided by 4, or length < 8 */
180
- if (dlen & 3 || dlen < 8) {
181
- rb_raise(rb_eArgError, "Invalid data.");
182
- return Qnil;
426
+ alen = data_len / 4;
427
+ if (alen > INT_MAX || alen > LONG_MAX / 4) {
428
+ rb_raise(rb_eRangeError, "data too large");
183
429
  }
184
430
 
185
- retval = rb_str_new(NULL, dlen);
431
+ retval = rb_str_new(NULL, data_len);
432
+ rb_enc_associate(retval, rb_ascii8bit_encoding());
433
+
434
+ memset(&job, 0, sizeof(job));
435
+ job.data = RSTRING_PTR(data);
436
+ job.data_len = data_len;
437
+ memcpy(job.key, key, 16);
438
+ job.buf = (uint32_t *)RSTRING_PTR(retval);
439
+ job.n = (int)alen;
440
+ job.padding = padding;
441
+ job.rounds = rounds;
186
442
 
187
- if (!retval) {
188
- free(d);
189
- return Qnil;
443
+ decrypt_nogvl(&job);
444
+ RB_GC_GUARD(data);
445
+
446
+ if (padding) {
447
+ if (job.rc < 0) {
448
+ rb_raise(rb_eArgError,
449
+ "Invalid data, illegal padding. Could be using a wrong key.");
450
+ }
451
+ rb_str_resize(retval, job.rc);
190
452
  }
191
453
 
192
- retbuf = RSTRING_PTR(retval);
454
+ return retval;
455
+ }
456
+
457
+ static VALUE
458
+ bytes_to_hex(VALUE bytes)
459
+ {
460
+ static const char hex[] = "0123456789abcdef";
461
+ const unsigned char *s;
462
+ char *d;
463
+ long i, len;
464
+ VALUE out;
465
+
466
+ StringValue(bytes);
467
+ len = RSTRING_LEN(bytes);
468
+ out = rb_usascii_str_new(NULL, len * 2);
469
+ s = (const unsigned char *)RSTRING_PTR(bytes);
470
+ d = RSTRING_PTR(out);
471
+ for (i = 0; i < len; i++) {
472
+ d[i * 2] = hex[s[i] >> 4];
473
+ d[i * 2 + 1] = hex[s[i] & 0x0f];
474
+ }
475
+ return out;
476
+ }
193
477
 
194
- alen = dlen >> 2;
195
- d = (uint32_t *)calloc(alen, sizeof(uint32_t));
478
+ static int
479
+ hex_nibble(int c)
480
+ {
481
+ if (c >= '0' && c <= '9') return c - '0';
482
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
483
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
484
+ return -1;
485
+ }
196
486
 
197
- if (!d) {
198
- rb_raise(rb_eNoMemError, "calloc failed.");
199
- return Qnil;
487
+ static VALUE
488
+ hex_to_bytes(VALUE hex)
489
+ {
490
+ const unsigned char *s;
491
+ unsigned char *d;
492
+ long i, len;
493
+ VALUE out;
494
+
495
+ StringValue(hex);
496
+ len = RSTRING_LEN(hex);
497
+ if (len & 1) {
498
+ rb_raise(rb_eArgError, "Odd-length string");
200
499
  }
201
500
 
202
- retval = rb_str_new(NULL, (alen << 2));
501
+ out = rb_str_new(NULL, len / 2);
502
+ rb_enc_associate(out, rb_ascii8bit_encoding());
503
+ s = (const unsigned char *)RSTRING_PTR(hex);
504
+ d = (unsigned char *)RSTRING_PTR(out);
203
505
 
204
- if (!retval) {
205
- free(d);
206
- return NULL;
506
+ for (i = 0; i < len; i += 2) {
507
+ int hi = hex_nibble(s[i]);
508
+ int lo = hex_nibble(s[i + 1]);
509
+ if (hi < 0 || lo < 0) {
510
+ rb_raise(rb_eArgError, "Non-hexadecimal digit found");
511
+ }
512
+ d[i / 2] = (unsigned char)((hi << 4) | lo);
207
513
  }
514
+ return out;
515
+ }
516
+
517
+ static VALUE
518
+ xxtea_s_encrypt(int argc, VALUE *argv, VALUE klass)
519
+ {
520
+ VALUE data, key, opts;
521
+ int padding;
522
+ unsigned int rounds;
523
+ char keybuf[16];
524
+
525
+ (void)klass;
526
+ rb_scan_args(argc, argv, "2:", &data, &key, &opts);
527
+ parse_opts(opts, &padding, &rounds);
528
+ require_key(key, keybuf);
529
+ return encrypt_impl(data, keybuf, padding, rounds);
530
+ }
208
531
 
209
- retbuf = RSTRING_PTR(retval);
532
+ static VALUE
533
+ xxtea_s_decrypt(int argc, VALUE *argv, VALUE klass)
534
+ {
535
+ VALUE data, key, opts;
536
+ int padding;
537
+ unsigned int rounds;
538
+ char keybuf[16];
539
+
540
+ (void)klass;
541
+ rb_scan_args(argc, argv, "2:", &data, &key, &opts);
542
+ parse_opts(opts, &padding, &rounds);
543
+ require_key(key, keybuf);
544
+ return decrypt_impl(data, keybuf, padding, rounds);
545
+ }
210
546
 
211
- bytes2longs(StringValuePtr(data), dlen, d, 0);
212
- bytes2longs(StringValuePtr(key), klen, k, 0);
213
- btea(d, -alen, k);
547
+ static VALUE
548
+ xxtea_s_encrypt_hex(int argc, VALUE *argv, VALUE klass)
549
+ {
550
+ return bytes_to_hex(xxtea_s_encrypt(argc, argv, klass));
551
+ }
214
552
 
215
- if ((rc = longs2bytes(d, alen, retbuf, 1)) != dlen) {
216
- /* Remove PKCS#7 padded chars */
217
- rb_str_resize(retval, rc);
218
- }
553
+ static VALUE
554
+ xxtea_s_decrypt_hex(int argc, VALUE *argv, VALUE klass)
555
+ {
556
+ VALUE data, key, opts;
557
+ int padding;
558
+ unsigned int rounds;
559
+ char keybuf[16];
560
+ VALUE raw;
561
+
562
+ rb_scan_args(argc, argv, "2:", &data, &key, &opts);
563
+ parse_opts(opts, &padding, &rounds);
564
+ raw = hex_to_bytes(data);
565
+ require_key(key, keybuf);
566
+ return decrypt_impl(raw, keybuf, padding, rounds);
567
+ }
219
568
 
220
- return retval;
569
+ static VALUE
570
+ xxtea_alloc(VALUE klass)
571
+ {
572
+ xxtea_cipher_t *cipher;
573
+ VALUE obj = TypedData_Make_Struct(klass, xxtea_cipher_t, &xxtea_cipher_type, cipher);
574
+ memset(cipher, 0, sizeof(*cipher));
575
+ cipher->padding = 4;
576
+ cipher->rounds = 0;
577
+ return obj;
578
+ }
579
+
580
+ static VALUE
581
+ xxtea_initialize(int argc, VALUE *argv, VALUE self)
582
+ {
583
+ VALUE key, opts;
584
+ xxtea_cipher_t *cipher = xxtea_get(self);
585
+ int padding;
586
+ unsigned int rounds;
587
+
588
+ rb_scan_args(argc, argv, "1:", &key, &opts);
589
+ parse_opts(opts, &padding, &rounds);
590
+ require_key(key, cipher->key);
591
+ cipher->padding = padding;
592
+ cipher->rounds = rounds;
593
+ return self;
221
594
  }
222
595
 
596
+ static VALUE
597
+ xxtea_encrypt(VALUE self, VALUE data)
598
+ {
599
+ xxtea_cipher_t *cipher = xxtea_get(self);
600
+ return encrypt_impl(data, cipher->key, cipher->padding, cipher->rounds);
601
+ }
602
+
603
+ static VALUE
604
+ xxtea_decrypt(VALUE self, VALUE data)
605
+ {
606
+ xxtea_cipher_t *cipher = xxtea_get(self);
607
+ return decrypt_impl(data, cipher->key, cipher->padding, cipher->rounds);
608
+ }
609
+
610
+ static VALUE
611
+ xxtea_encrypt_hex(VALUE self, VALUE data)
612
+ {
613
+ return bytes_to_hex(xxtea_encrypt(self, data));
614
+ }
615
+
616
+ static VALUE
617
+ xxtea_decrypt_hex(VALUE self, VALUE data)
618
+ {
619
+ xxtea_cipher_t *cipher = xxtea_get(self);
620
+ VALUE raw = hex_to_bytes(data);
621
+ return decrypt_impl(raw, cipher->key, cipher->padding, cipher->rounds);
622
+ }
623
+
624
+ static VALUE
625
+ xxtea_inspect(VALUE self)
626
+ {
627
+ xxtea_cipher_t *cipher = xxtea_get(self);
628
+ return rb_sprintf("#<%s:%p padding=%s rounds=%u>",
629
+ rb_obj_classname(self), (void *)self,
630
+ cipher->padding == 0 ? "false" :
631
+ cipher->padding == 4 ? "pkcs7_4_min8" : "pkcs7_8",
632
+ cipher->rounds);
633
+ }
223
634
 
224
- void Init_xxtea()
635
+ void
636
+ Init_xxtea(void)
225
637
  {
226
- VALUE mXXTEA = rb_define_module("XXTEA");
227
- rb_define_singleton_method(mXXTEA, "encrypt", xxtea_encrypt, 2);
228
- rb_define_singleton_method(mXXTEA, "decrypt", xxtea_decrypt, 2);
638
+ rb_ext_ractor_safe(true);
639
+
640
+ id_padding = rb_intern("padding");
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");
645
+
646
+ cXXTEA = rb_define_class("XXTEA", rb_cObject);
647
+ rb_define_alloc_func(cXXTEA, xxtea_alloc);
648
+
649
+ rb_define_singleton_method(cXXTEA, "encrypt", xxtea_s_encrypt, -1);
650
+ rb_define_singleton_method(cXXTEA, "decrypt", xxtea_s_decrypt, -1);
651
+ rb_define_singleton_method(cXXTEA, "encrypt_hex", xxtea_s_encrypt_hex, -1);
652
+ rb_define_singleton_method(cXXTEA, "decrypt_hex", xxtea_s_decrypt_hex, -1);
653
+
654
+ rb_define_method(cXXTEA, "initialize", xxtea_initialize, -1);
655
+ rb_define_method(cXXTEA, "encrypt", xxtea_encrypt, 1);
656
+ rb_define_method(cXXTEA, "decrypt", xxtea_decrypt, 1);
657
+ rb_define_method(cXXTEA, "encrypt_hex", xxtea_encrypt_hex, 1);
658
+ rb_define_method(cXXTEA, "decrypt_hex", xxtea_decrypt_hex, 1);
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));
229
663
  }
data/lib/xxtea/version.rb CHANGED
@@ -1,3 +1,5 @@
1
- module XXTEA
2
- VERSION = "0.0.1"
1
+ # frozen_string_literal: true
2
+
3
+ class XXTEA
4
+ VERSION = "1.1.0"
3
5
  end
data/lib/xxtea.rb CHANGED
@@ -1,13 +1,4 @@
1
- require 'xxtea/version'
2
- require 'xxtea/xxtea'
1
+ # frozen_string_literal: true
3
2
 
4
-
5
- module XXTEA
6
- def self.encrypt_hex(data, key)
7
- encrypt(data, key).unpack('H*').first
8
- end
9
-
10
- def self.decrypt_hex(data, key)
11
- decrypt([data].pack('H*'), key)
12
- end
13
- end
3
+ require "xxtea/version"
4
+ require "xxtea/xxtea"
metadata CHANGED
@@ -1,16 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xxtea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yue Du
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-08 00:00:00.000000000 Z
11
+ date: 2026-08-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Ruby xxtea module
13
+ description: |
14
+ XXTEA implemented as a Ruby C extension. Ciphertext is compatible with the
15
+ Python xxtea package: little-endian 32-bit words and non-standard 4-byte
16
+ PKCS#7 padding.
14
17
  email:
15
18
  - ifduyue@gmail.com
16
19
  executables: []
@@ -18,19 +21,23 @@ extensions:
18
21
  - ext/xxtea/extconf.rb
19
22
  extra_rdoc_files: []
20
23
  files:
21
- - ".gitignore"
22
- - Gemfile
23
- - Rakefile
24
+ - CHANGELOG.md
25
+ - LICENSE
26
+ - README.md
24
27
  - ext/xxtea/extconf.rb
25
28
  - ext/xxtea/xxtea.c
26
29
  - lib/xxtea.rb
27
30
  - lib/xxtea/version.rb
28
- - xxtea.gemspec
29
- homepage: http://github.com/ifduyue/xxtea
31
+ homepage: https://github.com/ifduyue/ruby-xxtea
30
32
  licenses:
31
- - BSD
32
- metadata: {}
33
- post_install_message:
33
+ - BSD-2-Clause
34
+ metadata:
35
+ homepage_uri: https://github.com/ifduyue/ruby-xxtea
36
+ source_code_uri: https://github.com/ifduyue/ruby-xxtea
37
+ changelog_uri: https://github.com/ifduyue/ruby-xxtea/blob/master/CHANGELOG.md
38
+ bug_tracker_uri: https://github.com/ifduyue/ruby-xxtea/issues
39
+ rubygems_mfa_required: 'true'
40
+ post_install_message:
34
41
  rdoc_options: []
35
42
  require_paths:
36
43
  - lib
@@ -38,16 +45,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
38
45
  requirements:
39
46
  - - ">="
40
47
  - !ruby/object:Gem::Version
41
- version: '0'
48
+ version: 3.1.0
42
49
  required_rubygems_version: !ruby/object:Gem::Requirement
43
50
  requirements:
44
51
  - - ">="
45
52
  - !ruby/object:Gem::Version
46
53
  version: '0'
47
54
  requirements: []
48
- rubyforge_project:
49
- rubygems_version: 2.4.2
50
- signing_key:
55
+ rubygems_version: 3.5.22
56
+ signing_key:
51
57
  specification_version: 4
52
- summary: Ruby xxtea module
58
+ summary: XXTEA block cipher as a Ruby C extension
53
59
  test_files: []
data/.gitignore DELETED
@@ -1,3 +0,0 @@
1
- *.bundle
2
- *.gem
3
- *.o
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- gemspec
4
-
5
- gem 'rake'
6
- gem 'rake-compiler'
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- require 'rake/extensiontask'
3
-
4
- Rake::ExtensionTask.new('xxtea') do |ext|
5
- ext.lib_dir = 'lib/xxtea'
6
- end
data/xxtea.gemspec DELETED
@@ -1,21 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- lib = File.expand_path('../lib', __FILE__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
-
6
- require 'xxtea/version'
7
- Gem::Specification.new do |gem|
8
- gem.name = "xxtea"
9
- gem.version = XXTEA::VERSION
10
- gem.authors = ["Yue Du"]
11
- gem.email = ["ifduyue@gmail.com"]
12
- gem.description = %q{Ruby xxtea module}
13
- gem.summary = %q{Ruby xxtea module}
14
- gem.homepage = "http://github.com/ifduyue/xxtea"
15
- gem.license = 'BSD'
16
- gem.files = `git ls-files`.split($/)
17
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
- gem.require_paths = ["lib"]
20
- gem.extensions = ["ext/xxtea/extconf.rb"]
21
- end