xxtea 0.0.1 → 1.0.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 +5 -5
- data/CHANGELOG.md +16 -0
- data/LICENSE +22 -0
- data/README.md +216 -0
- data/ext/xxtea/extconf.rb +3 -3
- data/ext/xxtea/xxtea.c +486 -131
- data/lib/xxtea/version.rb +4 -2
- data/lib/xxtea.rb +3 -12
- metadata +23 -17
- data/.gitignore +0 -3
- data/Gemfile +0 -6
- data/Rakefile +0 -6
- data/xxtea.gemspec +0 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b8eca82a1c887086264d8807bf55a1980dc28f35f956c02a5bf66ebef33ab8f6
|
|
4
|
+
data.tar.gz: d6707b17d68788035c7d8f2d90210f15408ba3a1d7a0e3c4dc0636d2b519dd10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e829728fcc9f4296e74f9bee45568aab867679971c866ca992146a715a62444fea17602bf21322041c82bed745d7ba7a7f10905e7df4ecf194547b41164ac686
|
|
7
|
+
data.tar.gz: 3a4224238e8e3d062dfc83671981834e1ab571c2e293bb90095db8ad46de86921c94d87972dcd0f3180ab6c562d9913e1dd1f2f4692dd07815752ffb12b8349e
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
- Rewrite as a Ruby C extension compatible with Python [xxtea](https://github.com/ifduyue/xxtea) 5.3.3.
|
|
6
|
+
- Keep `XXTEA.encrypt` / `XXTEA.decrypt` (16-byte key, non-standard 4-byte PKCS#7 padding).
|
|
7
|
+
- Add `padding:` and `rounds:` keyword arguments.
|
|
8
|
+
- Add `encrypt_hex` / `decrypt_hex`.
|
|
9
|
+
- Add `XXTEA.new(key, padding: true, rounds: 0)` cipher objects.
|
|
10
|
+
- Validate PKCS#7 padding on decrypt; reject invalid padding with `ArgumentError`.
|
|
11
|
+
- Encode ciphertext as little-endian bytes on all architectures.
|
|
12
|
+
- Require Ruby >= 3.1.
|
|
13
|
+
|
|
14
|
+
## 0.0.1 — 2015-03-08
|
|
15
|
+
|
|
16
|
+
- 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,216 @@
|
|
|
1
|
+
# xxtea
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ifduyue/ruby-xxtea/actions/workflows/test.yml)
|
|
4
|
+
[](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
|
+
Ciphertext is compatible with the [Python xxtea](https://github.com/ifduyue/xxtea) package.
|
|
9
|
+
|
|
10
|
+
The XXTEA algorithm takes a 128-bit key and operates on an array of 32-bit
|
|
11
|
+
integers (at least 2 integers), but it doesn't define the conversions between
|
|
12
|
+
bytes and array. Due to this reason, many XXTEA implementations out there are
|
|
13
|
+
not compatible with each other.
|
|
14
|
+
|
|
15
|
+
In this implementation, the conversions between bytes and array are taken care
|
|
16
|
+
of by `longs2bytes` and `bytes2longs`. A non-standard 4-byte block
|
|
17
|
+
[PKCS#7](https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7) padding is
|
|
18
|
+
used to make sure that the input bytes are padded to a multiple of 4-byte (the
|
|
19
|
+
size of a 32-bit integer) and at least 8-byte long (the size of two 32-bit
|
|
20
|
+
integers, which is required by the XXTEA algorithm). As a result of these
|
|
21
|
+
measures, you can encrypt not only texts, but also any binary bytes of any
|
|
22
|
+
length.
|
|
23
|
+
|
|
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).
|
|
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=true
|
|
91
|
+
c = XXTEA.new(key, rounds: 64) # override rounds
|
|
92
|
+
c = XXTEA.new(key, padding: false) # disable padding
|
|
93
|
+
c = XXTEA.new(key, padding: false, rounds: 42)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`encrypt_hex` and `decrypt_hex` operate on ciphertext in a hexadecimal
|
|
97
|
+
representation. They are exactly equivalent to:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
hexenc = XXTEA.encrypt(s, key).unpack1("H*")
|
|
101
|
+
s == XXTEA.decrypt([hexenc].pack("H*"), key) # => true
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Padding
|
|
105
|
+
|
|
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).
|
|
110
|
+
|
|
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.
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
XXTEA.decrypt_hex(XXTEA.encrypt_hex("", key), key) # => ""
|
|
118
|
+
XXTEA.decrypt_hex(XXTEA.encrypt_hex(" ", key), key) # => " "
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
You can disable padding by setting `padding: false`.
|
|
122
|
+
In this case data will not be padded, so data length must be a multiple of 4
|
|
123
|
+
bytes and must not be less than 8 bytes. Otherwise `ArgumentError` will be
|
|
124
|
+
raised:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
XXTEA.encrypt_hex("", key, padding: false)
|
|
128
|
+
# ArgumentError: Data length must be a multiple of 4 bytes and must not be less than 8 bytes
|
|
129
|
+
|
|
130
|
+
XXTEA.encrypt_hex("xxtea is good", key, padding: false)
|
|
131
|
+
# ArgumentError: Data length must be a multiple of 4 bytes and must not be less than 8 bytes
|
|
132
|
+
|
|
133
|
+
XXTEA.decrypt_hex(XXTEA.encrypt_hex("12345678", key, padding: false), key, padding: false)
|
|
134
|
+
# => "12345678"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Rounds
|
|
138
|
+
|
|
139
|
+
By default xxtea manipulates the input data for `6 + 52 / n` rounds,
|
|
140
|
+
where n denotes how many 32-bit integers the input data can fit in.
|
|
141
|
+
We can change this by setting the `rounds` parameter.
|
|
142
|
+
|
|
143
|
+
Do note that the more rounds it is, the more time will be consumed.
|
|
144
|
+
`rounds` must fit in a 32-bit unsigned integer; values exceeding
|
|
145
|
+
`2**32 - 1` raise `RangeError`.
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
data = "0123456789"
|
|
149
|
+
key = "abcdefghijklmnop"
|
|
150
|
+
XXTEA.encrypt_hex(data, key)
|
|
151
|
+
# => "5b80b08a5d1923e4cd992dd5"
|
|
152
|
+
6 + 52 / ((data.bytesize + 3) / 4) # 23
|
|
153
|
+
XXTEA.encrypt_hex(data, key, rounds: 23)
|
|
154
|
+
# => "5b80b08a5d1923e4cd992dd5"
|
|
155
|
+
XXTEA.encrypt_hex(data, key, rounds: 1024)
|
|
156
|
+
# => "1577bbf28c43ced93bd50720"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Catching Exceptions
|
|
160
|
+
|
|
161
|
+
When calling these methods, an `ArgumentError`, `TypeError`, or `RangeError`
|
|
162
|
+
may be raised.
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
begin
|
|
166
|
+
XXTEA.decrypt("", key: "")
|
|
167
|
+
rescue => e
|
|
168
|
+
puts "#{e.class} : #{e.message}"
|
|
169
|
+
end
|
|
170
|
+
# ArgumentError : Need a 16-byte key.
|
|
171
|
+
|
|
172
|
+
XXTEA.decrypt("", " " * 16)
|
|
173
|
+
# ArgumentError : Data length must be a multiple of 4 bytes and must not be less than 8 bytes
|
|
174
|
+
|
|
175
|
+
XXTEA.decrypt(" " * 8, " " * 16)
|
|
176
|
+
# ArgumentError : Invalid data, illegal padding. Could be using a wrong key.
|
|
177
|
+
|
|
178
|
+
XXTEA.decrypt_hex(" " * 8, " " * 16)
|
|
179
|
+
# ArgumentError : Non-hexadecimal digit found
|
|
180
|
+
|
|
181
|
+
XXTEA.decrypt_hex("abc", " " * 16)
|
|
182
|
+
# ArgumentError : Odd-length string
|
|
183
|
+
|
|
184
|
+
XXTEA.decrypt_hex("abcd", " " * 16)
|
|
185
|
+
# ArgumentError : Data length must be a multiple of 4 bytes and must not be less than 8 bytes
|
|
186
|
+
|
|
187
|
+
XXTEA.encrypt("x", "k" * 16, rounds: 2**32)
|
|
188
|
+
# RangeError : rounds value too large
|
|
189
|
+
|
|
190
|
+
XXTEA.new("short")
|
|
191
|
+
# ArgumentError : Need a 16-byte key.
|
|
192
|
+
|
|
193
|
+
XXTEA.new("k" * 16, rounds: 2**32)
|
|
194
|
+
# RangeError : rounds value too large
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Compatibility
|
|
198
|
+
|
|
199
|
+
- Compatible with [Python xxtea](https://github.com/ifduyue/xxtea) (same padding, endianness, and rounds).
|
|
200
|
+
- 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
|
+
|
|
202
|
+
## Releasing
|
|
203
|
+
|
|
204
|
+
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.
|
|
205
|
+
|
|
206
|
+
The trusted publisher on RubyGems.org must match:
|
|
207
|
+
|
|
208
|
+
- Repository owner: `ifduyue`
|
|
209
|
+
- Repository name: `ruby-xxtea`
|
|
210
|
+
- Workflow filename: `build.yml`
|
|
211
|
+
- Environment: `release`
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
BSD-2-Clause. See [LICENSE](LICENSE).
|
|
216
|
+
|
data/ext/xxtea/extconf.rb
CHANGED
data/ext/xxtea/xxtea.c
CHANGED
|
@@ -1,19 +1,84 @@
|
|
|
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
|
+
* Ciphertext is compatible with the Python xxtea package
|
|
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).
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
#include "ruby.h"
|
|
31
|
+
#include "ruby/encoding.h"
|
|
32
|
+
|
|
33
|
+
#include <limits.h>
|
|
2
34
|
#include <stdint.h>
|
|
3
|
-
#include <
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
35
|
+
#include <string.h>
|
|
36
|
+
|
|
37
|
+
#define DELTA 0x9e3779b9U
|
|
38
|
+
#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
|
|
39
|
+
|
|
40
|
+
#if defined(WORDS_BIGENDIAN)
|
|
41
|
+
# define XXTEA_LITTLE_ENDIAN 0
|
|
42
|
+
#else
|
|
43
|
+
# define XXTEA_LITTLE_ENDIAN 1
|
|
44
|
+
#endif
|
|
45
|
+
|
|
46
|
+
static ID id_padding;
|
|
47
|
+
static ID id_rounds;
|
|
48
|
+
static VALUE cXXTEA;
|
|
49
|
+
|
|
50
|
+
typedef struct {
|
|
51
|
+
char key[16];
|
|
52
|
+
unsigned int rounds;
|
|
53
|
+
int padding;
|
|
54
|
+
} xxtea_cipher_t;
|
|
55
|
+
|
|
56
|
+
static const rb_data_type_t xxtea_cipher_type = {
|
|
57
|
+
.wrap_struct_name = "XXTEA",
|
|
58
|
+
.function = {
|
|
59
|
+
.dmark = NULL,
|
|
60
|
+
.dfree = RUBY_DEFAULT_FREE,
|
|
61
|
+
.dsize = NULL,
|
|
62
|
+
},
|
|
63
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
static inline xxtea_cipher_t *
|
|
67
|
+
xxtea_get(VALUE obj)
|
|
68
|
+
{
|
|
69
|
+
xxtea_cipher_t *cipher;
|
|
70
|
+
TypedData_Get_Struct(obj, xxtea_cipher_t, &xxtea_cipher_type, cipher);
|
|
71
|
+
return cipher;
|
|
72
|
+
}
|
|
9
73
|
|
|
10
|
-
static void
|
|
74
|
+
static void
|
|
75
|
+
btea(uint32_t *v, int n, uint32_t const key[4], unsigned int rounds)
|
|
11
76
|
{
|
|
12
77
|
uint32_t y, z, sum;
|
|
13
|
-
unsigned p,
|
|
78
|
+
unsigned p, e;
|
|
14
79
|
|
|
15
80
|
if (n > 1) { /* Coding Part */
|
|
16
|
-
rounds = 6 + 52 / n;
|
|
81
|
+
rounds = rounds == 0 ? (unsigned)(6 + 52 / n) : rounds;
|
|
17
82
|
sum = 0;
|
|
18
83
|
z = v[n - 1];
|
|
19
84
|
|
|
@@ -21,26 +86,25 @@ static void btea(uint32_t *v, int n, uint32_t const key[4])
|
|
|
21
86
|
sum += DELTA;
|
|
22
87
|
e = (sum >> 2) & 3;
|
|
23
88
|
|
|
24
|
-
for (p = 0; p < n - 1; p++) {
|
|
89
|
+
for (p = 0; p < (unsigned)(n - 1); p++) {
|
|
25
90
|
y = v[p + 1];
|
|
26
91
|
z = v[p] += MX;
|
|
27
92
|
}
|
|
28
93
|
|
|
29
94
|
y = v[0];
|
|
30
95
|
z = v[n - 1] += MX;
|
|
31
|
-
}
|
|
32
|
-
while (--rounds);
|
|
96
|
+
} while (--rounds);
|
|
33
97
|
}
|
|
34
98
|
else if (n < -1) { /* Decoding Part */
|
|
35
99
|
n = -n;
|
|
36
|
-
rounds = 6 + 52 / n;
|
|
37
|
-
sum = rounds * DELTA;
|
|
100
|
+
rounds = rounds == 0 ? (unsigned)(6 + 52 / n) : rounds;
|
|
101
|
+
sum = (uint32_t)(rounds * DELTA);
|
|
38
102
|
y = v[0];
|
|
39
103
|
|
|
40
104
|
do {
|
|
41
105
|
e = (sum >> 2) & 3;
|
|
42
106
|
|
|
43
|
-
for (p = n - 1; p > 0; p--) {
|
|
107
|
+
for (p = (unsigned)(n - 1); p > 0; p--) {
|
|
44
108
|
z = v[p - 1];
|
|
45
109
|
y = v[p] -= MX;
|
|
46
110
|
}
|
|
@@ -48,182 +112,473 @@ static void btea(uint32_t *v, int n, uint32_t const key[4])
|
|
|
48
112
|
z = v[n - 1];
|
|
49
113
|
y = v[0] -= MX;
|
|
50
114
|
sum -= DELTA;
|
|
51
|
-
}
|
|
52
|
-
while (--rounds);
|
|
115
|
+
} while (--rounds);
|
|
53
116
|
}
|
|
54
117
|
}
|
|
55
118
|
|
|
56
|
-
static
|
|
119
|
+
static void
|
|
120
|
+
bytes2longs(const char *in, long inlen, uint32_t *out, int padding)
|
|
57
121
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
out[i
|
|
122
|
+
long i, nwords;
|
|
123
|
+
int pad;
|
|
124
|
+
const unsigned char *s = (const unsigned char *)in;
|
|
125
|
+
|
|
126
|
+
nwords = inlen >> 2;
|
|
127
|
+
for (i = 0; i < nwords; i++) {
|
|
128
|
+
#if XXTEA_LITTLE_ENDIAN
|
|
129
|
+
memcpy(&out[i], s + 4 * i, 4);
|
|
130
|
+
#else
|
|
131
|
+
const unsigned char *p = s + 4 * i;
|
|
132
|
+
out[i] = (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
|
133
|
+
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
|
134
|
+
#endif
|
|
66
135
|
}
|
|
67
136
|
|
|
68
|
-
/*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
137
|
+
/*
|
|
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.
|
|
143
|
+
*/
|
|
144
|
+
i = nwords << 2;
|
|
145
|
+
if (padding || (inlen & 3) != 0) {
|
|
146
|
+
uint32_t w = 0;
|
|
147
|
+
int r = (int)(inlen & 3);
|
|
148
|
+
int shift = 0;
|
|
149
|
+
for (; i < inlen; i++, shift += 8) {
|
|
150
|
+
w |= (uint32_t)s[i] << shift;
|
|
151
|
+
}
|
|
152
|
+
if (padding) {
|
|
153
|
+
pad = 4 - r;
|
|
154
|
+
/* Ensure XXTEA always has at least two 32-bit words. */
|
|
155
|
+
if (inlen < 4) {
|
|
156
|
+
pad += 4;
|
|
157
|
+
}
|
|
158
|
+
w |= (uint32_t)pad * 0x01010101u & (~0u << (8 * r));
|
|
159
|
+
if (inlen < 4) {
|
|
160
|
+
out[nwords + 1] = (uint32_t)pad * 0x01010101u;
|
|
161
|
+
}
|
|
75
162
|
}
|
|
163
|
+
out[nwords] = w;
|
|
76
164
|
}
|
|
77
|
-
|
|
78
|
-
/* Divided by 4, and then rounded up (ceil) to an integer.
|
|
79
|
-
* Which is the number of how many longs we've got.
|
|
80
|
-
*/
|
|
81
|
-
return ((i - 1) >> 2) + 1;
|
|
82
165
|
}
|
|
83
166
|
|
|
84
|
-
static
|
|
167
|
+
static long
|
|
168
|
+
longs2bytes(const uint32_t *in, long inlen, char *out, int padding)
|
|
85
169
|
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
170
|
+
long i, outlen;
|
|
171
|
+
int pad;
|
|
172
|
+
unsigned char *s = (unsigned char *)out;
|
|
173
|
+
|
|
174
|
+
#if XXTEA_LITTLE_ENDIAN
|
|
175
|
+
/* Callers always pass the same buffer; words are already LE bytes. */
|
|
176
|
+
(void)in;
|
|
177
|
+
#else
|
|
178
|
+
/*
|
|
179
|
+
* Big endian: write each word as little-endian bytes. Snapshot the
|
|
180
|
+
* whole word first because in and out alias.
|
|
181
|
+
*/
|
|
91
182
|
for (i = 0; i < inlen; i++) {
|
|
92
|
-
|
|
93
|
-
s[4 * i
|
|
94
|
-
s[4 * i +
|
|
95
|
-
s[4 * i +
|
|
183
|
+
uint32_t word = in[i];
|
|
184
|
+
s[4 * i] = (unsigned char)(word & 0xFF);
|
|
185
|
+
s[4 * i + 1] = (unsigned char)((word >> 8) & 0xFF);
|
|
186
|
+
s[4 * i + 2] = (unsigned char)((word >> 16) & 0xFF);
|
|
187
|
+
s[4 * i + 3] = (unsigned char)((word >> 24) & 0xFF);
|
|
96
188
|
}
|
|
189
|
+
#endif
|
|
97
190
|
|
|
98
|
-
|
|
191
|
+
outlen = inlen * 4;
|
|
99
192
|
|
|
100
|
-
/* PKCS#7 unpadding */
|
|
193
|
+
/* 4-byte PKCS#7-style unpadding. */
|
|
101
194
|
if (padding) {
|
|
102
|
-
pad = s[
|
|
103
|
-
|
|
104
|
-
|
|
195
|
+
pad = s[outlen - 1];
|
|
196
|
+
outlen -= pad;
|
|
197
|
+
|
|
198
|
+
if (pad < 1 || pad > 8) {
|
|
199
|
+
return -1;
|
|
200
|
+
}
|
|
105
201
|
|
|
106
|
-
|
|
202
|
+
if (outlen < 0) {
|
|
203
|
+
return -2;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (i = outlen; i < inlen * 4; i++) {
|
|
207
|
+
if (s[i] != pad) {
|
|
208
|
+
return -3;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
107
212
|
|
|
108
|
-
|
|
109
|
-
return
|
|
213
|
+
s[outlen] = '\0';
|
|
214
|
+
return outlen;
|
|
110
215
|
}
|
|
111
216
|
|
|
112
|
-
|
|
217
|
+
typedef struct {
|
|
218
|
+
const char *data;
|
|
219
|
+
long data_len;
|
|
220
|
+
char key[16];
|
|
221
|
+
uint32_t *buf;
|
|
222
|
+
int n;
|
|
223
|
+
int padding;
|
|
224
|
+
unsigned int rounds;
|
|
225
|
+
long rc;
|
|
226
|
+
} crypt_job;
|
|
227
|
+
|
|
228
|
+
static void *
|
|
229
|
+
encrypt_nogvl(void *ptr)
|
|
113
230
|
{
|
|
114
|
-
|
|
115
|
-
uint32_t
|
|
116
|
-
|
|
117
|
-
|
|
231
|
+
crypt_job *j = (crypt_job *)ptr;
|
|
232
|
+
uint32_t k[4];
|
|
233
|
+
|
|
234
|
+
bytes2longs(j->data, j->data_len, j->buf, j->padding);
|
|
235
|
+
bytes2longs(j->key, 16, k, 0);
|
|
236
|
+
btea(j->buf, j->n, k, j->rounds);
|
|
237
|
+
#if !XXTEA_LITTLE_ENDIAN
|
|
238
|
+
longs2bytes(j->buf, j->n, (char *)j->buf, 0);
|
|
239
|
+
#endif
|
|
240
|
+
return NULL;
|
|
241
|
+
}
|
|
118
242
|
|
|
119
|
-
|
|
120
|
-
|
|
243
|
+
static void *
|
|
244
|
+
decrypt_nogvl(void *ptr)
|
|
245
|
+
{
|
|
246
|
+
crypt_job *j = (crypt_job *)ptr;
|
|
247
|
+
uint32_t k[4];
|
|
248
|
+
|
|
249
|
+
bytes2longs(j->data, j->data_len, j->buf, 0);
|
|
250
|
+
bytes2longs(j->key, 16, k, 0);
|
|
251
|
+
btea(j->buf, -j->n, k, j->rounds);
|
|
252
|
+
j->rc = longs2bytes(j->buf, j->n, (char *)j->buf, j->padding);
|
|
253
|
+
return NULL;
|
|
254
|
+
}
|
|
121
255
|
|
|
122
|
-
|
|
123
|
-
|
|
256
|
+
static unsigned int
|
|
257
|
+
parse_rounds(VALUE obj)
|
|
258
|
+
{
|
|
259
|
+
if (!RB_INTEGER_TYPE_P(obj)) {
|
|
260
|
+
rb_raise(rb_eTypeError, "rounds must be an Integer");
|
|
261
|
+
}
|
|
262
|
+
if (RTEST(rb_funcall(obj, rb_intern("<"), 1, INT2FIX(0))) ||
|
|
263
|
+
RTEST(rb_funcall(obj, rb_intern(">"), 1, UINT2NUM(UINT_MAX)))) {
|
|
264
|
+
rb_raise(rb_eRangeError, "rounds value too large");
|
|
265
|
+
}
|
|
266
|
+
return NUM2UINT(obj);
|
|
267
|
+
}
|
|
124
268
|
|
|
125
|
-
|
|
126
|
-
|
|
269
|
+
static void
|
|
270
|
+
parse_opts(VALUE opts, int *padding, unsigned int *rounds)
|
|
271
|
+
{
|
|
272
|
+
ID kwids[2];
|
|
273
|
+
VALUE kwvals[2];
|
|
127
274
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
275
|
+
*padding = 1;
|
|
276
|
+
*rounds = 0;
|
|
277
|
+
if (NIL_P(opts)) {
|
|
278
|
+
return;
|
|
131
279
|
}
|
|
132
280
|
|
|
133
|
-
|
|
134
|
-
|
|
281
|
+
kwids[0] = id_padding;
|
|
282
|
+
kwids[1] = id_rounds;
|
|
283
|
+
rb_get_kwargs(opts, kwids, 0, 2, kwvals);
|
|
284
|
+
|
|
285
|
+
if (kwvals[0] != Qundef) {
|
|
286
|
+
*padding = RTEST(kwvals[0]);
|
|
287
|
+
}
|
|
288
|
+
if (kwvals[1] != Qundef) {
|
|
289
|
+
*rounds = parse_rounds(kwvals[1]);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
135
292
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
293
|
+
static void
|
|
294
|
+
require_key(VALUE key, char out[16])
|
|
295
|
+
{
|
|
296
|
+
StringValue(key);
|
|
297
|
+
if (RSTRING_LEN(key) != 16) {
|
|
298
|
+
rb_raise(rb_eArgError, "Need a 16-byte key.");
|
|
139
299
|
}
|
|
300
|
+
memcpy(out, RSTRING_PTR(key), 16);
|
|
301
|
+
}
|
|
140
302
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
303
|
+
static VALUE
|
|
304
|
+
encrypt_impl(VALUE data, const char key[16], int padding, unsigned int rounds)
|
|
305
|
+
{
|
|
306
|
+
crypt_job job;
|
|
307
|
+
VALUE retval;
|
|
308
|
+
long data_len, alen;
|
|
309
|
+
|
|
310
|
+
StringValue(data);
|
|
311
|
+
data_len = RSTRING_LEN(data);
|
|
144
312
|
|
|
145
|
-
|
|
313
|
+
if (!padding && (data_len < 8 || (data_len & 3) != 0)) {
|
|
314
|
+
rb_raise(rb_eArgError,
|
|
315
|
+
"Data length must be a multiple of 4 bytes and must not be less than 8 bytes");
|
|
316
|
+
}
|
|
146
317
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
318
|
+
alen = data_len < 4 ? 2 : (data_len >> 2) + padding;
|
|
319
|
+
if (alen > INT_MAX || alen > LONG_MAX / 4) {
|
|
320
|
+
rb_raise(rb_eRangeError, "data too large");
|
|
150
321
|
}
|
|
151
322
|
|
|
152
|
-
|
|
153
|
-
|
|
323
|
+
retval = rb_str_new(NULL, alen << 2);
|
|
324
|
+
rb_enc_associate(retval, rb_ascii8bit_encoding());
|
|
154
325
|
|
|
326
|
+
memset(&job, 0, sizeof(job));
|
|
327
|
+
job.data = RSTRING_PTR(data);
|
|
328
|
+
job.data_len = data_len;
|
|
329
|
+
memcpy(job.key, key, 16);
|
|
330
|
+
job.buf = (uint32_t *)RSTRING_PTR(retval);
|
|
331
|
+
job.n = (int)alen;
|
|
332
|
+
job.padding = padding;
|
|
333
|
+
job.rounds = rounds;
|
|
334
|
+
|
|
335
|
+
encrypt_nogvl(&job);
|
|
336
|
+
RB_GC_GUARD(data);
|
|
155
337
|
return retval;
|
|
156
338
|
}
|
|
157
339
|
|
|
158
|
-
|
|
340
|
+
static VALUE
|
|
341
|
+
decrypt_impl(VALUE data, const char key[16], int padding, unsigned int rounds)
|
|
159
342
|
{
|
|
160
|
-
|
|
161
|
-
uint32_t *d, k[4];
|
|
343
|
+
crypt_job job;
|
|
162
344
|
VALUE retval;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
d = NULL;
|
|
166
|
-
k[0] = k[1] = k[2] = k[3] = 0;
|
|
167
|
-
|
|
168
|
-
Check_Type(data, T_STRING);
|
|
169
|
-
Check_Type(key, T_STRING);
|
|
345
|
+
long data_len, alen;
|
|
170
346
|
|
|
171
|
-
|
|
172
|
-
|
|
347
|
+
StringValue(data);
|
|
348
|
+
data_len = RSTRING_LEN(data);
|
|
173
349
|
|
|
174
|
-
if (
|
|
175
|
-
rb_raise(rb_eArgError,
|
|
176
|
-
|
|
350
|
+
if (data_len & 3 || data_len < 8) {
|
|
351
|
+
rb_raise(rb_eArgError,
|
|
352
|
+
"Data length must be a multiple of 4 bytes and must not be less than 8 bytes");
|
|
177
353
|
}
|
|
178
354
|
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
rb_raise(
|
|
182
|
-
return Qnil;
|
|
355
|
+
alen = data_len / 4;
|
|
356
|
+
if (alen > INT_MAX || alen > LONG_MAX / 4) {
|
|
357
|
+
rb_raise(rb_eRangeError, "data too large");
|
|
183
358
|
}
|
|
184
359
|
|
|
185
|
-
retval = rb_str_new(NULL,
|
|
360
|
+
retval = rb_str_new(NULL, data_len);
|
|
361
|
+
rb_enc_associate(retval, rb_ascii8bit_encoding());
|
|
362
|
+
|
|
363
|
+
memset(&job, 0, sizeof(job));
|
|
364
|
+
job.data = RSTRING_PTR(data);
|
|
365
|
+
job.data_len = data_len;
|
|
366
|
+
memcpy(job.key, key, 16);
|
|
367
|
+
job.buf = (uint32_t *)RSTRING_PTR(retval);
|
|
368
|
+
job.n = (int)alen;
|
|
369
|
+
job.padding = padding;
|
|
370
|
+
job.rounds = rounds;
|
|
186
371
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
372
|
+
decrypt_nogvl(&job);
|
|
373
|
+
RB_GC_GUARD(data);
|
|
374
|
+
|
|
375
|
+
if (padding) {
|
|
376
|
+
if (job.rc < 0) {
|
|
377
|
+
rb_raise(rb_eArgError,
|
|
378
|
+
"Invalid data, illegal padding. Could be using a wrong key.");
|
|
379
|
+
}
|
|
380
|
+
rb_str_resize(retval, job.rc);
|
|
190
381
|
}
|
|
191
382
|
|
|
192
|
-
|
|
383
|
+
return retval;
|
|
384
|
+
}
|
|
193
385
|
|
|
194
|
-
|
|
195
|
-
|
|
386
|
+
static VALUE
|
|
387
|
+
bytes_to_hex(VALUE bytes)
|
|
388
|
+
{
|
|
389
|
+
static const char hex[] = "0123456789abcdef";
|
|
390
|
+
const unsigned char *s;
|
|
391
|
+
char *d;
|
|
392
|
+
long i, len;
|
|
393
|
+
VALUE out;
|
|
394
|
+
|
|
395
|
+
StringValue(bytes);
|
|
396
|
+
len = RSTRING_LEN(bytes);
|
|
397
|
+
out = rb_usascii_str_new(NULL, len * 2);
|
|
398
|
+
s = (const unsigned char *)RSTRING_PTR(bytes);
|
|
399
|
+
d = RSTRING_PTR(out);
|
|
400
|
+
for (i = 0; i < len; i++) {
|
|
401
|
+
d[i * 2] = hex[s[i] >> 4];
|
|
402
|
+
d[i * 2 + 1] = hex[s[i] & 0x0f];
|
|
403
|
+
}
|
|
404
|
+
return out;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
static int
|
|
408
|
+
hex_nibble(int c)
|
|
409
|
+
{
|
|
410
|
+
if (c >= '0' && c <= '9') return c - '0';
|
|
411
|
+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
412
|
+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
413
|
+
return -1;
|
|
414
|
+
}
|
|
196
415
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
416
|
+
static VALUE
|
|
417
|
+
hex_to_bytes(VALUE hex)
|
|
418
|
+
{
|
|
419
|
+
const unsigned char *s;
|
|
420
|
+
unsigned char *d;
|
|
421
|
+
long i, len;
|
|
422
|
+
VALUE out;
|
|
423
|
+
|
|
424
|
+
StringValue(hex);
|
|
425
|
+
len = RSTRING_LEN(hex);
|
|
426
|
+
if (len & 1) {
|
|
427
|
+
rb_raise(rb_eArgError, "Odd-length string");
|
|
200
428
|
}
|
|
201
429
|
|
|
202
|
-
|
|
430
|
+
out = rb_str_new(NULL, len / 2);
|
|
431
|
+
rb_enc_associate(out, rb_ascii8bit_encoding());
|
|
432
|
+
s = (const unsigned char *)RSTRING_PTR(hex);
|
|
433
|
+
d = (unsigned char *)RSTRING_PTR(out);
|
|
203
434
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
435
|
+
for (i = 0; i < len; i += 2) {
|
|
436
|
+
int hi = hex_nibble(s[i]);
|
|
437
|
+
int lo = hex_nibble(s[i + 1]);
|
|
438
|
+
if (hi < 0 || lo < 0) {
|
|
439
|
+
rb_raise(rb_eArgError, "Non-hexadecimal digit found");
|
|
440
|
+
}
|
|
441
|
+
d[i / 2] = (unsigned char)((hi << 4) | lo);
|
|
207
442
|
}
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
208
445
|
|
|
209
|
-
|
|
446
|
+
static VALUE
|
|
447
|
+
xxtea_s_encrypt(int argc, VALUE *argv, VALUE klass)
|
|
448
|
+
{
|
|
449
|
+
VALUE data, key, opts;
|
|
450
|
+
int padding;
|
|
451
|
+
unsigned int rounds;
|
|
452
|
+
char keybuf[16];
|
|
453
|
+
|
|
454
|
+
(void)klass;
|
|
455
|
+
rb_scan_args(argc, argv, "2:", &data, &key, &opts);
|
|
456
|
+
parse_opts(opts, &padding, &rounds);
|
|
457
|
+
require_key(key, keybuf);
|
|
458
|
+
return encrypt_impl(data, keybuf, padding, rounds);
|
|
459
|
+
}
|
|
210
460
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
461
|
+
static VALUE
|
|
462
|
+
xxtea_s_decrypt(int argc, VALUE *argv, VALUE klass)
|
|
463
|
+
{
|
|
464
|
+
VALUE data, key, opts;
|
|
465
|
+
int padding;
|
|
466
|
+
unsigned int rounds;
|
|
467
|
+
char keybuf[16];
|
|
468
|
+
|
|
469
|
+
(void)klass;
|
|
470
|
+
rb_scan_args(argc, argv, "2:", &data, &key, &opts);
|
|
471
|
+
parse_opts(opts, &padding, &rounds);
|
|
472
|
+
require_key(key, keybuf);
|
|
473
|
+
return decrypt_impl(data, keybuf, padding, rounds);
|
|
474
|
+
}
|
|
214
475
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
476
|
+
static VALUE
|
|
477
|
+
xxtea_s_encrypt_hex(int argc, VALUE *argv, VALUE klass)
|
|
478
|
+
{
|
|
479
|
+
return bytes_to_hex(xxtea_s_encrypt(argc, argv, klass));
|
|
480
|
+
}
|
|
219
481
|
|
|
220
|
-
|
|
482
|
+
static VALUE
|
|
483
|
+
xxtea_s_decrypt_hex(int argc, VALUE *argv, VALUE klass)
|
|
484
|
+
{
|
|
485
|
+
VALUE data, key, opts;
|
|
486
|
+
int padding;
|
|
487
|
+
unsigned int rounds;
|
|
488
|
+
char keybuf[16];
|
|
489
|
+
VALUE raw;
|
|
490
|
+
|
|
491
|
+
rb_scan_args(argc, argv, "2:", &data, &key, &opts);
|
|
492
|
+
parse_opts(opts, &padding, &rounds);
|
|
493
|
+
raw = hex_to_bytes(data);
|
|
494
|
+
require_key(key, keybuf);
|
|
495
|
+
return decrypt_impl(raw, keybuf, padding, rounds);
|
|
221
496
|
}
|
|
222
497
|
|
|
498
|
+
static VALUE
|
|
499
|
+
xxtea_alloc(VALUE klass)
|
|
500
|
+
{
|
|
501
|
+
xxtea_cipher_t *cipher;
|
|
502
|
+
VALUE obj = TypedData_Make_Struct(klass, xxtea_cipher_t, &xxtea_cipher_type, cipher);
|
|
503
|
+
memset(cipher, 0, sizeof(*cipher));
|
|
504
|
+
cipher->padding = 1;
|
|
505
|
+
cipher->rounds = 0;
|
|
506
|
+
return obj;
|
|
507
|
+
}
|
|
223
508
|
|
|
224
|
-
|
|
509
|
+
static VALUE
|
|
510
|
+
xxtea_initialize(int argc, VALUE *argv, VALUE self)
|
|
225
511
|
{
|
|
226
|
-
VALUE
|
|
227
|
-
|
|
228
|
-
|
|
512
|
+
VALUE key, opts;
|
|
513
|
+
xxtea_cipher_t *cipher = xxtea_get(self);
|
|
514
|
+
int padding;
|
|
515
|
+
unsigned int rounds;
|
|
516
|
+
|
|
517
|
+
rb_scan_args(argc, argv, "1:", &key, &opts);
|
|
518
|
+
parse_opts(opts, &padding, &rounds);
|
|
519
|
+
require_key(key, cipher->key);
|
|
520
|
+
cipher->padding = padding;
|
|
521
|
+
cipher->rounds = rounds;
|
|
522
|
+
return self;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
static VALUE
|
|
526
|
+
xxtea_encrypt(VALUE self, VALUE data)
|
|
527
|
+
{
|
|
528
|
+
xxtea_cipher_t *cipher = xxtea_get(self);
|
|
529
|
+
return encrypt_impl(data, cipher->key, cipher->padding, cipher->rounds);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
static VALUE
|
|
533
|
+
xxtea_decrypt(VALUE self, VALUE data)
|
|
534
|
+
{
|
|
535
|
+
xxtea_cipher_t *cipher = xxtea_get(self);
|
|
536
|
+
return decrypt_impl(data, cipher->key, cipher->padding, cipher->rounds);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
static VALUE
|
|
540
|
+
xxtea_encrypt_hex(VALUE self, VALUE data)
|
|
541
|
+
{
|
|
542
|
+
return bytes_to_hex(xxtea_encrypt(self, data));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
static VALUE
|
|
546
|
+
xxtea_decrypt_hex(VALUE self, VALUE data)
|
|
547
|
+
{
|
|
548
|
+
xxtea_cipher_t *cipher = xxtea_get(self);
|
|
549
|
+
VALUE raw = hex_to_bytes(data);
|
|
550
|
+
return decrypt_impl(raw, cipher->key, cipher->padding, cipher->rounds);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
static VALUE
|
|
554
|
+
xxtea_inspect(VALUE self)
|
|
555
|
+
{
|
|
556
|
+
xxtea_cipher_t *cipher = xxtea_get(self);
|
|
557
|
+
return rb_sprintf("#<%s:%p padding=%s rounds=%u>",
|
|
558
|
+
rb_obj_classname(self), (void *)self,
|
|
559
|
+
cipher->padding ? "true" : "false", cipher->rounds);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
void
|
|
563
|
+
Init_xxtea(void)
|
|
564
|
+
{
|
|
565
|
+
rb_ext_ractor_safe(true);
|
|
566
|
+
|
|
567
|
+
id_padding = rb_intern("padding");
|
|
568
|
+
id_rounds = rb_intern("rounds");
|
|
569
|
+
|
|
570
|
+
cXXTEA = rb_define_class("XXTEA", rb_cObject);
|
|
571
|
+
rb_define_alloc_func(cXXTEA, xxtea_alloc);
|
|
572
|
+
|
|
573
|
+
rb_define_singleton_method(cXXTEA, "encrypt", xxtea_s_encrypt, -1);
|
|
574
|
+
rb_define_singleton_method(cXXTEA, "decrypt", xxtea_s_decrypt, -1);
|
|
575
|
+
rb_define_singleton_method(cXXTEA, "encrypt_hex", xxtea_s_encrypt_hex, -1);
|
|
576
|
+
rb_define_singleton_method(cXXTEA, "decrypt_hex", xxtea_s_decrypt_hex, -1);
|
|
577
|
+
|
|
578
|
+
rb_define_method(cXXTEA, "initialize", xxtea_initialize, -1);
|
|
579
|
+
rb_define_method(cXXTEA, "encrypt", xxtea_encrypt, 1);
|
|
580
|
+
rb_define_method(cXXTEA, "decrypt", xxtea_decrypt, 1);
|
|
581
|
+
rb_define_method(cXXTEA, "encrypt_hex", xxtea_encrypt_hex, 1);
|
|
582
|
+
rb_define_method(cXXTEA, "decrypt_hex", xxtea_decrypt_hex, 1);
|
|
583
|
+
rb_define_method(cXXTEA, "inspect", xxtea_inspect, 0);
|
|
229
584
|
}
|
data/lib/xxtea/version.rb
CHANGED
data/lib/xxtea.rb
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
require 'xxtea/xxtea'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
4
|
+
version: 1.0.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:
|
|
11
|
+
date: 2026-08-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
|
-
description:
|
|
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
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
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
|
-
-
|
|
29
|
-
homepage: http://github.com/ifduyue/xxtea
|
|
31
|
+
homepage: https://github.com/ifduyue/ruby-xxtea
|
|
30
32
|
licenses:
|
|
31
|
-
- BSD
|
|
32
|
-
metadata:
|
|
33
|
-
|
|
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:
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
signing_key:
|
|
55
|
+
rubygems_version: 3.5.22
|
|
56
|
+
signing_key:
|
|
51
57
|
specification_version: 4
|
|
52
|
-
summary: Ruby
|
|
58
|
+
summary: XXTEA block cipher as a Ruby C extension
|
|
53
59
|
test_files: []
|
data/.gitignore
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
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
|