twofish 1.0.2 → 1.0.3
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/README.rdoc +11 -5
- data/Rakefile +12 -9
- data/lib/string.rb +16 -0
- data/lib/twofish.rb +553 -623
- data/lib/twofish/mode.rb +33 -0
- data/lib/twofish/padding.rb +75 -0
- data/test/benchmark.rb +17 -0
- data/test/test_twofish.rb +35 -9
- metadata +6 -2
data/lib/twofish/mode.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
class Twofish
|
2
|
+
|
3
|
+
# Encryption modes.
|
4
|
+
#
|
5
|
+
# The only currently implemented modes are ECB (Electronic Code Book)
|
6
|
+
# and CBC (Cipher Block Chaining).
|
7
|
+
module Mode
|
8
|
+
|
9
|
+
# Electronic code book mode.
|
10
|
+
ECB = :ecb
|
11
|
+
|
12
|
+
# Cipher block chaining mode.
|
13
|
+
CBC = :cbc
|
14
|
+
|
15
|
+
# Array of all known modes.
|
16
|
+
ALL = [ CBC, ECB ]
|
17
|
+
|
18
|
+
# Default mode (ECB).
|
19
|
+
DEFAULT = ECB
|
20
|
+
|
21
|
+
# Takes a string or symbol and returns the lowercased
|
22
|
+
# symbol representation if this is a recognized mode.
|
23
|
+
# Otherwise, throws ArgumentError.
|
24
|
+
def self.validate(mode)
|
25
|
+
mode_sym = mode.nil? ? DEFAULT : mode.to_s.downcase.to_sym
|
26
|
+
raise ArgumentError, "unknown cipher mode #{mode.inspect}" unless ALL.include? mode_sym
|
27
|
+
mode_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class Twofish
|
2
|
+
|
3
|
+
# Implements padding modes to make plaintext into a complete
|
4
|
+
# number of blocks before encryption and to remove that padding
|
5
|
+
# after successful decryption.
|
6
|
+
#
|
7
|
+
# The only implemented padding schemes are :none and
|
8
|
+
# :zero_byte. Note that zero byte padding is potentially
|
9
|
+
# dangerous because if the plaintext terminates in
|
10
|
+
# zero bytes then these will be erroneously removed by #unpad.
|
11
|
+
# A more sensible padding scheme should be used in this case.
|
12
|
+
module Padding
|
13
|
+
|
14
|
+
# Use no padding.
|
15
|
+
NONE = :none
|
16
|
+
|
17
|
+
# Use zero byte padding.
|
18
|
+
ZERO_BYTE = :zero_byte
|
19
|
+
|
20
|
+
# Array of all known paddings.
|
21
|
+
ALL = [ NONE, ZERO_BYTE ]
|
22
|
+
|
23
|
+
# Default padding (none).
|
24
|
+
DEFAULT = NONE
|
25
|
+
|
26
|
+
# Takes a string or symbol and returns the lowercased
|
27
|
+
# symbol representation if this is a recognized padding scheme.
|
28
|
+
# Otherwise, throws ArgumentError.
|
29
|
+
def self.validate(scheme)
|
30
|
+
scheme_sym = scheme.nil? ? DEFAULT : scheme.to_s.downcase.to_sym
|
31
|
+
raise ArgumentError, "unknown padding scheme #{scheme.inspect}" unless ALL.include? scheme_sym
|
32
|
+
scheme_sym
|
33
|
+
end
|
34
|
+
|
35
|
+
# Pad the given plaintext to a complete number of blocks,
|
36
|
+
# returning a new string. See #pad!.
|
37
|
+
def self.pad(plaintext, block_size, scheme=DEFAULT)
|
38
|
+
self.pad!(plaintext.dup, block_size, scheme)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Pad the given plaintext to a complete number of blocks,
|
42
|
+
# returning a new string. If the padding scheme is :none
|
43
|
+
# and the plaintext is not a whole number of blocks then
|
44
|
+
# ArgumentError is thrown.
|
45
|
+
def self.pad!(plaintext, block_size, scheme=DEFAULT)
|
46
|
+
remainder = plaintext.length % block_size
|
47
|
+
case validate(scheme)
|
48
|
+
when NONE
|
49
|
+
raise ArgumentError, "no padding scheme specified and plaintext length is not a multiple of the block size" unless remainder.zero?
|
50
|
+
plaintext
|
51
|
+
when ZERO_BYTE
|
52
|
+
remainder.zero? ? plaintext : plaintext << "\0" * (block_size - remainder)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Unpad the given plaintext using the given scheme, returning
|
57
|
+
# a new string. See #unpad!.
|
58
|
+
def self.unpad(plaintext, block_size, scheme=DEFAULT)
|
59
|
+
self.unpad!(plaintext.dup, block_size, scheme)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Unpad the given plaintext in place using the given scheme.
|
63
|
+
def self.unpad!(plaintext, block_size, scheme=DEFAULT)
|
64
|
+
case validate(scheme)
|
65
|
+
when NONE
|
66
|
+
plaintext.dup
|
67
|
+
when ZERO_BYTE
|
68
|
+
plaintext.sub(/\000+\Z/, '')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
data/test/benchmark.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
require 'twofish'
|
5
|
+
|
6
|
+
m = 10000
|
7
|
+
n = 100000
|
8
|
+
key = ['5d9d4eeffa9151575524f115815a12e0'].pack('H*')
|
9
|
+
block = ['e75449212beef9f4a390bd860a640941'].pack('H*')
|
10
|
+
tf = Twofish.new(key)
|
11
|
+
|
12
|
+
Benchmark.bm(7) do |x|
|
13
|
+
x.report('new') { m.times do ; Twofish.new(key) ; end }
|
14
|
+
x.report('encrypt') { n.times do ; tf.encrypt(block) ; end }
|
15
|
+
x.report('decrypt') { n.times do ; tf.decrypt(block) ; end }
|
16
|
+
end
|
17
|
+
|
data/test/test_twofish.rb
CHANGED
@@ -37,7 +37,7 @@ class TestBasics < Test::Unit::TestCase
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def test_default_mode
|
40
|
-
assert_equal(Mode::DEFAULT, Mode::ECB)
|
40
|
+
assert_equal(Twofish::Mode::DEFAULT, Twofish::Mode::ECB)
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
@@ -74,7 +74,7 @@ class TestEcbEncryption < TestBasics
|
|
74
74
|
key = pack_bytes('37fe26ff1cf66175f5ddf4c33b97a205')
|
75
75
|
tf = Twofish.new(key)
|
76
76
|
assert_raise ArgumentError do
|
77
|
-
|
77
|
+
tf.encrypt(plaintext)
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
@@ -86,6 +86,29 @@ class TestEcbEncryption < TestBasics
|
|
86
86
|
assert_equal(plaintext, tf.decrypt(ciphertext))
|
87
87
|
end
|
88
88
|
|
89
|
+
def test_utf8_plaintext_invalid_length
|
90
|
+
# this string is 16 chars in length, but 18 bytes
|
91
|
+
plaintext = pack_bytes('72c3a973657276c3a96573') + # 11 bytes, 9 chars
|
92
|
+
'1234567' # 7 bytes, 7 chars
|
93
|
+
plaintext.force_encoding('UTF-8')
|
94
|
+
key = pack_bytes('37fe26ff1cf66175f5ddf4c33b97a205')
|
95
|
+
tf = Twofish.new(key)
|
96
|
+
assert_raise ArgumentError do
|
97
|
+
tf.encrypt(plaintext)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_utf8_plaintext
|
102
|
+
# this string is 16 bytes in length (but 14 chars)
|
103
|
+
plaintext = pack_bytes('72c3a973657276c3a96573') + # 11 bytes, 9 chars
|
104
|
+
'12345' # 5 bytes, 5 chars
|
105
|
+
plaintext.force_encoding('UTF-8')
|
106
|
+
key = pack_bytes('37fe26ff1cf66175f5ddf4c33b97a205')
|
107
|
+
tf = Twofish.new(key)
|
108
|
+
ciphertext = tf.encrypt(plaintext)
|
109
|
+
assert_equal(plaintext.force_encoding('ASCII-8BIT'), tf.decrypt(ciphertext))
|
110
|
+
end
|
111
|
+
|
89
112
|
private
|
90
113
|
|
91
114
|
# Convert ASCII hex representation into binary.
|
@@ -122,10 +145,13 @@ class TestCbcEncryption < TestBasics
|
|
122
145
|
|
123
146
|
def test_encryption_decryption_random_iv
|
124
147
|
tf = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc)
|
125
|
-
|
148
|
+
plaintext = LONG_PLAINTEXT
|
149
|
+
ciphertext = tf.encrypt(plaintext)
|
150
|
+
assert_equal(LONG_PLAINTEXT, plaintext)
|
126
151
|
iv = tf.iv
|
127
152
|
tf2 = Twofish.new(NULL_KEY_16_BYTES, :mode => :cbc, :iv => iv)
|
128
153
|
assert_equal(LONG_PLAINTEXT, tf2.decrypt(ciphertext))
|
154
|
+
assert_equal(LONG_PLAINTEXT, plaintext)
|
129
155
|
end
|
130
156
|
|
131
157
|
def test_encryption_given_null_iv
|
@@ -265,29 +291,29 @@ class TestPadding < TestBasics
|
|
265
291
|
end
|
266
292
|
|
267
293
|
def test_symbolize_padding
|
268
|
-
assert_equal(:zero_byte, Padding::validate('zero_byte'))
|
294
|
+
assert_equal(:zero_byte, Twofish::Padding::validate('zero_byte'))
|
269
295
|
end
|
270
296
|
|
271
297
|
def test_pad_none
|
272
298
|
assert_raise ArgumentError do
|
273
|
-
Padding::pad(TO_PAD, BLOCK_SIZE, :none)
|
299
|
+
Twofish::Padding::pad(TO_PAD, BLOCK_SIZE, :none)
|
274
300
|
end
|
275
301
|
end
|
276
302
|
|
277
303
|
def test_unpad_none
|
278
|
-
assert_equal(TO_PAD+"\0"*10, Padding::unpad(TO_PAD+"\0"*10, BLOCK_SIZE, :none))
|
304
|
+
assert_equal(TO_PAD+"\0"*10, Twofish::Padding::unpad(TO_PAD+"\0"*10, BLOCK_SIZE, :none))
|
279
305
|
end
|
280
306
|
|
281
307
|
def test_pad_zero_byte
|
282
|
-
assert_equal(TO_PAD+"\0"*10, Padding::pad(TO_PAD, BLOCK_SIZE, :zero_byte))
|
308
|
+
assert_equal(TO_PAD+"\0"*10, Twofish::Padding::pad(TO_PAD, BLOCK_SIZE, :zero_byte))
|
283
309
|
end
|
284
310
|
|
285
311
|
def test_unpad_zero_byte
|
286
|
-
assert_equal(TO_PAD, Padding::unpad(TO_PAD+"\0"*10, BLOCK_SIZE, :zero_byte))
|
312
|
+
assert_equal(TO_PAD, Twofish::Padding::unpad(TO_PAD+"\0"*10, BLOCK_SIZE, :zero_byte))
|
287
313
|
end
|
288
314
|
|
289
315
|
def test_pad_block_size_zero_byte
|
290
316
|
to_pad = TO_PAD * BLOCK_SIZE
|
291
|
-
assert_equal(to_pad, Padding::pad(to_pad, BLOCK_SIZE, :zero_byte))
|
317
|
+
assert_equal(to_pad, Twofish::Padding::pad(to_pad, BLOCK_SIZE, :zero_byte))
|
292
318
|
end
|
293
319
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twofish
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-03-08 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Twofish symmetric cipher in pure Ruby with ECB and CBC cipher modes derived
|
15
15
|
from an original Perl implementation by Guido Flohr
|
@@ -22,6 +22,10 @@ extra_rdoc_files:
|
|
22
22
|
- README.rdoc
|
23
23
|
files:
|
24
24
|
- lib/twofish.rb
|
25
|
+
- lib/twofish/mode.rb
|
26
|
+
- lib/twofish/padding.rb
|
27
|
+
- lib/string.rb
|
28
|
+
- test/benchmark.rb
|
25
29
|
- test/test_twofish.rb
|
26
30
|
- LICENSE
|
27
31
|
- Rakefile
|