kingpong-ruby-mcrypt 0.1.0

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