ruby-mcrypt 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Manifest +14 -0
- data/README.rdoc +84 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/ext/.gitignore +8 -0
- data/ext/extconf.rb +46 -0
- data/ext/mcrypt_wrapper.c +588 -0
- data/lib/.gitignore +3 -0
- data/lib/mcrypt.rb +472 -0
- data/ruby-mcrypt.gemspec +147 -0
- data/test/generate/.gitignore +3 -0
- data/test/generate/Makefile +11 -0
- data/test/generate/generate_testcases.c +389 -0
- data/test/helper.rb +13 -0
- data/test/test_basics.rb +216 -0
- data/test/test_brute.rb +3386 -0
- data/test/test_reciprocity.rb +50 -0
- metadata +174 -0
data/lib/.gitignore
ADDED
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
|