kingpong-ruby-mcrypt 0.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.
- data/Manifest +14 -0
- data/README.rdoc +85 -0
- data/Rakefile +16 -0
- data/ext/extconf.rb +33 -0
- data/ext/mcrypt_wrapper.c +581 -0
- data/lib/mcrypt.rb +472 -0
- data/ruby-mcrypt.gemspec +32 -0
- data/test/generate/Makefile +11 -0
- data/test/generate/generate_testcases.c +389 -0
- data/test/helper.rb +13 -0
- data/test/test_all.rb +5 -0
- data/test/test_basics.rb +216 -0
- data/test/test_brute.rb +3386 -0
- data/test/test_reciprocity.rb +50 -0
- metadata +76 -0
data/lib/mcrypt.rb
ADDED
@@ -0,0 +1,472 @@
|
|
1
|
+
#
|
2
|
+
# mcrypt.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009 Philip Garrett.
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
7
|
+
# copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included
|
15
|
+
# in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
18
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
20
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
21
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
22
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
23
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#
|
25
|
+
require 'mcrypt.so'
|
26
|
+
|
27
|
+
class Mcrypt
|
28
|
+
|
29
|
+
class InvalidKeyError < ArgumentError; end
|
30
|
+
class InvalidIVError < ArgumentError; end
|
31
|
+
class PaddingError < RuntimeError; end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
|
35
|
+
# :call-seq:
|
36
|
+
# Mcrypt.algorithm_info(algorithm_name) -> Hash
|
37
|
+
#
|
38
|
+
# Provides information about the specified algorithm.
|
39
|
+
# Returns a hash with the following keys:
|
40
|
+
# [:block_algorithm] true if the algorithm operates in blocks (mutually exclusive with stream_algorithm)
|
41
|
+
# [:stream_algorithm] true if the algorithm operates in bytes (mutually exclusive with block_algorithm)
|
42
|
+
# [:block_size] the size of blocks the algorithm works with (in bytes)
|
43
|
+
# [:key_size] the maximum key size this algorithm will accept (in bytes)
|
44
|
+
# [:key_sizes] an array containing all the key sizes the algorithm will accept (in bytes)
|
45
|
+
#
|
46
|
+
def algorithm_info(algorithm_name)
|
47
|
+
{
|
48
|
+
:block_algorithm => block_algorithm?(algorithm_name),
|
49
|
+
:stream_algorithm => stream_algorithm?(algorithm_name),
|
50
|
+
:block_size => block_size(algorithm_name),
|
51
|
+
:key_size => key_size(algorithm_name),
|
52
|
+
:key_sizes => key_sizes(algorithm_name),
|
53
|
+
:algorithm_version => algorithm_version(algorithm_name)
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
# :call-seq:
|
58
|
+
# Mcrypt.stream_algorithm?(algorithm_name) -> true or false
|
59
|
+
#
|
60
|
+
# Returns true if the algorithm specified operates in bytes.
|
61
|
+
# This is mutually exclusive with <tt>block_algorithm?</tt>.
|
62
|
+
def stream_algorithm?(algorithm_name)
|
63
|
+
! block_algorithm?(algorithm_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
# :call-seq:
|
67
|
+
# Mcrypt.mode_info(mode_name) -> Hash
|
68
|
+
#
|
69
|
+
# Provides information about the specified operation mode.
|
70
|
+
# Returns a hash with the following keys:
|
71
|
+
# [:block_mode] true if the mode operates in blocks (mutually exclusive with stream_mode)
|
72
|
+
# [:stream_mode] true if the mode operates in bytes (mutually exclusive with block_mode)
|
73
|
+
# [:block_algorithm_mode] true if the mode is for use with block algorithms (mutually exclusive with stream_algorithm_mode)
|
74
|
+
# [:stream_algorithm_mode] true if the mode is for use with stream algorithms (mutually exclusive with block_algorithm_mode)
|
75
|
+
# [:mode_version] an integer identifying the version of the mode implementation
|
76
|
+
#
|
77
|
+
def mode_info(mode_name)
|
78
|
+
{
|
79
|
+
:block_mode => block_mode?(mode_name),
|
80
|
+
:stream_mode => stream_mode?(mode_name),
|
81
|
+
:block_algorithm_mode => block_algorithm_mode?(mode_name),
|
82
|
+
:stream_algorithm_mode => stream_algorithm_mode?(mode_name),
|
83
|
+
:mode_version => mode_version(mode_name)
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
# :call-seq:
|
88
|
+
# Mcrypt.stream_mode?(mode_name) -> true or false
|
89
|
+
#
|
90
|
+
# Returns true if the mode specified operates in bytes.
|
91
|
+
# This is mutually exclusive with <tt>block_mode?</tt>.
|
92
|
+
def stream_mode?(mode_name)
|
93
|
+
! block_mode?(mode_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
# :call-seq:
|
97
|
+
# Mcrypt.stream_algorithm_mode?(mode_name) -> true or false
|
98
|
+
#
|
99
|
+
# Returns true if the mode specified is for use with stream algorithms (e.g. ARCFOUR)
|
100
|
+
# This is mutually exclusive with <tt>block_algorithm_mode?</tt>.
|
101
|
+
def stream_algorithm_mode?(mode_name)
|
102
|
+
! block_algorithm_mode?(mode_name)
|
103
|
+
end
|
104
|
+
|
105
|
+
# :call-seq:
|
106
|
+
# Mcrypt.canonicalize_algorithm(algorithm) -> String
|
107
|
+
#
|
108
|
+
# Converts :rijndael_256 to "rijndael-256".
|
109
|
+
# No need to call manually -- it's called for you when needed.
|
110
|
+
def canonicalize_algorithm(algo) #:nodoc:
|
111
|
+
algo.to_s.downcase.gsub(/_/,'-')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# attr_reader screwed up rdoc.
|
116
|
+
|
117
|
+
# :call-seq:
|
118
|
+
# algorithm -> String
|
119
|
+
#
|
120
|
+
# The canonical name of the algorithm currently in use.
|
121
|
+
def algorithm
|
122
|
+
@algorithm
|
123
|
+
end
|
124
|
+
|
125
|
+
# :call-seq:
|
126
|
+
# mode -> String
|
127
|
+
#
|
128
|
+
# The name of the mode currently in use.
|
129
|
+
def mode
|
130
|
+
@mode
|
131
|
+
end
|
132
|
+
|
133
|
+
# :call-seq:
|
134
|
+
# key -> String
|
135
|
+
#
|
136
|
+
# The key currently in use (raw binary).
|
137
|
+
def key
|
138
|
+
@key
|
139
|
+
end
|
140
|
+
|
141
|
+
# :call-seq:
|
142
|
+
# iv -> String
|
143
|
+
#
|
144
|
+
# The IV currently in use (raw binary).
|
145
|
+
def iv
|
146
|
+
@iv
|
147
|
+
end
|
148
|
+
|
149
|
+
# :call-seq:
|
150
|
+
# padding -> String
|
151
|
+
#
|
152
|
+
# One of +false+ (default), <tt>:pkcs</tt> or <tt>:zeros</tt>.
|
153
|
+
# See <tt>padding=</tt>.
|
154
|
+
def padding
|
155
|
+
@padding
|
156
|
+
end
|
157
|
+
|
158
|
+
# Set the cryptographic key to be used. This is the <em>final raw
|
159
|
+
# binary representation</em> of the key (i.e. not base64 or hex-encoded).
|
160
|
+
#
|
161
|
+
# The key is validated to ensure it is an acceptable length for the
|
162
|
+
# algorithm currently in use (specified in call to +new+).
|
163
|
+
#
|
164
|
+
# The key cannot be reassigned while the object is mid-encryption/decryption
|
165
|
+
# (e.g. after encrypt_more but before encrypt_finish).
|
166
|
+
# Attempting to do so will raise an exception.
|
167
|
+
def key=(new_key)
|
168
|
+
if @opened
|
169
|
+
raise(RuntimeError, "cannot change key mid-stream")
|
170
|
+
end
|
171
|
+
@key = validate_key(new_key)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Set the initialization vector (IV) to be used. This is the <em>final
|
175
|
+
# raw binary representation</em> of the key (i.e. not base64 or hex-encoded).
|
176
|
+
#
|
177
|
+
# The IV cannot be reassigned while the object is mid-encryption/decryption
|
178
|
+
# (e.g. after encrypt_more but before encrypt_finish).
|
179
|
+
# Attempting to do so will raise an exception.
|
180
|
+
#
|
181
|
+
# If the mode in use does not use an IV and +new_iv+ is non-nil,
|
182
|
+
# an exception will be raised to prevent you shooting yourself in
|
183
|
+
# the foot.
|
184
|
+
def iv=(new_iv)
|
185
|
+
if @opened
|
186
|
+
raise(RuntimeError, "cannot change IV mid-stream")
|
187
|
+
end
|
188
|
+
@iv = validate_iv(new_iv)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Set the padding technique to be used. Most ciphers work in
|
192
|
+
# blocks, not bytes, so unless you know that the size of your
|
193
|
+
# plaintext will always be a multiple of the cipher's block size,
|
194
|
+
# you'll need to use some sort of padding.
|
195
|
+
#
|
196
|
+
# <tt>padding_type.to_s</tt> should be one of:
|
197
|
+
#
|
198
|
+
# ["pkcs","pkcs5","pkcs7"]
|
199
|
+
# Use pkcs5/7 padding which is safe for use with arbitrary binary
|
200
|
+
# inputs (as opposed to null-terminated C-strings). Each byte of
|
201
|
+
# padding contains the number of bytes of padding used. For example,
|
202
|
+
# if 5 bytes of padding are needed, each byte has the value 0x05. See
|
203
|
+
# {RFC 2315}[http://tools.ietf.org/html/rfc2315#page-22] for a more
|
204
|
+
# detailed explanation. Padding is <em>always</em> added to
|
205
|
+
# disambiguate an incomplete message from one that happens to fall on
|
206
|
+
# block boundaries.
|
207
|
+
#
|
208
|
+
# ["zeros","zeroes"]
|
209
|
+
# Pads the plaintext with NUL characters. This works fine with C-
|
210
|
+
# strings. Don't use it with anything that might have other embedded
|
211
|
+
# nulls.
|
212
|
+
#
|
213
|
+
# ["none"]
|
214
|
+
# No padding is used. Will throw exceptions if the input size does
|
215
|
+
# not fall on block boundaries.
|
216
|
+
#
|
217
|
+
# You can also pass +true+ (which means "pkcs") or +false+ (no
|
218
|
+
# padding). No padding is used by default.
|
219
|
+
#
|
220
|
+
# N.B. This is not a feature of libmcrypt but of this Ruby module.
|
221
|
+
def padding=(padding_type)
|
222
|
+
@padding = case padding_type.to_s
|
223
|
+
when "true", /\Apkcs[57]?\Z/
|
224
|
+
@padding = :pkcs
|
225
|
+
when /\Azeroe?s\Z/
|
226
|
+
@padding = :zeros
|
227
|
+
when "false", "none", ""
|
228
|
+
@padding = false
|
229
|
+
else
|
230
|
+
raise(ArgumentError, "invalid padding type #{padding_type.to_s}")
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Returns true if the mode in use operates in bytes.
|
235
|
+
def stream_mode?
|
236
|
+
! block_mode?
|
237
|
+
end
|
238
|
+
|
239
|
+
# Encrypts +plaintext+ and returns the encrypted result in one step.
|
240
|
+
# Use this for small inputs.
|
241
|
+
#
|
242
|
+
# To save memory when encrypting larger inputs, process the plaintext
|
243
|
+
# in chunks instead by using +encrypt_more+ and +encrypt_finish+.
|
244
|
+
def encrypt(plaintext)
|
245
|
+
if @opened
|
246
|
+
raise(RuntimeError, "cannot combine streaming use and atomic use")
|
247
|
+
end
|
248
|
+
encrypt_more(plaintext) << encrypt_finish
|
249
|
+
end
|
250
|
+
|
251
|
+
# Encrypts +plaintext+ and returns a chunk of ciphertext. Input to
|
252
|
+
# this function is buffered across calls until it is large enough to
|
253
|
+
# fill a complete block (as defined by the algorithm in use), at which
|
254
|
+
# point the encrypted data will be returned. If there is not enough
|
255
|
+
# buffer to encrypt an entire block, an empty string will be returned.
|
256
|
+
def encrypt_more(plaintext)
|
257
|
+
open_td
|
258
|
+
|
259
|
+
return encrypt_generic(plaintext) if stream_mode?
|
260
|
+
|
261
|
+
# buffer plaintext and process in blocks.
|
262
|
+
# stream modes return 1 for block_size so this still works.
|
263
|
+
buffer << plaintext
|
264
|
+
blocks = buffer.length / block_size
|
265
|
+
|
266
|
+
if blocks == 0
|
267
|
+
# we don't have an entire block yet. keep buffering
|
268
|
+
''
|
269
|
+
else
|
270
|
+
encrypt_generic(buffer.slice!(0,blocks*block_size))
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Completes the encryption process and returns the final ciphertext chunk if
|
275
|
+
# any.
|
276
|
+
def encrypt_finish
|
277
|
+
open_td
|
278
|
+
|
279
|
+
# no buffering/padding in stream mode
|
280
|
+
return '' if stream_mode?
|
281
|
+
|
282
|
+
# nothing to encrypt, no padding to add
|
283
|
+
return '' if buffer.length == 0 && !padding
|
284
|
+
|
285
|
+
buffer << padding_str
|
286
|
+
ciphertext = encrypt_more('') # consume existing buffer
|
287
|
+
|
288
|
+
if buffer.length > 0
|
289
|
+
raise(RuntimeError, "internal error: buffer should be empty")
|
290
|
+
end
|
291
|
+
|
292
|
+
ciphertext
|
293
|
+
ensure
|
294
|
+
close_td
|
295
|
+
end
|
296
|
+
|
297
|
+
# Decrypts +ciphertext+ and returns the decrypted result in one step.
|
298
|
+
# Use this for small inputs.
|
299
|
+
#
|
300
|
+
# To save memory when decrypting larger inputs, process the ciphertext
|
301
|
+
# in chunks instead by using +decrypt_more+ and +decrypt_finish+.
|
302
|
+
def decrypt(ciphertext)
|
303
|
+
if @opened
|
304
|
+
raise(RuntimeError, "cannot combine streaming use and atomic use")
|
305
|
+
end
|
306
|
+
decrypt_more(ciphertext) << decrypt_finish
|
307
|
+
end
|
308
|
+
|
309
|
+
# Decrypts +ciphertext+ and returns a chunk of plaintext. Input to
|
310
|
+
# this function is buffered across calls until it is large enough to
|
311
|
+
# safely perform the decryption (as defined by the block size of
|
312
|
+
# algorithm in use). When there is enough data, a chunk of the
|
313
|
+
# decrypted data is returned. Otherwise it returns an empty string.
|
314
|
+
#
|
315
|
+
def decrypt_more(ciphertext)
|
316
|
+
open_td
|
317
|
+
|
318
|
+
# no buffering in stream mode
|
319
|
+
return decrypt_generic(ciphertext) if stream_mode?
|
320
|
+
|
321
|
+
# buffer ciphertext and process in blocks.
|
322
|
+
buffer << ciphertext
|
323
|
+
blocks = buffer.length / block_size
|
324
|
+
|
325
|
+
if blocks > 1
|
326
|
+
# maintain at least one block of buffer, because it may be padding
|
327
|
+
# that we'll need to process in decrypt_finish.
|
328
|
+
decrypt_generic(buffer.slice!(0,(blocks - 1)*block_size))
|
329
|
+
else
|
330
|
+
# we don't have enough blocks yet. keep buffering
|
331
|
+
''
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Completes the decryption process and returns the final plaintext chunk.
|
336
|
+
def decrypt_finish
|
337
|
+
open_td
|
338
|
+
|
339
|
+
# no buffering/padding in stream mode
|
340
|
+
return '' if stream_mode?
|
341
|
+
|
342
|
+
# There should always be exactly zero or one block(s) in the buffer
|
343
|
+
# at this point, because the input should be on block boundaries,
|
344
|
+
# and we've consumed all available blocks but one in decrypt_more().
|
345
|
+
if ! [0,block_size].include?(buffer.length)
|
346
|
+
raise(RuntimeError, "input is not a multiple of the block size (#{block_size})")
|
347
|
+
end
|
348
|
+
|
349
|
+
plaintext = decrypt_generic(buffer.slice!(0,buffer.length))
|
350
|
+
|
351
|
+
case padding
|
352
|
+
when :pkcs
|
353
|
+
unpad_pkcs(plaintext)
|
354
|
+
when :zeros
|
355
|
+
plaintext.sub!(/\0*\Z/,'')
|
356
|
+
else
|
357
|
+
plaintext
|
358
|
+
end
|
359
|
+
ensure
|
360
|
+
close_td
|
361
|
+
end
|
362
|
+
|
363
|
+
# todo: figure out how to declare these private in the extension file
|
364
|
+
private :generic_init, :encrypt_generic, :decrypt_generic
|
365
|
+
|
366
|
+
private
|
367
|
+
|
368
|
+
def buffer
|
369
|
+
@buffer
|
370
|
+
end
|
371
|
+
|
372
|
+
# This gets called by +initialize+ which is implemented in C.
|
373
|
+
# If key and iv are passed to new(), they will be passed through
|
374
|
+
# here for processing.
|
375
|
+
def after_init(key=nil,iv=nil,padding=nil)
|
376
|
+
@padding = false
|
377
|
+
@buffer = ""
|
378
|
+
|
379
|
+
self.key = key if key
|
380
|
+
self.iv = iv if iv
|
381
|
+
self.padding = padding if padding
|
382
|
+
end
|
383
|
+
|
384
|
+
# Validates that the key is of the proper size. Raises exception if
|
385
|
+
# invalid.
|
386
|
+
def validate_key(key)
|
387
|
+
if key.length == key_size || key_sizes.include?(key.length)
|
388
|
+
key
|
389
|
+
else
|
390
|
+
raise(InvalidKeyError, "Key length #{key.length} is not supported by #{algorithm}.")
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# Validates that the IV is of the proper size and that the IV presence is
|
395
|
+
# supported by the encryption mode. Raises exception if invalid.
|
396
|
+
def validate_iv(iv)
|
397
|
+
if iv.nil? && !has_iv?
|
398
|
+
nil
|
399
|
+
elsif !has_iv?
|
400
|
+
raise(InvalidIVError, "Mode #{mode} does not use an IV.")
|
401
|
+
elsif iv.length == iv_size
|
402
|
+
iv
|
403
|
+
else
|
404
|
+
raise(InvalidIVError, "IV length #{iv.length} is not supported by #{mode}.")
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# Validates both key and IV. Raises exception if invalid.
|
409
|
+
def validate!
|
410
|
+
validate_key(@key)
|
411
|
+
if has_iv?
|
412
|
+
if @iv.nil?
|
413
|
+
raise(InvalidIVError, "#{algorithm}/#{mode} requires an IV but none was provided.")
|
414
|
+
end
|
415
|
+
validate_iv(@iv)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# Opens a encryption thread descriptor if it is not already opened.
|
420
|
+
def open_td
|
421
|
+
return if @opened
|
422
|
+
validate!
|
423
|
+
generic_init
|
424
|
+
@opened = true
|
425
|
+
end
|
426
|
+
|
427
|
+
# Closes and deinitializes the encryption thread descriptor if it is open.
|
428
|
+
def close_td
|
429
|
+
return unless @opened
|
430
|
+
generic_deinit
|
431
|
+
@opened = false
|
432
|
+
end
|
433
|
+
|
434
|
+
# Returns the padding string to apply to the plaintext buffer.
|
435
|
+
def padding_str
|
436
|
+
if buffer.length > block_size
|
437
|
+
raise(RuntimeError, "internal error: buffer is larger than block size")
|
438
|
+
end
|
439
|
+
|
440
|
+
pad_size = block_size - buffer.length
|
441
|
+
pad_size = block_size if pad_size == 0 # add a block to disambiguate
|
442
|
+
pad_char = nil
|
443
|
+
case padding
|
444
|
+
when :pkcs
|
445
|
+
pad_char = "%c" % pad_size
|
446
|
+
when :zeros
|
447
|
+
pad_char = "%c" % 0
|
448
|
+
else
|
449
|
+
raise(RuntimeError, "Input is not an even multiple of the block size " +
|
450
|
+
"(#{block_size}), but no padding has been specified.")
|
451
|
+
end
|
452
|
+
pad_char * pad_size
|
453
|
+
end
|
454
|
+
|
455
|
+
# Strips and validates pkcs padding.
|
456
|
+
def unpad_pkcs(block)
|
457
|
+
chars = block.unpack('C*')
|
458
|
+
padding_bytes = mod = chars.last
|
459
|
+
if mod > chars.length
|
460
|
+
raise(PaddingError, "incorrect pkcs padding mod value #{mod}")
|
461
|
+
end
|
462
|
+
while padding_bytes > 0
|
463
|
+
if chars.last != mod
|
464
|
+
raise(PaddingError, "incorrect pkcs padding character #{chars.last} (should be #{mod})")
|
465
|
+
end
|
466
|
+
chars.pop
|
467
|
+
padding_bytes -= 1
|
468
|
+
end
|
469
|
+
chars.pack('C*')
|
470
|
+
end
|
471
|
+
|
472
|
+
end
|