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 +5 -5
- data/CHANGELOG.md +22 -0
- data/LICENSE +22 -0
- data/README.md +230 -0
- data/ext/xxtea/extconf.rb +3 -3
- data/ext/xxtea/xxtea.c +562 -128
- 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: 52dd181dad48d0089d4f84d4b715b0c7231ea004c35928e4ec5c154b5c19a569
|
|
4
|
+
data.tar.gz: b60c3915ae8573e6d234cd6e9315f48ccb893c535d78ed3defd5f61428b4c825
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a49001c4bb5c6e53a495bf3a96ecc61d084bb822a77dfda6370dd432b5f50ae54321ffbec3736cc1773fc68059465cb42aeedb3db2511b54c86f44ba390976f0
|
|
7
|
+
data.tar.gz: a9671360c531507c76ddec41561817312e4f0c961ef1bc6865b6e2ae5ff1ac39eab35d990ca1cab69317992e815e34277e039b60cab7b6b692aa20c550f51731
|
data/CHANGELOG.md
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
|
+
[](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
|
+
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
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 <
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
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
|
|
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,
|
|
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
|
|
124
|
+
static void
|
|
125
|
+
bytes2longs(const char *in, long inlen, uint32_t *out, int padding)
|
|
57
126
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
out[i
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
/*
|
|
79
|
-
*
|
|
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
|
-
|
|
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
|
|
193
|
+
static long
|
|
194
|
+
longs2bytes(const uint32_t *in, long inlen, char *out, int padding)
|
|
85
195
|
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
93
|
-
s[4 * i
|
|
94
|
-
s[4 * i +
|
|
95
|
-
s[4 * i +
|
|
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
|
-
|
|
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[
|
|
103
|
-
|
|
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[
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
282
|
+
static unsigned int
|
|
283
|
+
parse_rounds(VALUE obj)
|
|
113
284
|
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
335
|
+
*padding = 4;
|
|
336
|
+
*rounds = 0;
|
|
337
|
+
if (NIL_P(opts)) {
|
|
338
|
+
return;
|
|
131
339
|
}
|
|
132
340
|
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
btea(d, alen, k);
|
|
370
|
+
StringValue(data);
|
|
371
|
+
data_len = RSTRING_LEN(data);
|
|
144
372
|
|
|
145
|
-
|
|
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 (
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
411
|
+
static VALUE
|
|
412
|
+
decrypt_impl(VALUE data, const char key[16], int padding, unsigned int rounds)
|
|
159
413
|
{
|
|
160
|
-
|
|
161
|
-
uint32_t *d, k[4];
|
|
414
|
+
crypt_job job;
|
|
162
415
|
VALUE retval;
|
|
163
|
-
|
|
416
|
+
long data_len, alen;
|
|
164
417
|
|
|
165
|
-
|
|
166
|
-
|
|
418
|
+
StringValue(data);
|
|
419
|
+
data_len = RSTRING_LEN(data);
|
|
167
420
|
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
180
|
-
if (
|
|
181
|
-
rb_raise(
|
|
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,
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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
|
|
635
|
+
void
|
|
636
|
+
Init_xxtea(void)
|
|
225
637
|
{
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
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:
|
|
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:
|
|
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
|