crypt 1.1.3 → 2.2.1
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 +7 -0
- data/CREDITS +13 -0
- data/LICENSE +6 -0
- data/README +10 -0
- data/{crypt → lib/crypt}/blowfish-tables.rb +59 -3
- data/{crypt → lib/crypt}/blowfish.rb +27 -26
- data/{crypt → lib/crypt}/cbc.rb +33 -35
- data/{crypt → lib/crypt}/gost.rb +3 -4
- data/{crypt → lib/crypt}/idea.rb +25 -68
- data/{crypt → lib/crypt}/noise.rb +12 -12
- data/{crypt → lib/crypt}/rijndael-tables.rb +2 -3
- data/{crypt → lib/crypt}/rijndael.rb +57 -53
- data/lib/crypt/stringxor.rb +17 -0
- data/specs/blowfish.rb +80 -0
- data/specs/gost.rb +48 -0
- data/specs/idea.rb +156 -0
- data/specs/rijndael.rb +58 -0
- metadata +57 -42
- data/crypt/stringxor.rb +0 -27
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
require 'crypt/cbc'
|
2
|
+
# rijndael.rb Richard Kernahan
|
2
3
|
|
3
4
|
# Adapted from the reference C implementation:
|
4
5
|
# rijndael-alg-ref.c v2.2 March 2002
|
@@ -6,53 +7,51 @@
|
|
6
7
|
# authors: Paulo Barreto and Vincent Rijmen
|
7
8
|
# This code is placed in the public domain.
|
8
9
|
|
10
|
+
require 'crypt/rijndael-tables'
|
11
|
+
|
9
12
|
module Crypt
|
10
13
|
class Rijndael
|
11
14
|
|
12
|
-
require 'crypt/cbc'
|
13
15
|
include Crypt::CBC
|
14
|
-
|
15
|
-
require 'crypt/rijndael-tables'
|
16
16
|
include Crypt::RijndaelTables
|
17
|
-
|
18
|
-
|
17
|
+
|
19
18
|
def initialize(userKey, keyBits = 256, blockBits = 128)
|
20
19
|
case keyBits
|
21
|
-
when 128
|
20
|
+
when 128
|
22
21
|
@keyWords = 4
|
23
|
-
when 192
|
22
|
+
when 192
|
24
23
|
@keyWords = 6
|
25
24
|
when 256
|
26
25
|
@keyWords = 8
|
27
26
|
else raise "The key must be 128, 192, or 256 bits long."
|
28
27
|
end
|
29
|
-
|
28
|
+
|
30
29
|
case (keyBits >= blockBits) ? keyBits : blockBits
|
31
|
-
when 128
|
30
|
+
when 128
|
32
31
|
@rounds = 10
|
33
|
-
when 192
|
32
|
+
when 192
|
34
33
|
@rounds = 12
|
35
34
|
when 256
|
36
35
|
@rounds = 14
|
37
36
|
else raise "The key and block sizes must be 128, 192, or 256 bits long."
|
38
37
|
end
|
39
|
-
|
38
|
+
|
40
39
|
case blockBits
|
41
|
-
when 128
|
40
|
+
when 128
|
42
41
|
@blockSize = 16
|
43
42
|
@blockWords = 4
|
44
43
|
@shiftIndex = 0
|
45
|
-
when 192
|
44
|
+
when 192
|
46
45
|
@blockSize = 24
|
47
46
|
@blockWords = 6
|
48
47
|
@shiftIndex = 1
|
49
|
-
when 256
|
48
|
+
when 256
|
50
49
|
@blockSize = 32
|
51
50
|
@blockWords = 8
|
52
51
|
@shiftIndex = 2
|
53
52
|
else raise "The block size must be 128, 192, or 256 bits long."
|
54
53
|
end
|
55
|
-
|
54
|
+
|
56
55
|
uk = userKey.unpack('C'*userKey.length)
|
57
56
|
maxUsefulSizeOfUserKey = (keyBits/8)
|
58
57
|
uk = uk[0..maxUsefulSizeOfUserKey-1] # truncate
|
@@ -68,22 +67,22 @@ class Rijndael
|
|
68
67
|
@roundKeys = generate_key_schedule(@key, keyBits, blockBits)
|
69
68
|
end
|
70
69
|
|
71
|
-
|
70
|
+
|
72
71
|
def block_size
|
73
72
|
return(@blockSize) # needed for CBC
|
74
73
|
end
|
75
|
-
|
76
|
-
|
74
|
+
|
75
|
+
|
77
76
|
def mul(a, b)
|
78
77
|
if ((a ==0) | (b == 0))
|
79
|
-
result = 0
|
78
|
+
result = 0
|
80
79
|
else
|
81
80
|
result = AlogTable[(LogTable[a] + LogTable[b]) % 255]
|
82
81
|
end
|
83
82
|
return(result)
|
84
83
|
end
|
85
|
-
|
86
|
-
|
84
|
+
|
85
|
+
|
87
86
|
def add_round_key(blockArray, roundKey)
|
88
87
|
0.upto(3) { |i|
|
89
88
|
0.upto(@blockWords) { |j|
|
@@ -92,8 +91,8 @@ class Rijndael
|
|
92
91
|
}
|
93
92
|
return(blockArray)
|
94
93
|
end
|
95
|
-
|
96
|
-
|
94
|
+
|
95
|
+
|
97
96
|
def shift_rows(blockArray, direction)
|
98
97
|
tmp = []
|
99
98
|
1.upto(3) { |i| # row zero remains unchanged
|
@@ -106,8 +105,8 @@ class Rijndael
|
|
106
105
|
}
|
107
106
|
return(blockArray)
|
108
107
|
end
|
109
|
-
|
110
|
-
|
108
|
+
|
109
|
+
|
111
110
|
def substitution(blockArray, sBox)
|
112
111
|
# replace every byte of the input with the byte at that position in the S-box
|
113
112
|
0.upto(3) { |i|
|
@@ -117,8 +116,8 @@ class Rijndael
|
|
117
116
|
}
|
118
117
|
return(blockArray)
|
119
118
|
end
|
120
|
-
|
121
|
-
|
119
|
+
|
120
|
+
|
122
121
|
def mix_columns(blockArray)
|
123
122
|
mixed = [[], [], [], []]
|
124
123
|
0.upto(@blockWords-1) { |j|
|
@@ -131,22 +130,22 @@ class Rijndael
|
|
131
130
|
}
|
132
131
|
return(mixed)
|
133
132
|
end
|
134
|
-
|
135
|
-
|
133
|
+
|
134
|
+
|
136
135
|
def inverse_mix_columns(blockArray)
|
137
136
|
unmixed = [[], [], [], []]
|
138
137
|
0.upto(@blockWords-1) { |j|
|
139
138
|
0.upto(3) { |i|
|
140
139
|
unmixed[i][j] = mul(0xe, blockArray[i][j]) ^
|
141
|
-
mul(0xb, blockArray[(i + 1) % 4][j]) ^
|
140
|
+
mul(0xb, blockArray[(i + 1) % 4][j]) ^
|
142
141
|
mul(0xd, blockArray[(i + 2) % 4][j]) ^
|
143
142
|
mul(0x9, blockArray[(i + 3) % 4][j])
|
144
143
|
}
|
145
144
|
}
|
146
145
|
return(unmixed)
|
147
146
|
end
|
148
|
-
|
149
|
-
|
147
|
+
|
148
|
+
|
150
149
|
def generate_key_schedule(k, keyBits, blockBits)
|
151
150
|
tk = k[0..3][0..@keyWords-1] # using slice to get a copy instead of a reference
|
152
151
|
keySched = []
|
@@ -162,7 +161,7 @@ class Rijndael
|
|
162
161
|
end
|
163
162
|
# while not enough round key material collected, calculate new values
|
164
163
|
rconIndex = 0
|
165
|
-
while (t < (@rounds+1)*@blockWords)
|
164
|
+
while (t < (@rounds+1)*@blockWords)
|
166
165
|
0.upto(3) { |i|
|
167
166
|
tk[i][0] ^= S[tk[(i + 1) % 4][@keyWords - 1]]
|
168
167
|
}
|
@@ -184,8 +183,8 @@ class Rijndael
|
|
184
183
|
tk[i][@keyWords/2] ^= S[tk[i][@keyWords/2 - 1]]
|
185
184
|
}
|
186
185
|
(@keyWords/2 + 1).upto(@keyWords - 1) { |j|
|
187
|
-
0.upto(3) { |i|
|
188
|
-
tk[i][j] ^= tk[i][j-1]
|
186
|
+
0.upto(3) { |i|
|
187
|
+
tk[i][j] ^= tk[i][j-1]
|
189
188
|
}
|
190
189
|
}
|
191
190
|
end
|
@@ -200,8 +199,8 @@ class Rijndael
|
|
200
199
|
end
|
201
200
|
return(keySched)
|
202
201
|
end
|
203
|
-
|
204
|
-
|
202
|
+
|
203
|
+
|
205
204
|
def encrypt_byte_array(blockArray)
|
206
205
|
blockArray = add_round_key(blockArray, @roundKeys[0])
|
207
206
|
1.upto(@rounds - 1) { |round|
|
@@ -216,54 +215,59 @@ class Rijndael
|
|
216
215
|
blockArray = add_round_key(blockArray, @roundKeys[@rounds])
|
217
216
|
return(blockArray)
|
218
217
|
end
|
219
|
-
|
220
|
-
|
218
|
+
|
219
|
+
|
221
220
|
def encrypt_block(block)
|
221
|
+
block = block.force_encoding("ASCII-8BIT") if block.is_a?(String) # affordance
|
222
222
|
raise "block must be #{@blockSize} bytes long" if (block.length() != @blockSize)
|
223
223
|
blockArray = [[], [], [], []]
|
224
224
|
0.upto(@blockSize - 1) { |pos|
|
225
|
-
|
225
|
+
b = block[pos]
|
226
|
+
b = b.ord unless b.is_a?(Fixnum)
|
227
|
+
blockArray[pos % 4][pos / 4] = b
|
226
228
|
}
|
227
229
|
encryptedBlock = encrypt_byte_array(blockArray)
|
228
|
-
encrypted = ""
|
230
|
+
encrypted = "".force_encoding("ASCII-8BIT") # stop ruby 2 using Unicode
|
229
231
|
0.upto(@blockSize - 1) { |pos|
|
230
232
|
encrypted << encryptedBlock[pos % 4][pos / 4]
|
231
233
|
}
|
232
234
|
return(encrypted)
|
233
235
|
end
|
234
|
-
|
235
|
-
|
236
|
+
|
237
|
+
|
236
238
|
def decrypt_byte_array(blockArray)
|
237
239
|
# first special round without inverse_mix_columns
|
238
240
|
# add_round_key is an involution - applying it a second time returns the original result
|
239
|
-
blockArray = add_round_key(blockArray, @roundKeys[@rounds])
|
241
|
+
blockArray = add_round_key(blockArray, @roundKeys[@rounds])
|
240
242
|
blockArray = substitution(blockArray,Si) # using inverse S-box
|
241
243
|
blockArray = shift_rows(blockArray,1)
|
242
244
|
(@rounds-1).downto(1) { |round|
|
243
245
|
blockArray = add_round_key(blockArray, @roundKeys[round])
|
244
246
|
blockArray = inverse_mix_columns(blockArray)
|
245
|
-
blockArray = substitution(blockArray, Si)
|
247
|
+
blockArray = substitution(blockArray, Si)
|
246
248
|
blockArray = shift_rows(blockArray, 1)
|
247
249
|
}
|
248
250
|
blockArray = add_round_key(blockArray, @roundKeys[0])
|
249
251
|
return(blockArray)
|
250
252
|
end
|
251
|
-
|
252
|
-
|
253
|
+
|
254
|
+
|
253
255
|
def decrypt_block(block)
|
254
256
|
raise "block must be #{@blockSize} bytes long" if (block.length() != @blockSize)
|
255
257
|
blockArray = [[], [], [], []]
|
256
258
|
0.upto(@blockSize - 1) { |pos|
|
257
|
-
|
259
|
+
b = block[pos]
|
260
|
+
b = b.ord() unless b.is_a?(Fixnum) # make sure we have a byte, not a single-char string
|
261
|
+
blockArray[pos % 4][pos / 4] = b
|
258
262
|
}
|
259
263
|
decryptedBlock = decrypt_byte_array(blockArray)
|
260
|
-
decrypted = ""
|
264
|
+
decrypted = "".force_encoding("ASCII-8BIT") # stop ruby 2 using Unicode
|
261
265
|
0.upto(@blockSize - 1) { |pos|
|
262
266
|
decrypted << decryptedBlock[pos % 4][pos / 4]
|
263
267
|
}
|
264
268
|
return(decrypted)
|
265
269
|
end
|
266
|
-
|
267
|
-
|
270
|
+
|
271
|
+
|
272
|
+
end
|
268
273
|
end
|
269
|
-
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def ^(aString)
|
4
|
+
a = self.b.unpack('C'*(self.length))
|
5
|
+
b = aString.b.unpack('C'*(aString.length))
|
6
|
+
if (b.length < a.length)
|
7
|
+
(a.length - b.length).times { b << 0 }
|
8
|
+
end
|
9
|
+
xor = "".b()
|
10
|
+
0.upto(a.length-1) { |pos|
|
11
|
+
x = a[pos] ^ b[pos]
|
12
|
+
xor << x.chr()
|
13
|
+
}
|
14
|
+
return(xor)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/specs/blowfish.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'crypt/blowfish'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe "Crypt::Blowfish" do
|
5
|
+
|
6
|
+
it "has a blocksize of 8" do
|
7
|
+
bf = Crypt::Blowfish.new("Who is John Galt?") # Schneier's test key
|
8
|
+
expect(bf.block_size).to eql(8)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "requires a key of 1-56 bytes" do
|
12
|
+
expect {
|
13
|
+
b0 = Crypt::Blowfish.new("")
|
14
|
+
}.to raise_error(RuntimeError)
|
15
|
+
expect {
|
16
|
+
b1 = Crypt::Blowfish.new("1")
|
17
|
+
}.not_to raise_error()
|
18
|
+
expect {
|
19
|
+
b56 = Crypt::Blowfish.new("1"*56)
|
20
|
+
}.not_to raise_error()
|
21
|
+
expect {
|
22
|
+
b57 = Crypt::Blowfish.new("1"*57)
|
23
|
+
}.to raise_error(RuntimeError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "encrypts and decrypts 4-byte pairs" do
|
27
|
+
bf = Crypt::Blowfish.new("Who is John Galt?")
|
28
|
+
orig_l, orig_r = [0xfedcba98, 0x76543210]
|
29
|
+
l, r = bf.encrypt_pair(orig_l, orig_r)
|
30
|
+
expect(l).to eql(0xcc91732b)
|
31
|
+
expect(r).to eql(0x8022f684)
|
32
|
+
l, r = bf.decrypt_pair(l, r)
|
33
|
+
expect(l).to eql(orig_l)
|
34
|
+
expect(r).to eql(orig_r)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "encrypts and decrypts 8-byte blocks" do
|
38
|
+
bf = Crypt::Blowfish.new("Who is John Galt?")
|
39
|
+
block = "8 byte\u00cd" # unicode string of 8 bytes
|
40
|
+
encryptedBlock = bf.encrypt_block(block)
|
41
|
+
expect(encryptedBlock).to eql("\xC4G\xD3\xFD7\xF4\x1E\xD0".b())
|
42
|
+
decryptedBlock = bf.decrypt_block(encryptedBlock)
|
43
|
+
expect(decryptedBlock).to eql(block)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "encrypts and decrypts strings" do
|
47
|
+
length = 30 + rand(26)
|
48
|
+
userkey = ""
|
49
|
+
length.times { userkey << rand(256).chr }
|
50
|
+
bf = Crypt::Blowfish.new(userkey)
|
51
|
+
string = "This is a string which is not a multiple of 8 characters long"
|
52
|
+
encryptedString = bf.encrypt_string(string)
|
53
|
+
decryptedString = bf.decrypt_string(encryptedString)
|
54
|
+
expect(decryptedString).to eql(string)
|
55
|
+
secondstring = "This is another string to check repetitive use."
|
56
|
+
encryptedString = bf.encrypt_string(secondstring)
|
57
|
+
decryptedString = bf.decrypt_string(encryptedString)
|
58
|
+
expect(decryptedString).to eql(secondstring)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "encrypts and decrypts a file" do
|
62
|
+
plainText = "This is a multi-line string\nwhich is not a multiple of 8 \ncharacters long."
|
63
|
+
plainFile = File.new('plain.txt', 'wb+')
|
64
|
+
plainFile.puts(plainText)
|
65
|
+
plainFile.close()
|
66
|
+
bf = Crypt::Blowfish.new("Who is John Galt?")
|
67
|
+
bf.encrypt_file('plain.txt', 'crypt.txt')
|
68
|
+
bf.decrypt_file('crypt.txt', 'decrypt.txt')
|
69
|
+
decryptFile = File.new('decrypt.txt', 'rb')
|
70
|
+
decryptText = decryptFile.readlines().join('').chomp()
|
71
|
+
decryptFile.close()
|
72
|
+
expect(decryptText).to eql(plainText)
|
73
|
+
FileUtils.rm('plain.txt')
|
74
|
+
FileUtils.rm('crypt.txt')
|
75
|
+
FileUtils.rm('decrypt.txt')
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
|
data/specs/gost.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'crypt/gost'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe "Crypt::Gost" do
|
5
|
+
|
6
|
+
it "has a blocksize of 8" do
|
7
|
+
gost = Crypt::Gost.new("Whatever happened to Yuri Gagarin?")
|
8
|
+
expect(gost.block_size).to eql(8)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "encrypts and decrypts 8-byte blocks" do
|
12
|
+
gost = Crypt::Gost.new("Whatever happened to Yuri?")
|
13
|
+
block = "norandom"
|
14
|
+
encryptedBlock = gost.encrypt_block(block)
|
15
|
+
expect(encryptedBlock).to eql(".Vy\xFF\x05\e3`".b())
|
16
|
+
decryptedBlock = gost.decrypt_block(encryptedBlock)
|
17
|
+
expect(decryptedBlock).to eql(block)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "encrypts and decrypts strings" do
|
21
|
+
length = 25 + rand(12)
|
22
|
+
userkey = ""
|
23
|
+
length.times { userkey << rand(256).chr }
|
24
|
+
gost = Crypt::Gost.new(userkey)
|
25
|
+
string = "This is a string which is not a multiple of 8 characters long"
|
26
|
+
encryptedString = gost.encrypt_string(string)
|
27
|
+
decryptedString = gost.decrypt_string(encryptedString)
|
28
|
+
expect(decryptedString).to eql(string)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "encrypts and decrypts a file" do
|
32
|
+
plainText = "This is a multi-line string\nwhich is not a multiple of 8 \ncharacters long."
|
33
|
+
plainFile = File.new('plain.txt', 'wb+')
|
34
|
+
plainFile.puts(plainText)
|
35
|
+
plainFile.close()
|
36
|
+
gost = Crypt::Gost.new("Whatever happened to Yuri?")
|
37
|
+
gost.encrypt_file('plain.txt', 'crypt.txt')
|
38
|
+
gost.decrypt_file('crypt.txt', 'decrypt.txt')
|
39
|
+
decryptFile = File.new('decrypt.txt', 'rb')
|
40
|
+
decryptText = decryptFile.readlines().join('').chomp()
|
41
|
+
decryptFile.close()
|
42
|
+
expect(decryptText).to eql(plainText)
|
43
|
+
FileUtils.rm('plain.txt')
|
44
|
+
FileUtils.rm('crypt.txt')
|
45
|
+
FileUtils.rm('decrypt.txt')
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/specs/idea.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'crypt/idea'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe "Crypt::IDEA" do
|
5
|
+
|
6
|
+
it "encrypts and decrypts 4-byte pairs" do
|
7
|
+
enc = Crypt::IDEA.new("Nothing can stop an idea whose time has come", Crypt::IDEA::ENCRYPT)
|
8
|
+
dec = Crypt::IDEA.new("Nothing can stop an idea whose time has come", Crypt::IDEA::DECRYPT)
|
9
|
+
origL = 0x385a41f2
|
10
|
+
origR = 0xe03e5930
|
11
|
+
cl, cr = enc.crypt_pair(origL, origR)
|
12
|
+
dl, dr = dec.crypt_pair(cl, cr)
|
13
|
+
expect(dl).to eql(origL)
|
14
|
+
expect(dr).to eql(origR)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "encrypts and decrypts 8-byte blocks" do
|
18
|
+
enc = Crypt::IDEA.new("Nothing can stop an idea whose time has come", Crypt::IDEA::ENCRYPT)
|
19
|
+
dec = Crypt::IDEA.new("Nothing can stop an idea whose time has come", Crypt::IDEA::DECRYPT)
|
20
|
+
block = "norandom"
|
21
|
+
encryptedBlock = enc.encrypt_block(block)
|
22
|
+
expect(encryptedBlock).to eql("\xC2lzEU\x81\e_".b())
|
23
|
+
decryptedBlock = dec.decrypt_block(encryptedBlock)
|
24
|
+
expect(decryptedBlock).to eql(block)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "encrypts and decrypts strings" do
|
28
|
+
enc = Crypt::IDEA.new("Nothing can stop an idea whose time has come", Crypt::IDEA::ENCRYPT)
|
29
|
+
dec = Crypt::IDEA.new("Nothing can stop an idea whose time has come", Crypt::IDEA::DECRYPT)
|
30
|
+
string = "This is a string which is not a multiple of 8 characters long"
|
31
|
+
encryptedString = enc.encrypt_string(string)
|
32
|
+
decryptedString = dec.decrypt_string(encryptedString)
|
33
|
+
expect(decryptedString).to eql(string)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "encrypts and decrypts a file" do
|
37
|
+
plainText = "This is a multi-line string\nwhich is not a multiple of 8 \ncharacters long."
|
38
|
+
plainFile = File.new('plain.txt', 'wb+')
|
39
|
+
plainFile.puts(plainText)
|
40
|
+
plainFile.close()
|
41
|
+
enc = Crypt::IDEA.new("Nothing can stop an idea whose time has come", Crypt::IDEA::ENCRYPT)
|
42
|
+
dec = Crypt::IDEA.new("Nothing can stop an idea whose time has come", Crypt::IDEA::DECRYPT)
|
43
|
+
enc.encrypt_file('plain.txt', 'crypt.txt')
|
44
|
+
dec.decrypt_file('crypt.txt', 'decrypt.txt')
|
45
|
+
decryptFile = File.new('decrypt.txt', 'rb')
|
46
|
+
decryptText = decryptFile.readlines().join('').chomp()
|
47
|
+
decryptFile.close()
|
48
|
+
expect(decryptText).to eql(plainText)
|
49
|
+
FileUtils.rm('plain.txt')
|
50
|
+
FileUtils.rm('crypt.txt')
|
51
|
+
FileUtils.rm('decrypt.txt')
|
52
|
+
end
|
53
|
+
|
54
|
+
def h(s)
|
55
|
+
[s].pack("H*")
|
56
|
+
end
|
57
|
+
|
58
|
+
def n8(s)
|
59
|
+
h(s).unpack("n8")
|
60
|
+
end
|
61
|
+
|
62
|
+
def conform(key, plain, cipher)
|
63
|
+
enc = Crypt::IDEA.new("", Crypt::IDEA::ENCRYPT)
|
64
|
+
enc.subkeys = enc.generate_encryption_subkeys(key)
|
65
|
+
dec = Crypt::IDEA.new("", Crypt::IDEA::ENCRYPT)
|
66
|
+
dec.subkeys = dec.generate_decryption_subkeys(key)
|
67
|
+
encryptedBlock = enc.encrypt_block(plain)
|
68
|
+
expect(encryptedBlock).to eql(cipher.b())
|
69
|
+
decryptedBlock = dec.decrypt_block(encryptedBlock)
|
70
|
+
expect(decryptedBlock).to eql(plain)
|
71
|
+
end
|
72
|
+
|
73
|
+
def conform_dec(key, cipher, plain)
|
74
|
+
dec = Crypt::IDEA.new("", Crypt::IDEA::ENCRYPT)
|
75
|
+
dec.subkeys = dec.generate_decryption_subkeys(key)
|
76
|
+
decryptedBlock = dec.decrypt_block(cipher)
|
77
|
+
expect(decryptedBlock).to eql(plain)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "conforms to enceryption test vectors" do
|
81
|
+
conform([0x8000,0,0,0,0,0,0,0], h("0000000000000000"), h("B1F5F7F87901370F"))
|
82
|
+
conform([0x4000,0,0,0,0,0,0,0], h("0000000000000000"), h("B3927DFFB6358626"))
|
83
|
+
conform([0x2000,0,0,0,0,0,0,0], h("0000000000000000"), h("E987E0029FB99785"))
|
84
|
+
conform([0x1000,0,0,0,0,0,0,0], h("0000000000000000"), h("754A03CE08DB7DAA"))
|
85
|
+
conform([0x0800,0,0,0,0,0,0,0], h("0000000000000000"), h("F015F9FB0CFC7E1C"))
|
86
|
+
conform([0x0400,0,0,0,0,0,0,0], h("0000000000000000"), h("69C9FE6007B8FCDF"))
|
87
|
+
conform([0x0200,0,0,0,0,0,0,0], h("0000000000000000"), h("8DA7BC0E63B40DD0"))
|
88
|
+
conform([0x0100,0,0,0,0,0,0,0], h("0000000000000000"), h("2C49BF7DE28C666B"))
|
89
|
+
conform([ 0x80,0,0,0,0,0,0,0], h("0000000000000000"), h("9A4717E8F935712B"))
|
90
|
+
conform([0,0x8000,0,0,0,0,0,0], h("0000000000000000"), h("95A96731978C1B9A"))
|
91
|
+
conform([0,0,0x8000,0,0,0,0,0], h("0000000000000000"), h("398BD9A59E9F5DDB"))
|
92
|
+
conform([0,0,0,0x8000,0,0,0,0], h("0000000000000000"), h("AC1D8708AF0A37EE"))
|
93
|
+
conform([0,0,0,0,0x8000,0,0,0], h("0000000000000000"), h("FAE3FA7B8DB08800"))
|
94
|
+
conform([0,0,0,0,0,0x8000,0,0], h("0000000000000000"), h("B5803F82C0633F01"))
|
95
|
+
conform([0,0,0,0,0, 0x10,0,0], h("0000000000000000"), h("9E25090B7D4EF24E"))
|
96
|
+
conform([0,0,0,0,0, 0x8,0,0], h("0000000000000000"), h("EF62C1109F374AA8"))
|
97
|
+
conform([0,0,0,0,0, 0x2,0,0], h("0000000000000000"), h("5F0CCFE5EB0F19A8"))
|
98
|
+
conform([0xffff,0xffff,0xffff,0xffff,0xffff,0x1,0x000f,0xffff], h("0000000000000000"), h("2E4329C12455430B"))
|
99
|
+
# had problems with bits 95 to 108 inclusive, fixed by final % USHORT on mul()
|
100
|
+
conform([0,0,0,0,0, 0x1,0,0], h("0000000000000000"), h("FCC40014010D617C"))
|
101
|
+
conform([0,0,0,0,0,0,0x8000,0], h("0000000000000000"), h("705D780834A498DA"))
|
102
|
+
conform([0,0,0,0,0,0,0x4000,0], h("0000000000000000"), h("9BCA7BF025B38A68"))
|
103
|
+
conform([0,0,0,0,0,0,0x2000,0], h("0000000000000000"), h("5CF67D0181CB01C1"))
|
104
|
+
conform([0,0,0,0,0,0,0x1000,0], h("0000000000000000"), h("ECDE3D81820381C1"))
|
105
|
+
conform([0,0,0,0,0,0, 0x80,0], h("0000000000000000"), h("C781050DC4110220"))
|
106
|
+
conform([0,0,0,0,0,0, 0x40,0], h("0000000000000000"), h("6DFD0287EC4C0110"))
|
107
|
+
conform([0,0,0,0,0,0, 0x20,0], h("0000000000000000"), h("3B8A017EFB61800E"))
|
108
|
+
conform([0,0,0,0,0,0, 0x10,0], h("0000000000000000"), h("A08F7F81FF627FC0"))
|
109
|
+
conform([0,0,0,0,0,0, 0x8,0], h("0000000000000000"), h("00503FC1AFB93FE0"))
|
110
|
+
conform([0,0,0,0,0,0, 0x1,0], h("0000000000000000"), h("46D371477F33B152"))
|
111
|
+
conform([0,0,0,0,0,0,0,0x8000], h("0000000000000000"), h("BE67AC7DA294CA7C"))
|
112
|
+
conform([0,0,0,0,0,0,0,0], h("8000000000000000"), h("8001000180008000"))
|
113
|
+
conform([0,0,0,0,0,0,0,0], h("0080000000000000"), h("9181E3014C80C980"))
|
114
|
+
conform([0,0,0,0,0,0,0,0], h("0000800000000000"), h("0001800180008000"))
|
115
|
+
conform([0,0,0,0,0,0,0,0], h("0000000200000000"), h("FF49003BFF26FFAA"))
|
116
|
+
conform([0,0,0,0,0,0,0,0], h("0000000004000000"), h("100100014C00E000"))
|
117
|
+
conform([0,0,0,0,0,0,0,0], h("0000000000040000"), h("01110001FE4C00E0"))
|
118
|
+
conform([0,0,0,0,0,0,0,0], h("0000000000001000"), h("8001C001E0009000"))
|
119
|
+
conform([0,0,0,0,0,0,0,0], h("0000000000000100"), h("C8012C018E009900"))
|
120
|
+
conform([0,0,0,0,0,0,0,0], h("0000000000000040"), h("32010B012380E640"))
|
121
|
+
conform([0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101], h("0101010101010101"), h("E3F8AFF7A3795615"))
|
122
|
+
conform([0x7777,0x7777,0x7777,0x7777,0x7777,0x7777,0x7777,0x7777], h("7777777777777777"), h("D2E486D93304B9B6"))
|
123
|
+
conform([0xF7F7,0xF7F7,0xF7F7,0xF7F7,0xF7F7,0xF7F7,0xF7F7,0xF7F7], h("F7F7F7F7F7F7F7F7"), h("8E13C368F53E55AF"))
|
124
|
+
conform([1,0x203,0x405,0x607,0x809,0xa0b,0xc0d,0xe0f], h("0011223344556677"), h("F526AB9A62C0D258"))
|
125
|
+
end
|
126
|
+
|
127
|
+
it "conforms to decryption test vectors" do
|
128
|
+
conform_dec(n8("80000000000000000000000000000000"), h("0000000000000000"), h("78071EE87F0130E8"))
|
129
|
+
conform_dec(n8("00008000000000000000000000000000"), h("0000000000000000"), h("14D47C44835EEB99"))
|
130
|
+
conform_dec(n8("00000000800000000000000000000000"), h("0000000000000000"), h("CEA444C8CE44C2C2"))
|
131
|
+
conform_dec(n8("00000000000000000080000000000000"), h("0000000000000000"), h("6A9EF2F77DE21D8E"))
|
132
|
+
conform_dec(n8("00000000000000000000000800000000"), h("0000000000000000"), h("E698BE39AEA13C79"))
|
133
|
+
conform_dec(n8("00000000000000000000000000400000"), h("0000000000000000"), h("0579E00B945ED0B2"))
|
134
|
+
conform_dec(n8("00000000000000000000000000080000"), h("0000000000000000"), h("F0903DB58BEFF8CF"))
|
135
|
+
conform_dec(n8("00000000000000000000000000000040"), h("0000000000000000"), h("F75986F389F08110"))
|
136
|
+
conform_dec(n8("00000000000000000000000000000000"), h("8000000000000000"), h("8001000180008000"))
|
137
|
+
conform_dec(n8("00000000000000000000000000000000"), h("0002000000000000"), h("FE47FF8D0132FF26"))
|
138
|
+
conform_dec(n8("00000000000000000000000000000000"), h("0000000000200000"), h("08810001F2600700"))
|
139
|
+
conform_dec(n8("00000000000000000000000000000000"), h("0000000000000400"), h("2001B00138006400"))
|
140
|
+
conform_dec(n8("11111111111111111111111111111111"), h("1111111111111111"), h("3A1D3B4DB127C8B7"))
|
141
|
+
conform_dec(n8("52525252525252525252525252525252"), h("5252525252525252"), h("B255918917D30DB6"))
|
142
|
+
conform_dec(n8("66666666666666666666666666666666"), h("6666666666666666"), h("C9248B00868D8651"))
|
143
|
+
conform_dec(n8("ABABABABABABABABABABABABABABABAB"), h("ABABABABABABABAB"), h("5A4C4870F25A207F"))
|
144
|
+
conform_dec(n8("000102030405060708090A0B0C0D0E0F"), h("0011223344556677"), h("DB2D4A92AA68273F"))
|
145
|
+
conform_dec(n8("2BD6459F82C5B300952C49104881FF48"), h("EA024714AD5C4D84"), h("F129A6601EF62A47"))
|
146
|
+
end
|
147
|
+
|
148
|
+
it "calculates multiplicative inverses" do
|
149
|
+
idea = Crypt::IDEA.new("", Crypt::IDEA::ENCRYPT)
|
150
|
+
expect(idea.mulInv(0)).to eql(0)
|
151
|
+
(1..0xffff).each { |i|
|
152
|
+
expect(i * idea.mulInv(i) % 0x10001).to eql(1)
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
data/specs/rijndael.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'crypt/rijndael'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe "Crypt::Rijndael" do
|
5
|
+
|
6
|
+
it "has a blocksize of 128, 192, or 256 bits" do
|
7
|
+
expect {
|
8
|
+
rijndael = Crypt::Rijndael.new("Who is this John Galt guy, anyway?", 64)
|
9
|
+
}.to raise_error(RuntimeError)
|
10
|
+
expect {
|
11
|
+
rijndael = Crypt::Rijndael.new("Who is this John Galt guy, anyway?", 128, 64)
|
12
|
+
}.to raise_error(RuntimeError)
|
13
|
+
rijndael = Crypt::Rijndael.new("Who is this John Galt guy, anyway?", 128, 256)
|
14
|
+
expect(rijndael.block_size).to eql(32)
|
15
|
+
rijndael = Crypt::Rijndael.new("Who is this John Galt guy, anyway?", 256, 128)
|
16
|
+
expect(rijndael.block_size).to eql(16)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "encrypts and decrypts 8-byte blocks" do
|
20
|
+
rijndael = Crypt::Rijndael.new("Who is this John Galt guy, anyway?", 128, 128)
|
21
|
+
block = "This block \u00cd 16" # unicode string of 16 bytes
|
22
|
+
encryptedBlock = rijndael.encrypt_block(block)
|
23
|
+
expect(encryptedBlock).to eql("\x8E\x88\x03\xB8> PnwR)\x93\x1A\xC9:\xC4".b())
|
24
|
+
decryptedBlock = rijndael.decrypt_block(encryptedBlock)
|
25
|
+
expect(decryptedBlock).to eql(block)
|
26
|
+
# attempt to encrypt with wrong block size
|
27
|
+
rijndael = Crypt::Rijndael.new("Who is this John Galt guy, anyway?", 128, 256)
|
28
|
+
expect {
|
29
|
+
encryptedBlock = rijndael.encrypt_block(block)
|
30
|
+
}.to raise_error(RuntimeError)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "encrypts and decrypts strings" do
|
34
|
+
rijndael = Crypt::Rijndael.new("Who is this John Galt guy, anyway?")
|
35
|
+
string = "This is a string which is not a multiple of 8 characters long"
|
36
|
+
encryptedString = rijndael.encrypt_string(string)
|
37
|
+
decryptedString = rijndael.decrypt_string(encryptedString)
|
38
|
+
expect(decryptedString).to eql(string)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "encrypts and decrypts a file" do
|
42
|
+
plainText = "This is a multi-line string\nwhich is not a multiple of 8 \ncharacters long."
|
43
|
+
plainFile = File.new('plain.txt', 'wb+')
|
44
|
+
plainFile.puts(plainText)
|
45
|
+
plainFile.close()
|
46
|
+
rijndael = Crypt::Rijndael.new("Whatever happened to Yuri?")
|
47
|
+
rijndael.encrypt_file('plain.txt', 'crypt.txt')
|
48
|
+
rijndael.decrypt_file('crypt.txt', 'decrypt.txt')
|
49
|
+
decryptFile = File.new('decrypt.txt', 'rb')
|
50
|
+
decryptText = decryptFile.readlines().join('').chomp()
|
51
|
+
decryptFile.close()
|
52
|
+
expect(decryptText).to eql(plainText)
|
53
|
+
FileUtils.rm('plain.txt')
|
54
|
+
FileUtils.rm('crypt.txt')
|
55
|
+
FileUtils.rm('decrypt.txt')
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|