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/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