1/* 2 * $Id$ 3 * Copyright (C) 2007 Technorama Ltd. <oss-ruby@technorama.net> 4 */ 5#include "ossl.h" 6 7VALUE mPKCS5; 8VALUE ePKCS5; 9 10#ifdef HAVE_PKCS5_PBKDF2_HMAC 11/* 12 * call-seq: 13 * PKCS5.pbkdf2_hmac(pass, salt, iter, keylen, digest) => string 14 * 15 * === Parameters 16 * * +pass+ - string 17 * * +salt+ - string - should be at least 8 bytes long. 18 * * +iter+ - integer - should be greater than 1000. 20000 is better. 19 * * +keylen+ - integer 20 * * +digest+ - a string or OpenSSL::Digest object. 21 * 22 * Available in OpenSSL 0.9.4. 23 * 24 * Digests other than SHA1 may not be supported by other cryptography libraries. 25 */ 26static VALUE 27ossl_pkcs5_pbkdf2_hmac(VALUE self, VALUE pass, VALUE salt, VALUE iter, VALUE keylen, VALUE digest) 28{ 29 VALUE str; 30 const EVP_MD *md; 31 int len = NUM2INT(keylen); 32 33 StringValue(pass); 34 StringValue(salt); 35 md = GetDigestPtr(digest); 36 37 str = rb_str_new(0, len); 38 39 if (PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass), 40 (unsigned char *)RSTRING_PTR(salt), RSTRING_LENINT(salt), 41 NUM2INT(iter), md, len, 42 (unsigned char *)RSTRING_PTR(str)) != 1) 43 ossl_raise(ePKCS5, "PKCS5_PBKDF2_HMAC"); 44 45 return str; 46} 47#else 48#define ossl_pkcs5_pbkdf2_hmac rb_f_notimplement 49#endif 50 51 52#ifdef HAVE_PKCS5_PBKDF2_HMAC_SHA1 53/* 54 * call-seq: 55 * PKCS5.pbkdf2_hmac_sha1(pass, salt, iter, keylen) => string 56 * 57 * === Parameters 58 * * +pass+ - string 59 * * +salt+ - string - should be at least 8 bytes long. 60 * * +iter+ - integer - should be greater than 1000. 20000 is better. 61 * * +keylen+ - integer 62 * 63 * This method is available in almost any version of OpenSSL. 64 * 65 * Conforms to rfc2898. 66 */ 67static VALUE 68ossl_pkcs5_pbkdf2_hmac_sha1(VALUE self, VALUE pass, VALUE salt, VALUE iter, VALUE keylen) 69{ 70 VALUE str; 71 int len = NUM2INT(keylen); 72 73 StringValue(pass); 74 StringValue(salt); 75 76 str = rb_str_new(0, len); 77 78 if (PKCS5_PBKDF2_HMAC_SHA1(RSTRING_PTR(pass), RSTRING_LENINT(pass), 79 (const unsigned char *)RSTRING_PTR(salt), RSTRING_LENINT(salt), NUM2INT(iter), 80 len, (unsigned char *)RSTRING_PTR(str)) != 1) 81 ossl_raise(ePKCS5, "PKCS5_PBKDF2_HMAC_SHA1"); 82 83 return str; 84} 85#else 86#define ossl_pkcs5_pbkdf2_hmac_sha1 rb_f_notimplement 87#endif 88 89void 90Init_ossl_pkcs5() 91{ 92 /* 93 * Password-based Encryption 94 * 95 */ 96 97 #if 0 98 mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ 99 #endif 100 101 /* Document-class: OpenSSL::PKCS5 102 * 103 * Provides password-based encryption functionality based on PKCS#5. 104 * Typically used for securely deriving arbitrary length symmetric keys 105 * to be used with an OpenSSL::Cipher from passwords. Another use case 106 * is for storing passwords: Due to the ability to tweak the effort of 107 * computation by increasing the iteration count, computation can be 108 * slowed down artificially in order to render possible attacks infeasible. 109 * 110 * PKCS5 offers support for PBKDF2 with an OpenSSL::Digest::SHA1-based 111 * HMAC, or an arbitrary Digest if the underlying version of OpenSSL 112 * already supports it (>= 0.9.4). 113 * 114 * === Parameters 115 * ==== Password 116 * Typically an arbitrary String that represents the password to be used 117 * for deriving a key. 118 * ==== Salt 119 * Prevents attacks based on dictionaries of common passwords. It is a 120 * public value that can be safely stored along with the password (e.g. 121 * if PBKDF2 is used for password storage). For maximum security, a fresh, 122 * random salt should be generated for each stored password. According 123 * to PKCS#5, a salt should be at least 8 bytes long. 124 * ==== Iteration Count 125 * Allows to tweak the length that the actual computation will take. The 126 * larger the iteration count, the longer it will take. 127 * ==== Key Length 128 * Specifies the length in bytes of the output that will be generated. 129 * Typically, the key length should be larger than or equal to the output 130 * length of the underlying digest function, otherwise an attacker could 131 * simply try to brute-force the key. According to PKCS#5, security is 132 * limited by the output length of the underlying digest function, i.e. 133 * security is not improved if a key length strictly larger than the 134 * digest output length is chosen. Therefore, when using PKCS5 for 135 * password storage, it suffices to store values equal to the digest 136 * output length, nothing is gained by storing larger values. 137 * 138 * == Examples 139 * === Generating a 128 bit key for a Cipher (e.g. AES) 140 * pass = "secret" 141 * salt = OpenSSL::Random.random_bytes(16) 142 * iter = 20000 143 * key_len = 16 144 * key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, iter, key_len) 145 * 146 * === Storing Passwords 147 * pass = "secret" 148 * salt = OpenSSL::Random.random_bytes(16) #store this with the generated value 149 * iter = 20000 150 * digest = OpenSSL::Digest::SHA256.new 151 * len = digest.digest_length 152 * #the final value to be stored 153 * value = OpenSSL::PKCS5.pbkdf2_hmac(pass, salt, iter, len, digest) 154 * 155 * === Important Note on Checking Passwords 156 * When comparing passwords provided by the user with previously stored 157 * values, a common mistake made is comparing the two values using "==". 158 * Typically, "==" short-circuits on evaluation, and is therefore 159 * vulnerable to timing attacks. The proper way is to use a method that 160 * always takes the same amount of time when comparing two values, thus 161 * not leaking any information to potential attackers. To compare two 162 * values, the following could be used: 163 * def eql_time_cmp(a, b) 164 * unless a.length == b.length 165 * return false 166 * end 167 * cmp = b.bytes.to_a 168 * result = 0 169 * a.bytes.each_with_index {|c,i| 170 * result |= c ^ cmp[i] 171 * } 172 * result == 0 173 * end 174 * Please note that the premature return in case of differing lengths 175 * typically does not leak valuable information - when using PKCS#5, the 176 * length of the values to be compared is of fixed size. 177 */ 178 179 mPKCS5 = rb_define_module_under(mOSSL, "PKCS5"); 180 /* Document-class: OpenSSL::PKCS5::PKCS5Error 181 * 182 * Generic Exception class that is raised if an error occurs during a 183 * computation. 184 */ 185 ePKCS5 = rb_define_class_under(mPKCS5, "PKCS5Error", eOSSLError); 186 187 rb_define_module_function(mPKCS5, "pbkdf2_hmac", ossl_pkcs5_pbkdf2_hmac, 5); 188 rb_define_module_function(mPKCS5, "pbkdf2_hmac_sha1", ossl_pkcs5_pbkdf2_hmac_sha1, 4); 189} 190