twofish 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|