├── .gitignore ├── Manifest ├── README.rdoc ├── Rakefile ├── VERSION ├── cross.sh ├── ext ├── .gitignore ├── extconf.rb └── mcrypt_wrapper.c ├── lib ├── .gitignore └── mcrypt.rb ├── ruby-mcrypt.gemspec └── test ├── generate ├── .gitignore ├── Makefile └── generate_testcases.c ├── helper.rb ├── test_basics.rb ├── test_brute.rb └── test_reciprocity.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .config 2 | InstalledFiles 3 | *.gem 4 | doc 5 | *.swp 6 | MAINTAINER 7 | -------------------------------------------------------------------------------- /Manifest: -------------------------------------------------------------------------------- 1 | Manifest 2 | README.rdoc 3 | Rakefile 4 | ext/extconf.rb 5 | ext/mcrypt_wrapper.c 6 | lib/mcrypt.rb 7 | ruby-mcrypt.gemspec 8 | test/generate/Makefile 9 | test/generate/generate_testcases.c 10 | test/helper.rb 11 | test/test_all.rb 12 | test/test_basics.rb 13 | test/test_brute.rb 14 | test/test_reciprocity.rb 15 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Mcrypt - libmcrypt bindings for Ruby 2 | 3 | Mcrypt provides Ruby-language bindings for libmcrypt(3), a 4 | symmetric cryptography library. {Libmcrypt}[http://mcrypt.sourceforge.net/] 5 | supports lots of different ciphers and encryption modes. 6 | 7 | == You will need 8 | 9 | * A working Ruby installation (>= 1.8.6 or 1.9) 10 | * A working libmcrypt installation (2.5.x or 2.6.x, tested with 2.5.8) 11 | * A sane build environment 12 | 13 | == Installation 14 | 15 | Install the gem: 16 | gem install ruby-mcrypt --test -- --with-mcrypt-dir=/path/to/mcrypt/prefix 17 | 18 | If you're installing on Ubuntu: 19 | sudo apt-get install mcrypt libmcrypt-dev 20 | gem install ruby-mcrypt 21 | 22 | If you want to run the longer test suite, do this instead: 23 | MCRYPT_TEST_BRUTE=1 \ 24 | gem install ruby-mcrypt --test -- --with-mcrypt-dir=/path/to/mcrypt/prefix 25 | 26 | Put this in your code: 27 | require 'rubygems' 28 | require 'mcrypt' 29 | 30 | Or in Rails' environment.rb: 31 | gem "ruby-mcrypt", :lib => "mcrypt" 32 | 33 | == Usage 34 | 35 | crypto = Mcrypt.new(:twofish, :cbc, MY_KEY, MY_IV, :pkcs) 36 | 37 | # encryption and decryption in one step 38 | ciphertext = crypto.encrypt(plaintext) 39 | plaintext = crypto.decrypt(ciphertext) 40 | 41 | # encrypt in smaller steps 42 | while chunk = $stdin.read(4096) 43 | $stdout << crypto.encrypt_more(chunk) 44 | end 45 | $stdout << crypto.encrypt_finish 46 | 47 | # or decrypt: 48 | while chunk = $stdin.read(4096) 49 | $stdout << crypto.decrypt_more(chunk) 50 | end 51 | $stdout << crypto.decrypt_finish 52 | 53 | == Known Issues 54 | 55 | * Test coverage is lacking. 56 | 57 | If you find any bugs, please let the author know. 58 | 59 | == Wish List 60 | 61 | * IO-like behavior, e.g. crypto.open($stdin) { |stream| ... } 62 | 63 | == Author 64 | 65 | * Philip Garrett 66 | 67 | == Copyright and License 68 | 69 | Copyright (c) 2009-2013 Philip Garrett. 70 | 71 | Permission is hereby granted, free of charge, to any person obtaining a 72 | copy of this software and associated documentation files (the 73 | "Software"), to deal in the Software without restriction, including 74 | without limitation the rights to use, copy, modify, merge, publish, 75 | distribute, sublicense, and/or sell copies of the Software, and to 76 | permit persons to whom the Software is furnished to do so, subject to 77 | the following conditions: 78 | 79 | The above copyright notice and this permission notice shall be included 80 | in all copies or substantial portions of the Software. 81 | 82 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 83 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 84 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 85 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 86 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 87 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 88 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 89 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rbconfig' 3 | require 'rake' 4 | require 'rake/testtask' 5 | 6 | # http://stackoverflow.com/questions/213368/how-can-i-reliably-discover-the-full-path-of-the-ruby-executable 7 | RUBY = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']).sub(/.*\s.*/m, '"\&"') 8 | 9 | ENV["MAINTAINER_MODE"] = "1" if File.exists?(File.dirname(__FILE__) + "/MAINTAINER") 10 | 11 | task :default => :test 12 | 13 | begin 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gemspec| 16 | gemspec.name = "ruby-mcrypt" 17 | gemspec.summary = "Ruby bindings for libmcrypt" 18 | gemspec.description = File.read(File.join(File.dirname(__FILE__),"README.rdoc")) 19 | gemspec.email = "philgarr@gmail.com" 20 | gemspec.homepage = "http://github.com/kingpong/ruby-mcrypt" 21 | gemspec.authors = ["Philip Garrett"] 22 | gemspec.required_ruby_version = '>= 1.8.6' 23 | gemspec.requirements << 'libmcrypt (2.5.x or 2.6.x, tested with 2.5.8)' 24 | end 25 | Jeweler::GemcutterTasks.new 26 | rescue LoadError 27 | puts "Jeweler not available. Install it with: gem install jeweler" 28 | end 29 | 30 | EXTENSION = "ext/mcrypt.#{RbConfig::CONFIG["DLEXT"]}" 31 | 32 | desc "Compile extension" 33 | task :compile => EXTENSION 34 | file EXTENSION => FileList["ext/Makefile","ext/*.c"] do 35 | Dir.chdir("ext") do 36 | opts = ENV["MAINTAINER_MODE"] ? ["V=1"] : [] 37 | system("make", *opts) || raise("could not build ruby-mcrypt") 38 | end 39 | end 40 | 41 | file "ext/Makefile" => ["ext/extconf.rb"] do 42 | Dir.chdir("ext") do 43 | system("RUBY","extconf.rb",*ARGV) || raise("could not configure ruby-mcrypt for your system") 44 | end 45 | end 46 | 47 | desc "Delete build files and products" 48 | task :clean do 49 | %W( #{EXTENSION} ext/Makefile ext/*.o ).each do |pattern| 50 | Dir[pattern].each {|filespec| File.unlink(filespec) } 51 | end 52 | end 53 | 54 | Rake::TestTask.new do |t| 55 | t.libs << ["test", "ext"] 56 | t.test_files = FileList['test/test*.rb'] 57 | t.verbose = true 58 | end 59 | task :test => FileList[EXTENSION, "lib/mcrypt.rb"] 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.0 -------------------------------------------------------------------------------- /cross.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | [[ -s ~/.rvm/scripts/rvm ]] && source ~/.rvm/scripts/rvm 4 | 5 | cd "$(dirname "$0")" 6 | 7 | touch MAINTAINER 8 | 9 | for ruby in 1.8.7 1.9.3 10 | do 11 | rvm use $ruby 12 | rake clean 13 | rake || { 14 | echo "BUILD FAILED!" >&2 15 | exit 1 16 | } 17 | done 18 | -------------------------------------------------------------------------------- /ext/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.bak 3 | *.bundle 4 | *.o 5 | *.swp 6 | Makefile 7 | mcrypt.rb 8 | *.so 9 | -------------------------------------------------------------------------------- /ext/extconf.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'mkmf' 3 | extension_name = 'mcrypt' 4 | dir_config(extension_name) 5 | 6 | if RUBY_VERSION =~ /\A1.9/ 7 | $CPPFLAGS += " -DRUBY_19" 8 | elsif RUBY_VERSION =~ /\A1.8/ 9 | $CPPFLAGS += " -DRUBY_18" 10 | end 11 | 12 | unless have_header("mcrypt.h") && have_library("mcrypt","mcrypt_module_open") 13 | $stderr.puts <<-EOF 14 | 15 | ######################################################################## 16 | 17 | Unable to find your mcrypt library. 18 | 19 | Make sure you have libmcrypt installed (and libmcrypt-devel if you're 20 | on a Linux distribution that does things that way). 21 | 22 | If your libmcrypt is in a nonstandard location and has header files in 23 | PREFIX/include and libraries in PREFIX/lib, try installing the gem like 24 | this (note the extra "--"): 25 | 26 | gem install kingpong-ruby-mcrypt --source=http://gems.github.com \\ 27 | -- --with-mcrypt-dir=/path/to/mcrypt/prefix 28 | 29 | You can also specify the include and library directories separately: 30 | 31 | gem install kingpong-ruby-mcrypt --source=http://gems.github.com \\ 32 | -- --with-mcrypt-include=/path/to/mcrypt/include \\ 33 | --with-mcrypt-lib=/path/to/mcrypt/lib 34 | 35 | Specifically, if you're using MacPorts, this should work for you: 36 | 37 | sudo port install libmcrypt +universal 38 | sudo gem install kingpong-ruby-mcrypt --source=http://gems.github.com \\ 39 | -- --with-mcrypt-dir=/opt/local 40 | 41 | ######################################################################## 42 | 43 | EOF 44 | exit 1 45 | end 46 | 47 | if ENV["MAINTAINER_MODE"] 48 | $CFLAGS += " -Werror" 49 | end 50 | 51 | create_makefile(extension_name) 52 | -------------------------------------------------------------------------------- /ext/mcrypt_wrapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mcrypt_wrapper.c 3 | * 4 | * Copyright (c) 2009-2013 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 | 26 | #include "ruby.h" 27 | #include 28 | #include 29 | #include 30 | 31 | #ifdef RUBY_18 32 | # ifndef RSTRING_PTR 33 | # define RSTRING_PTR(x) (RSTRING(x)->ptr) 34 | # endif 35 | # ifndef RSTRING_LEN 36 | # define RSTRING_LEN(x) (RSTRING(x)->len) 37 | # endif 38 | #endif 39 | 40 | #define RSTR_N(V) (NIL_P(V) ? NULL : RSTRING_PTR(V)) 41 | #define TO_RB_BOOL(V) ((V) ? Qtrue : Qfalse) 42 | 43 | /* utilities */ 44 | static ID sym_to_s; 45 | static VALUE to_s(VALUE o); 46 | 47 | static ID sym_canonicalize_algorithm; 48 | static VALUE canonicalize_algorithm(VALUE o); 49 | 50 | static char *dup_rbstring(VALUE o, int include_null); 51 | 52 | static VALUE enumerate_key_sizes(int *sizes, int num_of_sizes, int max_size); 53 | 54 | static int safe_len(long orig); 55 | 56 | /* globals */ 57 | static VALUE cMcrypt; 58 | static VALUE cInvalidAlgorithmOrModeError; 59 | static VALUE cMcryptRuntimeError; 60 | 61 | 62 | static void mc_free(void *p) 63 | { 64 | MCRYPT *box = (MCRYPT *)p; 65 | if (*box != NULL) { 66 | mcrypt_generic_deinit(*box); /* shutdown */ 67 | mcrypt_module_close(*box); /* free */ 68 | } 69 | free(box); 70 | } 71 | 72 | static VALUE mc_alloc(VALUE klass) 73 | { 74 | MCRYPT *box; 75 | box = malloc(sizeof(MCRYPT)); 76 | *box = 0; /* will populate in mc_initialize */ 77 | return Data_Wrap_Struct(klass, 0, mc_free, box); 78 | } 79 | 80 | 81 | /* 82 | * call-seq: 83 | * Mcrypt.new(algorithm,mode,key=nil,iv=nil,padding=nil) -> new_mcrypt 84 | * 85 | * Creates and initializes a new Mcrypt object with the specified +algorithm+ and +mode+. 86 | * +key+, +iv+ and +padding+ will also be initialized if they are present. 87 | */ 88 | static VALUE mc_initialize(int argc, VALUE *argv, VALUE self) 89 | { 90 | VALUE algo, mode, key, iv, padding; 91 | char *s_algo, *s_mode; 92 | MCRYPT *box; 93 | 94 | rb_scan_args(argc, argv, "23", &algo, &mode, &key, &iv, &padding); 95 | 96 | Data_Get_Struct(self, MCRYPT, box); 97 | 98 | /* sanity check. should be empty still */ 99 | if (*box != NULL) 100 | rb_raise(rb_eFatal, "mcrypt binding internal error"); 101 | 102 | /* convert :rijndael_256 to "rijndael-256" */ 103 | algo = canonicalize_algorithm(algo); 104 | mode = to_s(mode); 105 | 106 | /* mcrypt needs null-terminated strings */ 107 | s_algo = dup_rbstring(algo, 1); 108 | s_mode = dup_rbstring(mode, 1); 109 | 110 | *box = mcrypt_module_open(s_algo, NULL, s_mode, NULL); 111 | if (*box == MCRYPT_FAILED) { 112 | char message[256]; 113 | /* MCRYPT_FAILED is currently 0, but we should explicitly set 114 | to zero in case they change that. We don't want to attempt to 115 | free it later. */ 116 | *box = 0; 117 | snprintf(message, sizeof(message), 118 | "Could not initialize using algorithm '%s' with mode " 119 | "'%s'. Check mcrypt(3) for supported combinations.", 120 | s_algo, s_mode); 121 | free(s_algo); 122 | free(s_mode); 123 | rb_raise(cInvalidAlgorithmOrModeError, message, NULL); 124 | } 125 | free(s_algo); 126 | free(s_mode); 127 | 128 | rb_iv_set(self, "@algorithm", algo); 129 | rb_iv_set(self, "@mode", mode); 130 | rb_iv_set(self, "@opened", TO_RB_BOOL(0)); 131 | 132 | /* post-initialization stuff that's easier done in ruby */ 133 | rb_funcall(self, rb_intern("after_init"), 3, key, iv, padding); 134 | 135 | return self; 136 | } 137 | 138 | /* :nodoc: */ 139 | static VALUE mc_generic_init(VALUE self) 140 | { 141 | /* ruby has already validated @key and @iv */ 142 | VALUE key, iv; 143 | int rv; 144 | MCRYPT *box; 145 | Data_Get_Struct(self, MCRYPT, box); 146 | 147 | key = rb_iv_get(self, "@key"); 148 | iv = rb_iv_get(self, "@iv"); 149 | 150 | rv = mcrypt_generic_init(*box, 151 | (void *)RSTRING_PTR(key), 152 | safe_len(RSTRING_LEN(key)), 153 | RSTR_N(iv)); 154 | if (rv < 0) { 155 | const char *err = mcrypt_strerror(rv); 156 | rb_raise(cMcryptRuntimeError, "Could not initialize mcrypt: %s", err); 157 | } 158 | 159 | return Qnil; 160 | } 161 | 162 | /* :nodoc: */ 163 | static VALUE mc_generic_deinit(VALUE self) 164 | { 165 | MCRYPT *box; 166 | Data_Get_Struct(self, MCRYPT, box); 167 | mcrypt_generic_deinit(*box); 168 | return Qnil; 169 | } 170 | 171 | /* :nodoc: */ 172 | static VALUE mc_encrypt_generic(VALUE self, VALUE plaintext) 173 | { 174 | /* plaintext is encrypted in-place */ 175 | MCRYPT *box; 176 | VALUE ciphertext; 177 | int rv; 178 | 179 | Data_Get_Struct(self, MCRYPT, box); 180 | 181 | /* rb_str_dup doesn't actually copy the buffer, hence rb_str_new */ 182 | ciphertext = rb_str_new(RSTRING_PTR(plaintext), RSTRING_LEN(plaintext)); 183 | 184 | rv = mcrypt_generic(*box, (void *)RSTRING_PTR(ciphertext), 185 | safe_len(RSTRING_LEN(ciphertext))); 186 | if (rv != 0) 187 | rb_raise(cMcryptRuntimeError, "internal error: mcrypt_generic returned %d", rv); 188 | return ciphertext; 189 | } 190 | 191 | /* :nodoc: */ 192 | static VALUE mc_decrypt_generic(VALUE self, VALUE ciphertext) 193 | { 194 | /* ciphertext is decrypted in-place */ 195 | MCRYPT *box; 196 | VALUE plaintext; 197 | int rv; 198 | 199 | Data_Get_Struct(self, MCRYPT, box); 200 | 201 | /* rb_str_dup doesn't actually copy the buffer, hence rb_str_new */ 202 | plaintext = rb_str_new(RSTRING_PTR(ciphertext), RSTRING_LEN(ciphertext)); 203 | 204 | rv = mdecrypt_generic(*box, (void *)RSTRING_PTR(plaintext), 205 | safe_len(RSTRING_LEN(plaintext))); 206 | if (rv != 0) 207 | rb_raise(cMcryptRuntimeError, "internal error: mdecrypt_generic returned %d", rv); 208 | return plaintext; 209 | } 210 | 211 | /* 212 | * call-seq: 213 | * key_size -> Fixnum 214 | * 215 | * Returns the maximum key size for the algorithm in use. 216 | */ 217 | static VALUE mc_key_size(VALUE self) 218 | { 219 | MCRYPT *box; 220 | Data_Get_Struct(self, MCRYPT, box); 221 | return INT2FIX(mcrypt_enc_get_key_size(*box)); 222 | } 223 | 224 | /* 225 | * call-seq: 226 | * block_size -> Fixnum 227 | * 228 | * Returns the block size (in bytes) for the algorithm in use. If it 229 | * is a stream algorithm, this will be 1. 230 | */ 231 | static VALUE mc_block_size(VALUE self) 232 | { 233 | MCRYPT *box; 234 | Data_Get_Struct(self, MCRYPT, box); 235 | return INT2FIX(mcrypt_enc_get_block_size(*box)); 236 | } 237 | 238 | /* 239 | * call-seq: 240 | * iv_size -> Fixnum or nil 241 | * 242 | * Returns the IV size (in bytes) for the mode in use. If the mode does 243 | * not use an IV, returns nil. 244 | */ 245 | static VALUE mc_iv_size(VALUE self) 246 | { 247 | MCRYPT *box; 248 | Data_Get_Struct(self, MCRYPT, box); 249 | if (mcrypt_enc_mode_has_iv(*box)) 250 | return INT2FIX(mcrypt_enc_get_iv_size(*box)); 251 | else 252 | return Qnil; 253 | } 254 | 255 | /* 256 | * call-seq: 257 | * block_algorithm? -> true or false 258 | * 259 | * True if the algorithm in use operates in blocks. 260 | */ 261 | static VALUE mc_is_block_algorithm(VALUE self) 262 | { 263 | MCRYPT *box; 264 | Data_Get_Struct(self, MCRYPT, box); 265 | return TO_RB_BOOL(mcrypt_enc_is_block_algorithm(*box)); 266 | } 267 | 268 | /* 269 | * call-seq: 270 | * block_mode? -> true or false 271 | * 272 | * True if the encryption mode in use operates in blocks. 273 | */ 274 | static VALUE mc_is_block_mode(VALUE self) 275 | { 276 | MCRYPT *box; 277 | Data_Get_Struct(self, MCRYPT, box); 278 | return TO_RB_BOOL(mcrypt_enc_is_block_mode(*box)); 279 | } 280 | 281 | /* 282 | * call-seq: 283 | * block_algorithm_mode? -> true or false 284 | * 285 | * True if the encryption mode is for use with block algorithms. 286 | */ 287 | static VALUE mc_is_block_algorithm_mode(VALUE self) 288 | { 289 | MCRYPT *box; 290 | Data_Get_Struct(self, MCRYPT, box); 291 | return TO_RB_BOOL(mcrypt_enc_is_block_algorithm_mode(*box)); 292 | } 293 | 294 | /* 295 | * call-seq: 296 | * has_iv? -> true or false 297 | * 298 | * True if the the encryption mode uses an IV. 299 | */ 300 | static VALUE mc_mode_has_iv(VALUE self) 301 | { 302 | MCRYPT *box; 303 | Data_Get_Struct(self, MCRYPT, box); 304 | return TO_RB_BOOL(mcrypt_enc_mode_has_iv(*box)); 305 | } 306 | 307 | /* 308 | * call-seq: 309 | * key_sizes -> Array 310 | * 311 | * An array of the key sizes supported by the algorithm. 312 | */ 313 | static VALUE mc_key_sizes(VALUE self) 314 | { 315 | MCRYPT *box; 316 | int *sizes, num_of_sizes; 317 | Data_Get_Struct(self, MCRYPT, box); 318 | 319 | sizes = mcrypt_enc_get_supported_key_sizes(*box, &num_of_sizes); 320 | return enumerate_key_sizes(sizes, num_of_sizes, mcrypt_enc_get_key_size(*box)); 321 | } 322 | 323 | /* 324 | * call-seq: 325 | * algorithm_version -> Fixnum 326 | * 327 | * The numeric version of the algorithm implementation. 328 | */ 329 | static VALUE mc_algorithm_version(VALUE self) 330 | { 331 | int version; 332 | VALUE algo = rb_iv_get(self,"@algorithm"); 333 | version = mcrypt_module_algorithm_version(RSTRING_PTR(algo), NULL); 334 | return INT2FIX(version); 335 | } 336 | 337 | /* 338 | * call-seq: 339 | * mode_version -> Fixnum 340 | * 341 | * The numeric version of the encryption mode implementation. 342 | */ 343 | static VALUE mc_mode_version(VALUE self) 344 | { 345 | int version; 346 | VALUE mode = rb_iv_get(self,"@mode"); 347 | version = mcrypt_module_mode_version(RSTRING_PTR(mode), NULL); 348 | return INT2FIX(version); 349 | } 350 | 351 | /* 352 | * call-seq: 353 | * Mcrypt.algorithms -> Array 354 | * 355 | * Returns an array of all the supported algorithm names. 356 | */ 357 | static VALUE mck_algorithms(VALUE self) 358 | { 359 | VALUE rv; 360 | int size, i; 361 | char **list; 362 | 363 | list = mcrypt_list_algorithms(NULL, &size); 364 | rv = rb_ary_new2(size); 365 | for (i = 0; i < size; i++) { 366 | rb_ary_push(rv, rb_str_new2(list[i])); 367 | } 368 | mcrypt_free_p(list, size); 369 | 370 | return rv; 371 | } 372 | 373 | /* 374 | * call-seq: 375 | * Mcrypt.modes -> Array 376 | * 377 | * Returns an array of all the supported mode names. 378 | */ 379 | static VALUE mck_modes(VALUE self) 380 | { 381 | VALUE rv; 382 | int size, i; 383 | char **list; 384 | 385 | list = mcrypt_list_modes(NULL, &size); 386 | rv = rb_ary_new2(size); 387 | for (i = 0; i < size; i++) { 388 | rb_ary_push(rv, rb_str_new2(list[i])); 389 | } 390 | mcrypt_free_p(list, size); 391 | 392 | return rv; 393 | } 394 | 395 | /* 396 | * call-seq: 397 | * Mcrypt.block_algorithm?(algorithm) -> true or false 398 | * 399 | * Returns true if the specified algorithm operates in blocks. 400 | */ 401 | static VALUE mck_is_block_algorithm(VALUE self, VALUE algo) 402 | { 403 | algo = canonicalize_algorithm(algo); 404 | return TO_RB_BOOL(mcrypt_module_is_block_algorithm(RSTRING_PTR(algo),NULL)); 405 | } 406 | 407 | /* 408 | * call-seq: 409 | * Mcrypt.key_size(algorithm) -> Fixnum 410 | * 411 | * Returns the maximum key size of the specified algorithm. 412 | */ 413 | static VALUE mck_key_size(VALUE self, VALUE algo) 414 | { 415 | algo = canonicalize_algorithm(algo); 416 | return INT2FIX(mcrypt_module_get_algo_key_size(RSTRING_PTR(algo),NULL)); 417 | } 418 | 419 | /* 420 | * call-seq: 421 | * Mcrypt.block_size(algorithm) -> Fixnum 422 | * 423 | * Returns the block size of the specified algorithm. 424 | */ 425 | static VALUE mck_block_size(VALUE self, VALUE algo) 426 | { 427 | algo = canonicalize_algorithm(algo); 428 | return INT2FIX(mcrypt_module_get_algo_block_size(RSTRING_PTR(algo),NULL)); 429 | } 430 | 431 | /* 432 | * call-seq: 433 | * Mcrypt.key_sizes(algorithm) -> Array 434 | * 435 | * Returns the key sizes supported by the specified algorithm. 436 | */ 437 | static VALUE mck_key_sizes(VALUE self, VALUE algo) 438 | { 439 | int *sizes, num_of_sizes, max; 440 | algo = canonicalize_algorithm(algo); 441 | max = mcrypt_module_get_algo_key_size(RSTRING_PTR(algo), NULL); 442 | sizes = mcrypt_module_get_algo_supported_key_sizes(RSTRING_PTR(algo), NULL, &num_of_sizes); 443 | return enumerate_key_sizes(sizes, num_of_sizes, max); 444 | } 445 | 446 | /* 447 | * call-seq: 448 | * Mcrypt.block_algorithm_mode?(mode) -> true or false 449 | * 450 | * Returns true if the specified mode is for use with block algorithms. 451 | */ 452 | static VALUE mck_is_block_algorithm_mode(VALUE self, VALUE mode) 453 | { 454 | mode = to_s(mode); 455 | return TO_RB_BOOL(mcrypt_module_is_block_algorithm_mode(RSTRING_PTR(mode),NULL)); 456 | } 457 | 458 | /* 459 | * call-seq: 460 | * Mcrypt.block_mode?(mode) -> true or false 461 | * 462 | * Returns true if the specified mode operates in blocks. 463 | */ 464 | static VALUE mck_is_block_mode(VALUE self, VALUE mode) 465 | { 466 | mode = to_s(mode); 467 | return TO_RB_BOOL(mcrypt_module_is_block_mode(RSTRING_PTR(mode),NULL)); 468 | } 469 | 470 | /* 471 | * call-seq: 472 | * Mcrypt.algorithm_version(algorithm) -> Fixnum 473 | * 474 | * Returns the implementation version number of the specified algorithm. 475 | */ 476 | static VALUE mck_algorithm_version(VALUE self, VALUE algo) 477 | { 478 | algo = canonicalize_algorithm(algo); 479 | return INT2FIX(mcrypt_module_algorithm_version(RSTRING_PTR(algo), NULL)); 480 | } 481 | 482 | /* 483 | * call-seq: 484 | * Mcrypt.mode_version(mode) -> Fixnum 485 | * 486 | * Returns the implementation version number of the specified mode. 487 | */ 488 | static VALUE mck_mode_version(VALUE self, VALUE mode) 489 | { 490 | mode = to_s(mode); 491 | return INT2FIX(mcrypt_module_mode_version(RSTRING_PTR(mode), NULL)); 492 | } 493 | 494 | void Init_mcrypt() 495 | { 496 | /* look up once, use many */ 497 | sym_to_s = rb_intern("to_s"); 498 | sym_canonicalize_algorithm = rb_intern("canonicalize_algorithm"); 499 | 500 | /*= GLOBALS =*/ 501 | cMcrypt = rb_define_class("Mcrypt", rb_cObject); 502 | cInvalidAlgorithmOrModeError = rb_define_class_under(cMcrypt, "InvalidAlgorithmOrModeError", rb_eArgError); 503 | cMcryptRuntimeError = rb_define_class_under(cMcrypt, "RuntimeError", rb_eRuntimeError); 504 | rb_define_const(cMcrypt, "LIBMCRYPT_VERSION", rb_str_new2(LIBMCRYPT_VERSION)); 505 | rb_define_alloc_func(cMcrypt, mc_alloc); 506 | 507 | /*= INSTANCE METHODS =*/ 508 | rb_define_method(cMcrypt, "initialize", mc_initialize, -1); 509 | rb_define_method(cMcrypt, "generic_init", mc_generic_init, 0); 510 | rb_define_method(cMcrypt, "generic_deinit", mc_generic_deinit, 0); 511 | rb_define_method(cMcrypt, "encrypt_generic", mc_encrypt_generic, 1); 512 | rb_define_method(cMcrypt, "decrypt_generic", mc_decrypt_generic, 1); 513 | rb_define_method(cMcrypt, "key_size", mc_key_size, 0); 514 | rb_define_method(cMcrypt, "block_size", mc_block_size, 0); 515 | rb_define_method(cMcrypt, "iv_size", mc_iv_size, 0); 516 | rb_define_method(cMcrypt, "block_algorithm?", mc_is_block_algorithm, 0); 517 | rb_define_method(cMcrypt, "block_mode?", mc_is_block_mode, 0); 518 | rb_define_method(cMcrypt, "block_algorithm_mode?", mc_is_block_algorithm_mode, 0); 519 | rb_define_method(cMcrypt, "has_iv?", mc_mode_has_iv, 0); 520 | rb_define_method(cMcrypt, "key_sizes", mc_key_sizes, 0); 521 | rb_define_method(cMcrypt, "algorithm_version", mc_algorithm_version, 0); 522 | rb_define_method(cMcrypt, "mode_version", mc_mode_version, 0); 523 | 524 | /*= CLASS METHODS =*/ 525 | rb_define_singleton_method(cMcrypt, "algorithms", mck_algorithms, 0); 526 | rb_define_singleton_method(cMcrypt, "modes", mck_modes, 0); 527 | rb_define_singleton_method(cMcrypt, "block_algorithm?", mck_is_block_algorithm, 1); 528 | rb_define_singleton_method(cMcrypt, "key_size", mck_key_size, 1); 529 | rb_define_singleton_method(cMcrypt, "block_size", mck_block_size, 1); 530 | rb_define_singleton_method(cMcrypt, "key_sizes", mck_key_sizes, 1); 531 | rb_define_singleton_method(cMcrypt, "block_algorithm_mode?", mck_is_block_algorithm_mode, 1); 532 | rb_define_singleton_method(cMcrypt, "block_mode?", mck_is_block_mode, 1); 533 | rb_define_singleton_method(cMcrypt, "algorithm_version", mck_algorithm_version, 1); 534 | rb_define_singleton_method(cMcrypt, "mode_version", mck_mode_version, 1); 535 | 536 | /* TODO: 537 | Instance methods: 538 | (for copying) 539 | mcrypt_enc_get_state 540 | mcrypt_enc_set_state 541 | Maybe: 542 | self-tests 543 | */ 544 | } 545 | 546 | 547 | /* UTILITIES */ 548 | 549 | static VALUE to_s(VALUE o) 550 | { 551 | return rb_obj_is_kind_of(o,rb_cString) 552 | ? o : rb_funcall(o, sym_to_s, 0); 553 | } 554 | 555 | static VALUE canonicalize_algorithm(VALUE o) 556 | { 557 | return rb_funcall(cMcrypt, sym_canonicalize_algorithm, 1, o); 558 | } 559 | 560 | static char *dup_rbstring(VALUE o, int include_null) 561 | { 562 | char *rv; 563 | VALUE str = to_s(o); 564 | rv = malloc(RSTRING_LEN(str) + (include_null ? 1 : 0)); 565 | memcpy(rv, RSTRING_PTR(str), RSTRING_LEN(str)); 566 | if (include_null) 567 | rv[RSTRING_LEN(str)] = '\0'; 568 | return rv; 569 | } 570 | 571 | static VALUE enumerate_key_sizes(int *sizes, int num_of_sizes, int max_size) 572 | { 573 | int i; 574 | VALUE rv; 575 | if (sizes == NULL && num_of_sizes == 0) { 576 | rv = rb_ary_new2(max_size); 577 | for (i = 1; i <= max_size; i++) { 578 | rb_ary_push(rv, INT2FIX(i)); 579 | } 580 | return rv; 581 | } 582 | else if (num_of_sizes > 0) { 583 | rv = rb_ary_new2(num_of_sizes); 584 | for (i = 0; i < num_of_sizes; i++) { 585 | rb_ary_push(rv, INT2FIX(sizes[i])); 586 | } 587 | free(sizes); 588 | return rv; 589 | } 590 | else { 591 | rb_raise(rb_eFatal, "mcrypt_enc_get_supported_key_sizes returned invalid result."); 592 | return Qnil; /* quell warning */ 593 | } 594 | } 595 | 596 | static int safe_len(long orig) 597 | { 598 | int result = (int)orig; 599 | if (result != orig) { 600 | rb_raise(cMcryptRuntimeError, "The string is too large. " 601 | "This version of mcrypt can only handle %d bytes (32-bit signed int)", 602 | INT_MAX); 603 | } 604 | return result; 605 | } 606 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle 2 | *.swp 3 | *.so 4 | -------------------------------------------------------------------------------- /lib/mcrypt.rb: -------------------------------------------------------------------------------- 1 | # 2 | # mcrypt.rb 3 | # 4 | # Copyright (c) 2009-2013 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 block_algorithm?. 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 block_mode?. 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 block_algorithm_mode?. 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), :pkcs or :zeros. 153 | # See padding=. 154 | def padding 155 | @padding 156 | end 157 | 158 | # Set the cryptographic key to be used. This is the final raw 159 | # binary representation 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 final 175 | # raw binary representation 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 | # padding_type.to_s 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 always 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 473 | -------------------------------------------------------------------------------- /ruby-mcrypt.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{ruby-mcrypt} 8 | s.version = "0.2.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Philip Garrett"] 12 | s.date = %q{2013-02-24} 13 | s.description = %q{= Mcrypt - libmcrypt bindings for Ruby 14 | 15 | Mcrypt provides Ruby-language bindings for libmcrypt(3), a 16 | symmetric cryptography library. {Libmcrypt}[http://mcrypt.sourceforge.net/] 17 | supports lots of different ciphers and encryption modes. 18 | 19 | == You will need 20 | 21 | * A working Ruby installation (>= 1.8.6 or 1.9) 22 | * A working libmcrypt installation (2.5.x or 2.6.x, tested with 2.5.8) 23 | * A sane build environment 24 | 25 | == Installation 26 | 27 | Install the gem: 28 | gem install ruby-mcrypt --test -- --with-mcrypt-dir=/path/to/mcrypt/prefix 29 | 30 | If you're installing on Ubuntu: 31 | sudo apt-get install mcrypt libmcrypt-dev 32 | gem install ruby-mcrypt 33 | 34 | If you want to run the longer test suite, do this instead: 35 | MCRYPT_TEST_BRUTE=1 \ 36 | gem install ruby-mcrypt --test -- --with-mcrypt-dir=/path/to/mcrypt/prefix 37 | 38 | Put this in your code: 39 | require 'rubygems' 40 | require 'mcrypt' 41 | 42 | Or in Rails' environment.rb: 43 | gem "ruby-mcrypt", :lib => "mcrypt" 44 | 45 | == Usage 46 | 47 | crypto = Mcrypt.new(:twofish, :cbc, MY_KEY, MY_IV, :pkcs) 48 | 49 | # encryption and decryption in one step 50 | ciphertext = crypto.encrypt(plaintext) 51 | plaintext = crypto.decrypt(ciphertext) 52 | 53 | # encrypt in smaller steps 54 | while chunk = $stdin.read(4096) 55 | $stdout << crypto.encrypt_more(chunk) 56 | end 57 | $stdout << crypto.encrypt_finish 58 | 59 | # or decrypt: 60 | while chunk = $stdin.read(4096) 61 | $stdout << crypto.decrypt_more(chunk) 62 | end 63 | $stdout << crypto.decrypt_finish 64 | 65 | == Known Issues 66 | 67 | * Test coverage is lacking. 68 | 69 | If you find any bugs, please let the author know. 70 | 71 | == Wish List 72 | 73 | * IO-like behavior, e.g. crypto.open($stdin) { |stream| ... } 74 | 75 | == Author 76 | 77 | * Philip Garrett 78 | 79 | == Copyright and License 80 | 81 | Copyright (c) 2009-2013 Philip Garrett. 82 | 83 | Permission is hereby granted, free of charge, to any person obtaining a 84 | copy of this software and associated documentation files (the 85 | "Software"), to deal in the Software without restriction, including 86 | without limitation the rights to use, copy, modify, merge, publish, 87 | distribute, sublicense, and/or sell copies of the Software, and to 88 | permit persons to whom the Software is furnished to do so, subject to 89 | the following conditions: 90 | 91 | The above copyright notice and this permission notice shall be included 92 | in all copies or substantial portions of the Software. 93 | 94 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 95 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 96 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 97 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 98 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 99 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 100 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 101 | } 102 | s.email = %q{philgarr@gmail.com} 103 | s.extensions = ["ext/extconf.rb"] 104 | s.extra_rdoc_files = [ 105 | "README.rdoc" 106 | ] 107 | s.files = [ 108 | "Manifest", 109 | "README.rdoc", 110 | "Rakefile", 111 | "VERSION", 112 | "cross.sh", 113 | "ext/.gitignore", 114 | "ext/extconf.rb", 115 | "ext/mcrypt_wrapper.c", 116 | "lib/.gitignore", 117 | "lib/mcrypt.rb", 118 | "ruby-mcrypt.gemspec", 119 | "test/generate/.gitignore", 120 | "test/generate/Makefile", 121 | "test/generate/generate_testcases.c", 122 | "test/helper.rb", 123 | "test/test_basics.rb", 124 | "test/test_brute.rb", 125 | "test/test_reciprocity.rb" 126 | ] 127 | s.homepage = %q{http://github.com/kingpong/ruby-mcrypt} 128 | s.require_paths = ["lib"] 129 | s.required_ruby_version = Gem::Requirement.new(">= 1.8.6") 130 | s.requirements = ["libmcrypt (2.5.x or 2.6.x, tested with 2.5.8)"] 131 | s.rubygems_version = %q{1.3.6} 132 | s.summary = %q{Ruby bindings for libmcrypt} 133 | 134 | if s.respond_to? :specification_version then 135 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 136 | s.specification_version = 3 137 | 138 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 139 | else 140 | end 141 | else 142 | end 143 | end 144 | 145 | -------------------------------------------------------------------------------- /test/generate/.gitignore: -------------------------------------------------------------------------------- 1 | generate_testcases 2 | generate_testcases.o 3 | generate_testcases.dSYM 4 | -------------------------------------------------------------------------------- /test/generate/Makefile: -------------------------------------------------------------------------------- 1 | 2 | LOADLIBES=-lmcrypt 3 | CFLAGS=-O2 4 | 5 | CPPFLAGS=-I/opt/local/include 6 | LDFLAGS=-L/opt/local/lib 7 | 8 | generate_testcases: generate_testcases.c 9 | 10 | memcheck: generate_testcases 11 | valgrind --leak-check=yes ./generate_testcases > /dev/null 12 | -------------------------------------------------------------------------------- /test/generate/generate_testcases.c: -------------------------------------------------------------------------------- 1 | /* 2 | * generate_testcases.c 3 | * 4 | * Generates a list of known-good inputs/outputs to be compared against the 5 | * output of ruby-mcrypt. 6 | * 7 | * Copyright (c) 2009-2013 Philip Garrett. 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a 10 | * copy of this software and associated documentation files (the 11 | * "Software"), to deal in the Software without restriction, including 12 | * without limitation the rights to use, copy, modify, merge, publish, 13 | * distribute, sublicense, and/or sell copies of the Software, and to 14 | * permit persons to whom the Software is furnished to do so, subject to 15 | * the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included 18 | * in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 21 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 26 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | typedef enum { PADDING_NONE, PADDING_PKCS7, PADDING_ZEROS } padding_t; 36 | const char *padding_name(padding_t padding); 37 | 38 | unsigned char *gen_rand_data(unsigned int len); 39 | char *dump_value(void *buffer, int len); 40 | 41 | void dump_testcase(char *algo, char *mode); 42 | void dump_testcase_keysize(MCRYPT td, int key_size); 43 | void dump_testcase_block(MCRYPT td, unsigned char *key, int key_size, 44 | unsigned char *iv, int iv_size, int block_size, 45 | padding_t padding); 46 | 47 | int *list_key_sizes(MCRYPT td); 48 | 49 | static char testing_algo[128]; 50 | static char testing_mode[128]; 51 | 52 | int main() { 53 | char **algos, **modes; 54 | int n_algos, n_modes, 55 | i_algo, i_mode; 56 | 57 | /* get consistent output even though it's "random" */ 58 | srand48(1153616166L); 59 | 60 | algos = mcrypt_list_algorithms(NULL, &n_algos); 61 | if (algos == NULL) { 62 | fprintf(stderr, "Unable to list algorithms.\n"); 63 | exit(1); 64 | } 65 | 66 | modes = mcrypt_list_modes(NULL, &n_modes); 67 | if (modes == NULL) { 68 | fprintf(stderr, "Unable to list modes.\n"); 69 | exit(1); 70 | } 71 | 72 | for (i_algo = 0; i_algo < n_algos; i_algo++) { 73 | for (i_mode = 0; i_mode < n_modes; i_mode++) { 74 | dump_testcase(algos[i_algo],modes[i_mode]); 75 | } 76 | } 77 | 78 | mcrypt_free_p(algos, n_algos); 79 | mcrypt_free_p(modes, n_modes); 80 | 81 | return 0; 82 | } 83 | 84 | void dump_testcase(char *algo, char *mode) { 85 | MCRYPT td; 86 | int *key_sizes, *cur_size; 87 | 88 | /* globalize this for easier access below */ 89 | strcpy(testing_algo, algo); 90 | strcpy(testing_mode, mode); 91 | 92 | td = mcrypt_module_open(algo, NULL, mode, NULL); 93 | if (td == MCRYPT_FAILED) { 94 | /* assume that this algorithm just doesn't support this mode */ 95 | return; 96 | } 97 | 98 | cur_size = key_sizes = list_key_sizes(td); 99 | if (key_sizes == NULL) { 100 | return; 101 | } 102 | 103 | while (*cur_size != 0) { 104 | dump_testcase_keysize(td, *cur_size); 105 | cur_size++; 106 | } 107 | free(key_sizes); 108 | 109 | mcrypt_module_close(td); 110 | } 111 | 112 | void dump_testcase_keysize(MCRYPT td, int key_size) { 113 | unsigned char *key, *iv = NULL; 114 | int iv_size, block_size; 115 | int mc_ret; 116 | padding_t padding; 117 | 118 | key = gen_rand_data(key_size); 119 | 120 | iv_size = mcrypt_enc_get_iv_size(td); 121 | if (iv_size != 0) { 122 | iv = gen_rand_data(iv_size); 123 | } 124 | 125 | /* 126 | * Generate a test case for these plaintext sizes: 127 | * 128 | * * empty: test behavior of padding on empty blocks 129 | * * 5-byte: odd, and smaller than any block size 130 | * * block_size - 1: edge case 131 | * * block_size: generally guaranteed to work 132 | * * block_size * 2 - 1: multiblock edge case 133 | * * block_size * 3: more multiblock 134 | */ 135 | block_size = mcrypt_enc_get_block_size(td); 136 | 137 | padding = mcrypt_enc_is_block_mode(td) ? PADDING_PKCS7 : PADDING_NONE; 138 | 139 | for (; padding <= PADDING_ZEROS; padding++) { 140 | dump_testcase_block(td, key, key_size, iv, iv_size, 0, padding); 141 | dump_testcase_block(td, key, key_size, iv, iv_size, 5, padding); 142 | dump_testcase_block(td, key, key_size, iv, iv_size, block_size - 1, 143 | padding); 144 | dump_testcase_block(td, key, key_size, iv, iv_size, block_size, 145 | padding); 146 | dump_testcase_block(td, key, key_size, iv, iv_size, 147 | block_size * 2 - 1, padding); 148 | dump_testcase_block(td, key, key_size, iv, iv_size, block_size * 3, 149 | padding); 150 | } 151 | 152 | free(key); 153 | if (iv) free(iv); 154 | 155 | return; 156 | } 157 | 158 | void dump_testcase_block(MCRYPT td, unsigned char *key, int key_size, 159 | unsigned char *iv, int iv_size, int data_size, 160 | padding_t padding) { 161 | int mc_ret; 162 | int is_block, block_size, block_overlap, block_fill; 163 | int i; 164 | unsigned char *plaintext, *ciphertext; 165 | 166 | mc_ret = mcrypt_generic_init(td, (void *)key, key_size, (void *)iv); 167 | if (mc_ret < 0) { 168 | mcrypt_perror(mc_ret); 169 | return; 170 | } 171 | 172 | plaintext = gen_rand_data(data_size); 173 | if (plaintext == NULL) { 174 | return; 175 | } 176 | 177 | is_block = mcrypt_enc_is_block_mode(td); 178 | if (is_block) { 179 | block_size = mcrypt_enc_get_block_size(td); 180 | block_overlap = data_size % block_size; 181 | block_fill = block_size - block_overlap; 182 | if (padding == PADDING_NONE) { 183 | /* do nothing */ 184 | } 185 | else if (padding == PADDING_PKCS7) { 186 | if (block_fill == 0) { 187 | /* ALWAYS add padding */ 188 | block_fill = block_size; 189 | } 190 | plaintext = (unsigned char *)realloc(plaintext, 191 | data_size + block_fill); 192 | for (i = 0; i < block_fill; i++) { 193 | plaintext[data_size+i] = block_fill; 194 | } 195 | data_size = data_size + block_fill; 196 | if ((data_size % block_size) != 0) { 197 | fprintf(stderr, "bad data size!\n"); 198 | exit(1); 199 | } 200 | } 201 | else if (padding == PADDING_ZEROS) { 202 | if (block_overlap != 0) { 203 | plaintext = (unsigned char *)realloc(plaintext, 204 | data_size + block_fill); 205 | for (i = 0; i < block_fill; i++) { 206 | plaintext[data_size+i] = '\0'; 207 | } 208 | data_size = data_size + block_fill; 209 | } 210 | } 211 | else { 212 | fprintf(stderr, "bad error\n"); 213 | exit(1); 214 | } 215 | } 216 | 217 | ciphertext = malloc(data_size); 218 | if (ciphertext == NULL) { 219 | fprintf(stderr, "Out of memory\n"); 220 | return; 221 | } 222 | 223 | memcpy( (void *)ciphertext, (void *)plaintext, data_size); 224 | 225 | mc_ret = mcrypt_generic(td, ciphertext, data_size); 226 | if (mc_ret == 0) { 227 | char *enc_key, *enc_iv, *enc_pt, *enc_ct; 228 | enc_key = dump_value( (void *)key, key_size ); 229 | enc_iv = dump_value( (void *)iv, iv_size ); 230 | enc_pt = dump_value( (void *)plaintext, data_size ); 231 | enc_ct = dump_value( (void *)ciphertext, data_size ); 232 | 233 | printf("algo=%s,mode=%s,key=%s,iv=%s,padding=%s,pt=%s,ct=%s\n", 234 | testing_algo, testing_mode, enc_key, enc_iv, 235 | padding_name(padding), enc_pt, enc_ct); 236 | 237 | free(enc_key); 238 | free(enc_iv); 239 | free(enc_pt); 240 | free(enc_ct); 241 | } 242 | 243 | free(plaintext); 244 | free(ciphertext); 245 | 246 | mc_ret = mcrypt_generic_deinit(td); 247 | if (mc_ret < 0) { 248 | fprintf(stderr, "Error %d during deinit of %s in %s mode" 249 | " (%d-byte key)\n", mc_ret, testing_algo, testing_mode, key_size); 250 | return; 251 | } 252 | } 253 | 254 | /* 255 | * padding_name(padding) 256 | * 257 | * Returns the string name for the padding type. 258 | * 259 | */ 260 | 261 | const char *padding_name(padding_t padding) { 262 | switch(padding) { 263 | case PADDING_NONE: return "none"; 264 | case PADDING_PKCS7: return "pkcs"; 265 | case PADDING_ZEROS: return "zeros"; 266 | default: fprintf(stderr, "Invalid padding type\n"); 267 | exit(1); 268 | } 269 | } 270 | 271 | 272 | /* 273 | * list_key_sizes(td) 274 | * 275 | * Returns a pointer to an array of integer key sizes for the mcrypt 276 | * handle. This returns the actual list, as opposed to 277 | * mcrypt_enc_get_supported_key_sizes which sometimes just returns a 278 | * formula to create the list. 279 | * 280 | * The list is terminated with a zero. 281 | * 282 | */ 283 | int *list_key_sizes(MCRYPT td) { 284 | int *list; 285 | int *key_sizes, n_key_sizes, i; 286 | 287 | key_sizes = mcrypt_enc_get_supported_key_sizes(td, &n_key_sizes); 288 | 289 | if (!key_sizes && !n_key_sizes) { 290 | int max_size = mcrypt_enc_get_key_size(td); 291 | list = malloc( (max_size + 1) * sizeof(int) ); 292 | if (!list) { 293 | fprintf(stderr, "Out of memory\n"); 294 | return NULL; 295 | } 296 | 297 | for (i = 0; i < max_size; i++) { 298 | list[i] = i; 299 | } 300 | list[max_size] = 0; 301 | } 302 | else { 303 | list = malloc( (n_key_sizes + 1) * sizeof(int) ); 304 | if (!list) { 305 | fprintf(stderr, "Out of memory\n"); 306 | return NULL; 307 | } 308 | 309 | for (i = 0; i < n_key_sizes; i++) { 310 | list[i] = key_sizes[i]; 311 | } 312 | list[n_key_sizes] = 0; 313 | 314 | free(key_sizes); 315 | } 316 | 317 | return list; 318 | } 319 | 320 | 321 | /* 322 | * gen_rand_data(len) 323 | * 324 | * Returns a random key of byte length len. You need to free it when 325 | * you're done with it. 326 | * 327 | * This isn't truly random but it doesn't have to be as it's just 328 | * generating test cases. 329 | * 330 | */ 331 | unsigned char *gen_rand_data(unsigned int len) { 332 | unsigned char *key, *p; 333 | unsigned int i; 334 | 335 | p = key = malloc(len); 336 | if (key == NULL) { 337 | return NULL; 338 | } 339 | 340 | for (i = 0; i < len; i++) { 341 | *p++ = (unsigned char)(255L * drand48()); 342 | } 343 | 344 | return key; 345 | } 346 | 347 | 348 | /* 349 | * dump_value(void *buffer, int len) 350 | * 351 | * Returns a string containing the buffer's contents encoded as 352 | * two-character hexadecimal bytes separated by spaces. 353 | * 354 | * Free it when you're done. 355 | * 356 | */ 357 | 358 | char *dump_value(void *buffer, int len) { 359 | char *enc, *p; 360 | unsigned char *buf; 361 | int i; 362 | const char hex_digits[] = { '0','1','2','3','4','5','6','7','8','9', 363 | 'a','b','c','d','e','f' }; 364 | 365 | buf = (unsigned char *)buffer; 366 | 367 | if (len == 0) { 368 | return strdup(""); 369 | } 370 | 371 | p = enc = malloc(len * 3); 372 | if (enc == NULL) { 373 | return NULL; 374 | } 375 | 376 | *p = '\0'; 377 | 378 | for(i = 0; i < len; i++) { 379 | if (i != 0) { 380 | (*p++) = ' '; 381 | } 382 | (*p++) = hex_digits[buf[i] >> 4]; 383 | (*p++) = hex_digits[buf[i] & 0xF]; 384 | } 385 | (*p++) = '\0'; 386 | 387 | return enc; 388 | } 389 | 390 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | 2 | $VERBOSE = false 3 | require 'test/unit' 4 | 5 | begin 6 | here = File.dirname(__FILE__) 7 | %w(lib bin test).each do |dir| 8 | path = "#{here}/../#{dir}" 9 | $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path) 10 | end 11 | end 12 | 13 | require 'mcrypt' 14 | -------------------------------------------------------------------------------- /test/test_basics.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.join(File.dirname(__FILE__),"helper.rb") 4 | 5 | class McryptBasicsTest < Test::Unit::TestCase 6 | 7 | def test_instantiate_basic 8 | mc = Mcrypt.new(:tripledes, :cbc) 9 | assert_not_nil mc 10 | end 11 | 12 | def test_invalid_algorithm_or_mode 13 | # blatantly incorrect algorithm 14 | assert_raise Mcrypt::InvalidAlgorithmOrModeError do 15 | Mcrypt.new(:no_such_algorithm, :cbc) 16 | end 17 | # blatantly incorrect mode 18 | assert_raise Mcrypt::InvalidAlgorithmOrModeError do 19 | Mcrypt.new(:tripledes, :no_such_mode) 20 | end 21 | # bad combination of otherwise valid algo/mode 22 | assert_raise Mcrypt::InvalidAlgorithmOrModeError do 23 | Mcrypt.new(:wake, :cbc) 24 | end 25 | end 26 | 27 | def test_canonicalization 28 | assert_equal "rijndael-256", Mcrypt.new(:rijndael_256, :cfb).algorithm 29 | assert_equal "rijndael-256", Mcrypt.new("rijndael-256", :cfb).algorithm 30 | end 31 | 32 | def test_mode_accessor 33 | assert_equal "cfb", Mcrypt.new(:rijndael_256, :cfb).mode 34 | assert_equal "cfb", Mcrypt.new(:rijndael_256, "cfb").mode 35 | end 36 | 37 | def test_padding_initialization 38 | assert_equal :pkcs, Mcrypt.new(:twofish, :cfb, nil, nil, true).padding 39 | assert_equal :pkcs, Mcrypt.new(:twofish, :cfb, nil, nil, :pkcs).padding 40 | assert_equal :pkcs, Mcrypt.new(:twofish, :cfb, nil, nil, "pkcs").padding 41 | assert_equal :pkcs, Mcrypt.new(:twofish, :cfb, nil, nil, "pkcs7").padding 42 | assert_equal :pkcs, Mcrypt.new(:twofish, :cfb, nil, nil, "pkcs5").padding 43 | 44 | assert_equal :zeros, Mcrypt.new(:twofish, :cfb, nil, nil, :zeros).padding 45 | assert_equal :zeros, Mcrypt.new(:twofish, :cfb, nil, nil, :zeroes).padding 46 | 47 | assert_equal false, Mcrypt.new(:twofish, :cfb, nil, nil, :none).padding 48 | assert_equal false, Mcrypt.new(:twofish, :cfb, nil, nil, "none").padding 49 | assert_equal false, Mcrypt.new(:twofish, :cfb, nil, nil, false).padding 50 | assert_equal false, Mcrypt.new(:twofish, :cfb, nil, nil, nil).padding 51 | assert_equal false, Mcrypt.new(:twofish, :cfb, nil, nil, "").padding 52 | end 53 | 54 | def test_key_size 55 | assert_equal 24, Mcrypt.new(:tripledes, :cbc).key_size 56 | end 57 | 58 | def test_block_size 59 | end 60 | 61 | def test_key_size 62 | assert_equal 24, Mcrypt.new(:tripledes, :cbc).key_size 63 | end 64 | 65 | def test_block_size 66 | assert_equal 32, Mcrypt.new(:rijndael_256, :cfb).block_size 67 | assert_equal 8, Mcrypt.new(:des, :cbc).block_size 68 | end 69 | 70 | def test_iv_size 71 | assert_equal 32, Mcrypt.new(:rijndael_256, :cfb).iv_size 72 | assert_equal 8, Mcrypt.new(:des, :cbc).iv_size 73 | assert_nil Mcrypt.new(:tripledes, :ecb).iv_size 74 | end 75 | 76 | def test_block_algorithm_b 77 | assert_equal true, Mcrypt.new(:tripledes, :cbc).block_algorithm? 78 | assert_equal false, Mcrypt.new(:wake, :stream).block_algorithm? 79 | end 80 | 81 | def test_block_mode_b 82 | assert_equal true, Mcrypt.new(:tripledes, :cbc).block_mode? 83 | assert_equal false, Mcrypt.new(:wake, :stream).block_mode? 84 | end 85 | 86 | def test_block_algorithm_mode_b 87 | assert_equal true, Mcrypt.new(:tripledes, :cbc).block_algorithm_mode? 88 | assert_equal false, Mcrypt.new(:wake, :stream).block_algorithm_mode? 89 | end 90 | 91 | def test_mode_has_iv_b 92 | assert_equal true, Mcrypt.new(:tripledes, :cbc).has_iv? 93 | assert_equal false, Mcrypt.new(:tripledes, :ecb).has_iv? 94 | end 95 | 96 | def test_key_sizes 97 | assert_equal [16,24,32], Mcrypt.new(:twofish, :cbc).key_sizes 98 | assert_equal [32], Mcrypt.new(:wake, :stream).key_sizes 99 | assert_equal (1..128).to_a, Mcrypt.new(:rc2, :cbc).key_sizes 100 | end 101 | 102 | def test_algorithm_version 103 | assert_kind_of Integer, Mcrypt.new(:rijndael_256, :cfb).algorithm_version 104 | end 105 | 106 | def test_mode_version 107 | assert_kind_of Integer, Mcrypt.new(:rijndael_256, :cfb).mode_version 108 | end 109 | 110 | # CLASS METHODS 111 | def test_class_algorithms 112 | assert_equal ['tripledes','twofish'], Mcrypt.algorithms.grep(/tripledes|twofish/).sort 113 | end 114 | 115 | def test_class_modes 116 | assert_equal ['cbc','cfb'], Mcrypt.modes.grep(/\A(cbc|cfb)\Z/).sort 117 | end 118 | 119 | def test_class_block_algorithm_b 120 | assert_equal true, Mcrypt.block_algorithm?(:tripledes) 121 | assert_equal false, Mcrypt.block_algorithm?(:wake) 122 | end 123 | 124 | def test_class_key_size 125 | assert_equal 24, Mcrypt.key_size(:tripledes) 126 | end 127 | 128 | def test_class_block_size 129 | assert_equal 32, Mcrypt.block_size(:rijndael_256) 130 | assert_equal 8, Mcrypt.block_size(:des) 131 | end 132 | 133 | def test_class_key_sizes 134 | assert_equal [16,24,32], Mcrypt.key_sizes(:twofish) 135 | assert_equal [32], Mcrypt.key_sizes(:wake) 136 | assert_equal (1..128).to_a, Mcrypt.key_sizes(:rc2) 137 | end 138 | 139 | def test_class_block_algorithm_mode_b 140 | assert_equal true, Mcrypt.block_algorithm_mode?(:cbc) 141 | assert_equal true, Mcrypt.block_algorithm_mode?(:cfb) 142 | assert_equal false, Mcrypt.block_algorithm_mode?(:stream) 143 | end 144 | 145 | def test_class_block_mode_b 146 | assert_equal true, Mcrypt.block_mode?(:cbc) 147 | assert_equal false, Mcrypt.block_mode?(:cfb) 148 | assert_equal false, Mcrypt.block_mode?(:stream) 149 | end 150 | 151 | def test_class_algorithm_version 152 | assert_kind_of Integer, Mcrypt.algorithm_version(:rijndael_256) 153 | end 154 | 155 | def test_class_mode_version 156 | assert_kind_of Integer, Mcrypt.mode_version(:cfb) 157 | end 158 | 159 | def test_class_algorithm_info 160 | info = Mcrypt.algorithm_info(:rijndael_256) 161 | assert_equal true, info[:block_algorithm] 162 | assert_equal 32, info[:block_size] 163 | assert_equal 32, info[:key_size] 164 | assert_equal [16,24,32], info[:key_sizes] 165 | assert_kind_of Integer, info[:algorithm_version] 166 | end 167 | 168 | def test_class_mode_info 169 | info = Mcrypt.mode_info(:cbc) 170 | assert_equal true, info[:block_mode] 171 | assert_equal true, info[:block_algorithm_mode] 172 | assert_kind_of Integer, info[:mode_version] 173 | 174 | info = Mcrypt.mode_info(:cfb) 175 | assert_equal false, info[:block_mode] 176 | assert_equal true, info[:block_algorithm_mode] 177 | assert_kind_of Integer, info[:mode_version] 178 | 179 | info = Mcrypt.mode_info(:stream) 180 | assert_equal false, info[:block_mode] 181 | assert_equal false, info[:block_algorithm_mode] 182 | assert_kind_of Integer, info[:mode_version] 183 | end 184 | 185 | def test_bad_key_length_is_rejected 186 | assert_raise Mcrypt::InvalidKeyError do 187 | Mcrypt.new(:tripledes,:cbc,"0"*64) 188 | end 189 | end 190 | 191 | def test_good_key_length_is_not_rejected 192 | assert_nothing_raised do 193 | Mcrypt.new(:tripledes,:cbc,"0"*24) 194 | end 195 | end 196 | 197 | def test_bad_iv_length_is_rejected 198 | assert_raise Mcrypt::InvalidIVError do 199 | Mcrypt.new(:tripledes,:cbc,"0"*24,"0"*32) 200 | end 201 | end 202 | 203 | def test_good_iv_length_is_not_rejected 204 | assert_nothing_raised do 205 | Mcrypt.new(:tripledes,:cbc,"0"*24,"0"*8) 206 | end 207 | end 208 | 209 | def test_unused_iv_is_rejected 210 | # positive case is tested implicitly in test_good_iv_length_is_not_rejected 211 | assert_raise Mcrypt::InvalidIVError do 212 | Mcrypt.new(:tripledes,:ecb,"0"*24,"0"*8) 213 | end 214 | end 215 | 216 | end 217 | -------------------------------------------------------------------------------- /test/test_reciprocity.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.join(File.dirname(__FILE__),"helper.rb") 4 | 5 | class McryptReciprocityTest < Test::Unit::TestCase 6 | 7 | def generate(len) 8 | "0" * len 9 | end 10 | 11 | def make_mcrypt(algo,mode,padding) 12 | mc = Mcrypt.new(algo,mode) 13 | mc.key = generate(mc.key_size) 14 | mc.iv = generate(mc.iv_size) if mc.has_iv? 15 | mc.padding = padding 16 | mc 17 | end 18 | 19 | # test a few algorithms 20 | # with both stream and block modes, 21 | # with different padding implementations 22 | # with different input sizes 23 | # with on-boundary and off-boundary 24 | 25 | [:tripledes,:twofish,:rijndael_256].each do |algorithm| 26 | [:cbc, :cfb, :ecb].each do |mode| 27 | [:zeros,:pkcs,:none].each do |padding_type| 28 | [1,2,3].each do |blocks| 29 | 30 | define_method("test_#{algorithm}_#{mode}_#{padding_type}_#{blocks}_on_boundary") do 31 | mc = make_mcrypt(algorithm,mode,padding_type) 32 | plaintext = generate(mc.block_size * blocks) 33 | assert_equal plaintext, mc.decrypt(mc.encrypt(plaintext)) 34 | end 35 | 36 | # off-boundary only works without padding for stream modes 37 | if padding_type != :none || Mcrypt.stream_mode?(mode) 38 | define_method("test_#{algorithm}_#{mode}_#{padding_type}_#{blocks}_off_boundary") do 39 | mc = make_mcrypt(algorithm,mode,padding_type) 40 | plaintext = generate((mc.block_size * blocks) - 1) 41 | assert_equal plaintext, mc.decrypt(mc.encrypt(plaintext)) 42 | end 43 | end 44 | 45 | end 46 | end 47 | end 48 | end 49 | 50 | end 51 | --------------------------------------------------------------------------------