twofish 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.
- data/LICENSE +27 -0
- data/README.rdoc +130 -0
- data/Rakefile +35 -0
- data/lib/twofish.rb +1177 -0
- data/test/test_twofish.rb +290 -0
- metadata +61 -0
@@ -0,0 +1,290 @@
|
|
1
|
+
|
2
|
+
require 'test/unit'
|
3
|
+
require 'twofish'
|
4
|
+
|
5
|
+
# Define some useful constants and test some basic properties.
|
6
|
+
class TestBasics < Test::Unit::TestCase
|
7
|
+
|
8
|
+
NULL_KEY_16_BYTES = ("\0" * 16).freeze
|
9
|
+
NULL_KEY_24_BYTES = ("\0" * 24).freeze
|
10
|
+
NULL_KEY_32_BYTES = ("\0" * 32).freeze
|
11
|
+
NULL_BLOCK = ("\0" * Twofish::BLOCK_SIZE).freeze
|
12
|
+
BLOCK_SIZE = Twofish::BLOCK_SIZE
|
13
|
+
|
14
|
+
def test_16_byte_key_size
|
15
|
+
tf = Twofish.new(NULL_KEY_16_BYTES)
|
16
|
+
assert_equal(tf.key_size, 16)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_24_byte_key_size
|
20
|
+
tf = Twofish.new(NULL_KEY_24_BYTES)
|
21
|
+
assert_equal(tf.key_size, 24)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_32_byte_key_size
|
25
|
+
tf = Twofish.new(NULL_KEY_32_BYTES)
|
26
|
+
assert_equal(tf.key_size, 32)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_invalid_key_size
|
30
|
+
assert_raise ArgumentError do
|
31
|
+
Twofish.new('short key')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_block_size
|
36
|
+
assert(16, BLOCK_SIZE)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_default_mode
|
40
|
+
assert_equal(Mode::DEFAULT, Mode::ECB)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# Test the encryption vectors as given in the specification
|
46
|
+
# using block encryption (Electronic Code Book mode).
|
47
|
+
# On the way we check that each encrypted block successfully
|
48
|
+
# decrypts to the given plaintext.
|
49
|
+
class TestEcbEncryption < TestBasics
|
50
|
+
|
51
|
+
def test_16_byte_key_encryption
|
52
|
+
assert_equal(
|
53
|
+
pack_bytes('5d9d4eeffa9151575524f115815a12e0'),
|
54
|
+
repeated_block_encrypt(NULL_KEY_16_BYTES, NULL_BLOCK, 49)
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_24_byte_key_encryption
|
59
|
+
assert_equal(
|
60
|
+
pack_bytes('e75449212beef9f4a390bd860a640941'),
|
61
|
+
repeated_block_encrypt(NULL_KEY_24_BYTES, NULL_BLOCK, 49)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_32_byte_key_encryption
|
66
|
+
assert_equal(
|
67
|
+
pack_bytes('37fe26ff1cf66175f5ddf4c33b97a205'),
|
68
|
+
repeated_block_encrypt(NULL_KEY_32_BYTES, NULL_BLOCK, 49)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_padding_exception
|
73
|
+
plaintext = 'short' # < BLOCKSIZE == 16 bytes
|
74
|
+
key = pack_bytes('37fe26ff1cf66175f5ddf4c33b97a205')
|
75
|
+
tf = Twofish.new(key)
|
76
|
+
assert_raise ArgumentError do
|
77
|
+
ciphertext = tf.encrypt(plaintext)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_null_in_plaintext
|
82
|
+
plaintext = "xxxxxxx\0\0yyyyyyy"
|
83
|
+
key = pack_bytes('37fe26ff1cf66175f5ddf4c33b97a205')
|
84
|
+
tf = Twofish.new(key)
|
85
|
+
ciphertext = tf.encrypt(plaintext)
|
86
|
+
assert_equal(plaintext, tf.decrypt(ciphertext))
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Convert ASCII hex representation into binary.
|
92
|
+
def pack_bytes(byte_string)
|
93
|
+
[byte_string].pack('H*')
|
94
|
+
end
|
95
|
+
|
96
|
+
# Repeatedly encrypt the given plain text n times
|
97
|
+
# with the same key.
|
98
|
+
def repeated_block_encrypt(key, plain, iterations)
|
99
|
+
key_length = key.length
|
100
|
+
iterations.times do
|
101
|
+
tf = Twofish.new(key)
|
102
|
+
cipher = tf.encrypt(plain)
|
103
|
+
assert_equal(plain, tf.decrypt(cipher))
|
104
|
+
key = (plain + key)[0, key_length]
|
105
|
+
plain = cipher
|
106
|
+
end
|
107
|
+
plain
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
# Test the Cipher Block Chaining mode.
|
113
|
+
class TestCbcEncryption < TestBasics
|
114
|
+
|
115
|
+
# 123456781234567812345678123456781234567812
|
116
|
+
#LONG_PLAINTEXT = 'this message is longer than the block size'
|
117
|
+
#LONG_CIPHERTEXT = ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'].pack('H*')
|
118
|
+
#KEY = ['5d9d4eeffa9151575524f115815a12e0'].pack('H*')
|
119
|
+
#IV = ['e75449212beef9f4a390bd860a640941'].pack('H*')
|
120
|
+
LONG_PLAINTEXT = ("\0"*32).freeze
|
121
|
+
LONG_CIPHERTEXT = ['9f589f5cf6122c32b6bfec2f2ae8c35ad491db16e7b1c39e86cb086b789f5419'].pack('H*').freeze
|
122
|
+
|
123
|
+
def test_encryption_decryption_random_iv
|
124
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc)
|
125
|
+
ciphertext = tf.encrypt(LONG_PLAINTEXT)
|
126
|
+
iv = tf.iv
|
127
|
+
tf2 = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc, :iv => iv)
|
128
|
+
assert_equal(LONG_PLAINTEXT, tf2.decrypt(ciphertext))
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_encryption_given_null_iv
|
132
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc, :iv => NULL_BLOCK)
|
133
|
+
ciphertext = tf.encrypt(LONG_PLAINTEXT)
|
134
|
+
assert_equal(LONG_PLAINTEXT.size, ciphertext.size)
|
135
|
+
assert_equal(LONG_CIPHERTEXT, ciphertext)
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_decryption_given_null_iv
|
139
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc, :iv => NULL_BLOCK)
|
140
|
+
assert_equal(LONG_PLAINTEXT, tf.decrypt(LONG_CIPHERTEXT))
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_encryption_decryption_incomplete_block
|
144
|
+
tf1 = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc, :padding => :zero_byte)
|
145
|
+
plaintext = 'abcdefghijklmnopqrst'
|
146
|
+
ciphertext = tf1.encrypt(plaintext)
|
147
|
+
iv = tf1.iv
|
148
|
+
tf2 = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc, :iv => iv, :padding => :zero_byte)
|
149
|
+
assert_equal(plaintext, tf2.decrypt(ciphertext))
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
class TestInitializationVector < TestBasics
|
155
|
+
|
156
|
+
def test_nil_iv_for_ecb_mode
|
157
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => :ecb)
|
158
|
+
assert_equal(nil, tf.iv)
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_cannot_assign_iv_ecb_mode
|
162
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => :ecb)
|
163
|
+
assert_raise ArgumentError do
|
164
|
+
tf.iv = '1234567812345678'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_assign_bad_iv
|
169
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc)
|
170
|
+
assert_raise ArgumentError do
|
171
|
+
tf.iv = '1234'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_generated_length
|
176
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc)
|
177
|
+
assert_equal(BLOCK_SIZE, tf.iv.length)
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_generated_not_srand
|
181
|
+
tf1 = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc)
|
182
|
+
tf2 = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc)
|
183
|
+
assert_not_equal(tf1.iv, tf2.iv)
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
# Test the available encryption modes.
|
189
|
+
class TestModes < TestBasics
|
190
|
+
|
191
|
+
def test_default_mode
|
192
|
+
tf = Twofish.new(NULL_KEY_16_BYTES)
|
193
|
+
assert_equal(:ecb, tf.mode)
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_unknown_mode
|
197
|
+
assert_raise ArgumentError do
|
198
|
+
tf = Twofish.new(NULL_KEY_16_BYTES)
|
199
|
+
tf.mode = :unknown
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_unknown_mode_constructor
|
204
|
+
assert_raise ArgumentError do
|
205
|
+
Twofish.new(NULL_KEY_16_BYTES, :mode => :unknown)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_cbc_mode_constructor_string
|
210
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => 'cbc')
|
211
|
+
assert_equal(:cbc, tf.mode)
|
212
|
+
end
|
213
|
+
|
214
|
+
def test_cbc_mode_constructor_symbol
|
215
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc)
|
216
|
+
assert_equal(:cbc, tf.mode)
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_ecb_mode
|
220
|
+
tf = Twofish.new(NULL_KEY_16_BYTES)
|
221
|
+
tf.mode = :ecb
|
222
|
+
assert_equal(:ecb, tf.mode)
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_cbc_mode
|
226
|
+
tf = Twofish.new(NULL_KEY_16_BYTES)
|
227
|
+
tf.mode = :cbc
|
228
|
+
assert_equal(:cbc, tf.mode)
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_symbolize_mode
|
232
|
+
tf = Twofish.new(NULL_KEY_16_BYTES)
|
233
|
+
tf.mode = 'ecb'
|
234
|
+
assert_equal(:ecb, tf.mode)
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
class TestPadding < TestBasics
|
240
|
+
|
241
|
+
TO_PAD = 'abcdef'.freeze
|
242
|
+
|
243
|
+
def test_cipher_zero_byte_padding
|
244
|
+
tf = Twofish.new(NULL_KEY_16_BYTES)
|
245
|
+
tf.padding = :zero_byte
|
246
|
+
assert_equal(:zero_byte, tf.padding)
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_cipher_zero_byte_padding_constructor
|
250
|
+
tf = Twofish.new(NULL_KEY_16_BYTES, :padding => :zero_byte)
|
251
|
+
assert_equal(:zero_byte, tf.padding)
|
252
|
+
end
|
253
|
+
|
254
|
+
def test_cipher_unknown_padding
|
255
|
+
tf = Twofish.new(NULL_KEY_16_BYTES)
|
256
|
+
assert_raise ArgumentError do
|
257
|
+
tf.padding = :unknown
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_cipher_unknown_padding_constructor
|
262
|
+
assert_raise ArgumentError do
|
263
|
+
Twofish.new(NULL_KEY_16_BYTES, :padding => :unknown)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_symbolize_padding
|
268
|
+
assert_equal(:zero_byte, Padding::validate('zero_byte'))
|
269
|
+
end
|
270
|
+
|
271
|
+
def test_pad_none
|
272
|
+
assert_raise ArgumentError do
|
273
|
+
Padding::pad(TO_PAD, BLOCK_SIZE, :none)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_unpad_none
|
278
|
+
assert_equal(TO_PAD+"\0"*10, Padding::unpad(TO_PAD+"\0"*10, BLOCK_SIZE, :none))
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_pad_zero_byte
|
282
|
+
assert_equal(TO_PAD+"\0"*10, Padding::pad(TO_PAD, BLOCK_SIZE, :zero_byte))
|
283
|
+
end
|
284
|
+
|
285
|
+
def test_unpad_zero_byte
|
286
|
+
assert_equal(TO_PAD, Padding::unpad(TO_PAD+"\0"*10, BLOCK_SIZE, :zero_byte))
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: twofish
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Carpenter
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-11-26 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Twofish symmetric cipher in pure Ruby with ECB and CBC cipher modes derived from an original Perl implementation by Guido Flohr
|
17
|
+
email: mcarpenter@free.fr
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- Rakefile
|
25
|
+
- README.rdoc
|
26
|
+
files:
|
27
|
+
- lib/twofish.rb
|
28
|
+
- test/test_twofish.rb
|
29
|
+
- LICENSE
|
30
|
+
- Rakefile
|
31
|
+
- README.rdoc
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://mcarpenter.org/projects/twofish
|
34
|
+
licenses:
|
35
|
+
- BSD
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.3.5
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: Twofish symmetric cipher in pure Ruby
|
60
|
+
test_files: []
|
61
|
+
|