diff --git a/ChangeLog b/ChangeLog index 52c65022fe..fd5b7dfe09 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +Sat Oct 14 08:15:42 2006 Akinori MUSHA + + * ext/digest/digest.c, ext/digest/digest.h, + ext/digest/md5/md5init.c, ext/digest/rmd160/rmd160init.c, + ext/digest/sha1/sha1init.c, ext/digest/sha2/sha2init.c: + Introduce API versioning. + + * ext/digest/digest.c, ext/digest/digest.h, + ext/digest/md5/md5init.c, ext/digest/rmd160/rmd160init.c, + ext/digest/sha1/sha1init.c, ext/digest/sha2/sha2init.c: Remove + the constants DIGEST_LENGTH and BLOCK_LENGTH and turn them into + instance methods digest_length() and block_length(). Class + methods with the same names are also provided, which take extra + parameters for a digest method. + + * ext/digest/lib/digest/hmac.rb: Completely redesign the API which + is similar to Perl's, now that Digest classes can take hashing + parameters. + Sat Oct 14 05:54:05 2006 Akinori MUSHA * ext/digest/digest.c: Improve RDoc documentation further more. diff --git a/ext/digest/digest.c b/ext/digest/digest.c index 9d56cb89c4..65a78c0d6c 100644 --- a/ext/digest/digest.c +++ b/ext/digest/digest.c @@ -45,6 +45,13 @@ get_digest_base_metadata(VALUE klass) Data_Get_Struct(obj, algo_t, algo); + if (algo->api_version != 1) { + /* + * put conversion here if possible when API is updated + */ + rb_raise(rb_eRuntimeError, "Incompatible digest API version"); + } + return algo; } @@ -231,7 +238,8 @@ rb_digest_base_reset(VALUE self) * * Updates the digest using a given _string_ and returns self. * - * Implementation subclasses must redefine this method. + * Implementation subclasses must redefine this method, and should + * make `<<' an alias to it. */ static VALUE rb_digest_base_update(VALUE self, VALUE str) @@ -260,7 +268,8 @@ rb_digest_base_update(VALUE self, VALUE str) * * Calls update(string). * - * Subclasses need not redefine this method. + * Implementation subclasses need not but should alias this method to + * update() to eliminate chain calls. */ static VALUE rb_digest_base_lshift(VALUE self, VALUE str) @@ -407,6 +416,106 @@ rb_digest_base_equal(VALUE self, VALUE other) return Qfalse; } +/* + * call-seq: + * Digest::ALGORITHM.block_length(...) -> integer + * + * Returns the digest length of the digest algorithm. Parameters + * follow the same specification as the constructor. + * + * If an implementation subclass does not redefine this method, + * returns Digest::ALGORITHM.new(...).digest_length(). + */ +static VALUE +rb_digest_base_s_digest_length(int argc, VALUE *argv,VALUE klass) +{ + algo_t *algo; + + algo = get_digest_base_metadata(klass); + + if (algo == NULL) { + /* Subclasses really should redefine this method */ + VALUE obj = rb_funcall2(klass, id_new, argc, argv); + return rb_funcall(obj, rb_intern("digest_length"), 0); + } + + return INT2NUM(algo->digest_len); +} + +/* + * call-seq: + * digest_obj.block_length -> integer + * + * Returns the length of the hash value of the digest object. + * + * If an implementation subclass does not redefine this method, + * returns digest_obj.digest().length(). + */ +static VALUE +rb_digest_base_digest_length(VALUE self) +{ + algo_t *algo; + + algo = get_digest_base_metadata(rb_obj_class(self)); + + if (algo == NULL) { + /* subclasses really should redefine this method */ + VALUE digest = rb_funcall(self, id_digest, 0); + StringValue(digest); + return INT2NUM(RSTRING_LEN(digest)); + } + + return INT2NUM(algo->digest_len); +} + +/* + * call-seq: + * Digest::ALGORITHM.block_length(...) -> integer + * + * Returns the block length of the digest algorithm. Parameters + * follow the same specification as the constructor. + * + * If an implementation subclass does not redefine this method, + * returns Digest::ALGORITHM.new(...).block_length(). + */ +static VALUE +rb_digest_base_s_block_length(int argc, VALUE *argv,VALUE klass) +{ + algo_t *algo; + + algo = get_digest_base_metadata(klass); + + if (algo == NULL) { + VALUE obj = rb_funcall2(klass, id_new, argc, argv); + return rb_funcall(obj, rb_intern("block_length"), 0); + } + + return INT2NUM(algo->block_len); +} + +/* + * call-seq: + * digest_obj.block_length -> length + * + * Returns the block length of the digest. + * + * Implementation subclasses must redefine this method if used. + */ +static VALUE +rb_digest_base_block_length(VALUE self) +{ + algo_t *algo; + + algo = get_digest_base_metadata(rb_obj_class(self)); + + if (algo == NULL) { + /* subclasses must define this method (only if used) */ + rb_notimplement(); + } + + return INT2NUM(algo->block_len); +} + void Init_digest(void) { @@ -418,6 +527,9 @@ Init_digest(void) rb_define_singleton_method(cDigest_Base, "digest", rb_digest_base_s_digest, -1); rb_define_singleton_method(cDigest_Base, "hexdigest", rb_digest_base_s_hexdigest, -1); + rb_define_singleton_method(cDigest_Base, "digest_length", rb_digest_base_s_digest_length, -1); + rb_define_singleton_method(cDigest_Base, "block_length", rb_digest_base_s_block_length, -1); + rb_define_method(cDigest_Base, "initialize_copy", rb_digest_base_copy, 1); rb_define_method(cDigest_Base, "reset", rb_digest_base_reset, 0); rb_define_method(cDigest_Base, "update", rb_digest_base_update, 1); @@ -428,6 +540,9 @@ Init_digest(void) rb_define_method(cDigest_Base, "inspect", rb_digest_base_inspect, 0); rb_define_method(cDigest_Base, "==", rb_digest_base_equal, 1); + rb_define_method(cDigest_Base, "digest_length", rb_digest_base_digest_length, 0); + rb_define_method(cDigest_Base, "block_length", rb_digest_base_block_length, 0); + id_metadata = rb_intern("metadata"); id_new = rb_intern("new"); id_initialize = rb_intern("initialize"); diff --git a/ext/digest/digest.h b/ext/digest/digest.h index ee3c680a36..f8c5dd3906 100644 --- a/ext/digest/digest.h +++ b/ext/digest/digest.h @@ -20,7 +20,9 @@ typedef void (*hash_update_func_t)(void *, unsigned char *, size_t); typedef void (*hash_finish_func_t)(void *, unsigned char *); typedef struct { + int api_version; size_t digest_len; + size_t block_len; size_t ctx_size; hash_init_func_t init_func; hash_update_func_t update_func; diff --git a/ext/digest/lib/digest/hmac.rb b/ext/digest/lib/digest/hmac.rb index e6870f95c1..bc2f3bcbc4 100644 --- a/ext/digest/lib/digest/hmac.rb +++ b/ext/digest/lib/digest/hmac.rb @@ -12,10 +12,10 @@ # require 'digest/hmac' # # # one-liner example -# puts Digest::SHA1.hmac("hash key").hexdigest("data") +# puts Digest::HMAC.hexdigest("data", "hash key", Digest::SHA1) # # # rather longer one -# hmac = Digest::RMD160.hmac("foo").new +# hmac = Digest::HMAC.new("foo", Digest::RMD160) # # buf = "" # while stream.read(16384, buf) @@ -39,52 +39,60 @@ require 'digest' module Digest - class Base - def self.hmac(key) - algo = self - key = digest(key) if key.length > algo::BLOCK_LENGTH + class HMAC < Digest::Base + def initialize(key, digest_class, *digest_params) + @digest_class = digest_class.freeze + @digest_params = digest_params.freeze + @md = digest_class.new(*digest_params) + @tmp_md = @md.clone - (@digest_hmac_class_cache ||= {})[key] ||= Class.new(algo) { - const_set(:DIGEST_LENGTH, algo::DIGEST_LENGTH) - const_set(:BLOCK_LENGTH, algo::BLOCK_LENGTH) - const_set(:KEY, key) - @@algo = superclass + block_len = @md.block_length - def initialize(text = nil) - ipad = Array.new(BLOCK_LENGTH).fill(0x36) - opad = Array.new(BLOCK_LENGTH).fill(0x5c) + if key.length > block_len + key = @tmp_md.reset.update(key).digest + end - KEY.bytes.each_with_index { |c, i| - ipad[i] ^= c - opad[i] ^= c - } + ipad = Array.new(block_len).fill(0x36) + opad = Array.new(block_len).fill(0x5c) - @ipad = ipad.inject('') { |s, c| s << c.chr } - @opad = opad.inject('') { |s, c| s << c.chr } - - @md = @@algo.new - - update(text) unless text.nil? - end - - def update(text) - @md = @@algo.new(@opad + @@algo.digest(@ipad + text)) - - self - end - - def digest - @md.digest - end - - def self.inspect - sprintf('#<%s.hmac(%s)>', @@algo.name, KEY.inspect); - end - - def inspect - sprintf('#<%s.hmac(%s): %s>', @@algo.name, KEY.inspect, hexdigest()); - end + key.bytes.each_with_index { |c, i| + ipad[i] ^= c + opad[i] ^= c } + + @key = key.freeze + @ipad = ipad.inject('') { |s, c| s << c.chr }.freeze + @opad = opad.inject('') { |s, c| s << c.chr }.freeze + end + + def initialize_copy(other) + @md = other.instance_eval { @md } + end + + def update(text) + @md.reset.update(@opad + @tmp_md.reset.update(@ipad + text).digest) + + self + end + + def reset + @md.reset + end + + def digest + @md.digest + end + + def digest_length + @md.digest_length + end + + def block_length + @md.block_length + end + + def inspect + sprintf('#<%s: key=%s, digest=%s: %s>', self.class.name, @key.inspect, @tmp_md.reset.inspect, hexdigest()); end end end @@ -98,17 +106,21 @@ __END__ require 'test/unit' module TM_HMAC - def test_s_hexdigest - cases.each { |h| - hmac_class = digest_class.hmac(h[:key]) + def hmac_new(key) + Digest::HMAC.new(key, *digest_spec()) + end - assert_equal(h[:hexdigest], hmac_class.hexdigest(h[:data])) + def test_s_hexdigest + spec = digest_spec() + + cases.each { |h| + assert_equal(h[:hexdigest], Digest::HMAC.hexdigest(h[:data], h[:key], *spec)) } end def test_hexdigest cases.each { |h| - hmac = digest_class.hmac(h[:key]).new + hmac = hmac_new(h[:key]) hmac.update(h[:data]) assert_equal(h[:hexdigest], hmac.hexdigest) @@ -117,11 +129,12 @@ module TM_HMAC def test_reset cases.each { |h| - hmac = digest_class.hmac(h[:key]).new + hmac = hmac_new(h[:key]) hmac.update("test") hmac.reset hmac.update(h[:data]) + p hmac assert_equal(h[:hexdigest], hmac.hexdigest) } end @@ -130,8 +143,8 @@ end class TC_HMAC_MD5 < Test::Unit::TestCase include TM_HMAC - def digest_class - Digest::MD5 + def digest_spec + [Digest::MD5] end # Taken from RFC 2202: Test Cases for HMAC-MD5 and HMAC-SHA-1 @@ -173,8 +186,8 @@ end class TC_HMAC_SHA1 < Test::Unit::TestCase include TM_HMAC - def digest_class - Digest::SHA1 + def digest_spec + [Digest::SHA1] end # Taken from RFC 2202: Test Cases for HMAC-MD5 and HMAC-SHA-1 @@ -216,8 +229,8 @@ end class TC_HMAC_RMD160 < Test::Unit::TestCase include TM_HMAC - def digest_class - Digest::RMD160 + def digest_spec + [Digest::RMD160] end # Taken from RFC 2286: Test Cases for HMAC-RIPEMD160 and HMAC-RIPEMD128 diff --git a/ext/digest/md5/md5init.c b/ext/digest/md5/md5init.c index f48b56d02f..1230c17b57 100644 --- a/ext/digest/md5/md5init.c +++ b/ext/digest/md5/md5init.c @@ -9,7 +9,9 @@ #endif static algo_t md5 = { + 1, MD5_DIGEST_LENGTH, + MD5_BLOCK_LENGTH, sizeof(MD5_CTX), (hash_init_func_t)MD5_Init, (hash_update_func_t)MD5_Update, @@ -33,9 +35,6 @@ Init_md5() cDigest_MD5 = rb_define_class_under(mDigest, "MD5", cDigest_Base); - rb_define_const(cDigest_MD5, "DIGEST_LENGTH", INT2NUM(MD5_DIGEST_LENGTH)); - rb_define_const(cDigest_MD5, "BLOCK_LENGTH", INT2NUM(MD5_BLOCK_LENGTH)); - rb_ivar_set(cDigest_MD5, rb_intern("metadata"), Data_Wrap_Struct(rb_cObject, 0, 0, &md5)); } diff --git a/ext/digest/rmd160/rmd160init.c b/ext/digest/rmd160/rmd160init.c index f0a750ebef..a066763606 100644 --- a/ext/digest/rmd160/rmd160init.c +++ b/ext/digest/rmd160/rmd160init.c @@ -9,7 +9,9 @@ #endif static algo_t rmd160 = { + 1, RMD160_DIGEST_LENGTH, + RMD160_BLOCK_LENGTH, sizeof(RMD160_CTX), (hash_init_func_t)RMD160_Init, (hash_update_func_t)RMD160_Update, @@ -34,9 +36,6 @@ Init_rmd160() cDigest_RMD160 = rb_define_class_under(mDigest, "RMD160", cDigest_Base); - rb_define_const(cDigest_RMD160, "DIGEST_LENGTH", INT2NUM(RMD160_DIGEST_LENGTH)); - rb_define_const(cDigest_RMD160, "BLOCK_LENGTH", INT2NUM(RMD160_BLOCK_LENGTH)); - id_metadata = rb_intern("metadata"); rb_ivar_set(cDigest_RMD160, id_metadata, diff --git a/ext/digest/sha1/sha1init.c b/ext/digest/sha1/sha1init.c index 60fb457bf9..68c7637ab8 100644 --- a/ext/digest/sha1/sha1init.c +++ b/ext/digest/sha1/sha1init.c @@ -9,7 +9,9 @@ #endif static algo_t sha1 = { + 1, SHA1_DIGEST_LENGTH, + SHA1_BLOCK_LENGTH, sizeof(SHA1_CTX), (hash_init_func_t)SHA1_Init, (hash_update_func_t)SHA1_Update, @@ -33,9 +35,6 @@ Init_sha1() cDigest_SHA1 = rb_define_class_under(mDigest, "SHA1", cDigest_Base); - rb_define_const(cDigest_SHA1, "DIGEST_LENGTH", INT2NUM(SHA1_DIGEST_LENGTH)); - rb_define_const(cDigest_SHA1, "BLOCK_LENGTH", INT2NUM(SHA1_BLOCK_LENGTH)); - rb_ivar_set(cDigest_SHA1, rb_intern("metadata"), Data_Wrap_Struct(rb_cObject, 0, 0, &sha1)); } diff --git a/ext/digest/sha2/sha2init.c b/ext/digest/sha2/sha2init.c index 7d145c18c7..ee184819b8 100644 --- a/ext/digest/sha2/sha2init.c +++ b/ext/digest/sha2/sha2init.c @@ -8,7 +8,9 @@ #define DEFINE_ALGO_METADATA(bitlen) \ static algo_t sha##bitlen = { \ + 1, \ SHA##bitlen##_DIGEST_LENGTH, \ + SHA##bitlen##_BLOCK_LENGTH, \ sizeof(SHA##bitlen##_CTX), \ (hash_init_func_t)SHA##bitlen##_Init, \ (hash_update_func_t)SHA##bitlen##_Update, \ @@ -42,9 +44,6 @@ Init_sha2() #define DEFINE_ALGO_CLASS(bitlen) \ cDigest_SHA##bitlen = rb_define_class_under(mDigest, "SHA" #bitlen, cDigest_Base); \ -\ - rb_define_const(cDigest_SHA##bitlen, "DIGEST_LENGTH", INT2NUM(SHA##bitlen##_DIGEST_LENGTH)); \ - rb_define_const(cDigest_SHA##bitlen, "BLOCK_LENGTH", INT2NUM(SHA##bitlen##_BLOCK_LENGTH)); \ \ rb_ivar_set(cDigest_SHA##bitlen, id_metadata, \ Data_Wrap_Struct(rb_cObject, 0, 0, &sha##bitlen));