/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) | | Copyright (c) 1997-2010 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/ext/ext_hash.h" #include "hphp/runtime/ext/ext_file.h" #include "hphp/runtime/ext/hash/hash_md.h" #include "hphp/runtime/ext/hash/hash_sha.h" #include "hphp/runtime/ext/hash/hash_ripemd.h" #include "hphp/runtime/ext/hash/hash_whirlpool.h" #include "hphp/runtime/ext/hash/hash_tiger.h" #include "hphp/runtime/ext/hash/hash_snefru.h" #include "hphp/runtime/ext/hash/hash_gost.h" #include "hphp/runtime/ext/hash/hash_adler32.h" #include "hphp/runtime/ext/hash/hash_crc32.h" #include "hphp/runtime/ext/hash/hash_haval.h" #include "hphp/runtime/ext/hash/hash_fnv1.h" #include "hphp/runtime/ext/hash/hash_furc.h" #include "hphp/runtime/ext/hash/hash_murmur.h" #if defined(HPHP_OSS) #define furc_hash furc_hash_internal #else #include "memcache/ch/hash.h" #endif namespace HPHP { IMPLEMENT_DEFAULT_EXTENSION(hash); /////////////////////////////////////////////////////////////////////////////// // hash engines static HashEngineMap HashEngines; class HashEngineMapInitializer { public: HashEngineMapInitializer() { HashEngines["md2"] = HashEnginePtr(new hash_md2()); HashEngines["md4"] = HashEnginePtr(new hash_md4()); HashEngines["md5"] = HashEnginePtr(new hash_md5()); HashEngines["sha1"] = HashEnginePtr(new hash_sha1()); HashEngines["sha224"] = HashEnginePtr(new hash_sha224()); HashEngines["sha256"] = HashEnginePtr(new hash_sha256()); HashEngines["sha384"] = HashEnginePtr(new hash_sha384()); HashEngines["sha512"] = HashEnginePtr(new hash_sha512()); HashEngines["ripemd128"] = HashEnginePtr(new hash_ripemd128()); HashEngines["ripemd160"] = HashEnginePtr(new hash_ripemd160()); HashEngines["ripemd256"] = HashEnginePtr(new hash_ripemd256()); HashEngines["ripemd320"] = HashEnginePtr(new hash_ripemd320()); HashEngines["whirlpool"] = HashEnginePtr(new hash_whirlpool()); #ifdef FACEBOOK HashEngines["tiger128,3-fb"] = HashEnginePtr(new hash_tiger(true, 128, true)); // Temporarily leave tiger128,3 algo inverting its hash output // to retain BC pending conversion of user code to correct endianness // sgolemon(2013-04-30) HashEngines["tiger128,3"] = HashEnginePtr(new hash_tiger(true, 128, true)); #else HashEngines["tiger128,3"] = HashEnginePtr(new hash_tiger(true, 128)); #endif HashEngines["tiger160,3"] = HashEnginePtr(new hash_tiger(true, 160)); HashEngines["tiger192,3"] = HashEnginePtr(new hash_tiger(true, 192)); HashEngines["tiger128,4"] = HashEnginePtr(new hash_tiger(false, 128)); HashEngines["tiger160,4"] = HashEnginePtr(new hash_tiger(false, 160)); HashEngines["tiger192,4"] = HashEnginePtr(new hash_tiger(false, 192)); HashEngines["snefru"] = HashEnginePtr(new hash_snefru()); HashEngines["gost"] = HashEnginePtr(new hash_gost()); HashEngines["adler32"] = HashEnginePtr(new hash_adler32()); HashEngines["crc32"] = HashEnginePtr(new hash_crc32(false)); HashEngines["crc32b"] = HashEnginePtr(new hash_crc32(true)); HashEngines["haval128,3"] = HashEnginePtr(new hash_haval(3,128)); HashEngines["haval160,3"] = HashEnginePtr(new hash_haval(3,160)); HashEngines["haval192,3"] = HashEnginePtr(new hash_haval(3,192)); HashEngines["haval224,3"] = HashEnginePtr(new hash_haval(3,224)); HashEngines["haval256,3"] = HashEnginePtr(new hash_haval(3,256)); HashEngines["haval128,4"] = HashEnginePtr(new hash_haval(4,128)); HashEngines["haval160,4"] = HashEnginePtr(new hash_haval(4,160)); HashEngines["haval192,4"] = HashEnginePtr(new hash_haval(4,192)); HashEngines["haval224,4"] = HashEnginePtr(new hash_haval(4,224)); HashEngines["haval256,4"] = HashEnginePtr(new hash_haval(4,256)); HashEngines["haval128,5"] = HashEnginePtr(new hash_haval(5,128)); HashEngines["haval160,5"] = HashEnginePtr(new hash_haval(5,160)); HashEngines["haval192,5"] = HashEnginePtr(new hash_haval(5,192)); HashEngines["haval224,5"] = HashEnginePtr(new hash_haval(5,224)); HashEngines["haval256,5"] = HashEnginePtr(new hash_haval(5,256)); HashEngines["fnv132"] = HashEnginePtr(new hash_fnv132(false)); HashEngines["fnv1a32"] = HashEnginePtr(new hash_fnv132(true)); HashEngines["fnv164"] = HashEnginePtr(new hash_fnv164(false)); HashEngines["fnv1a64"] = HashEnginePtr(new hash_fnv164(true)); } }; static HashEngineMapInitializer s_engine_initializer; /////////////////////////////////////////////////////////////////////////////// // hash context class HashContext : public SweepableResourceData { public: static StaticString s_class_name; // overriding ResourceData virtual CStrRef o_getClassNameHook() const { return s_class_name; } HashContext(HashEnginePtr ops_, void *context_, int options_) : ops(ops_), context(context_), options(options_), key(NULL) { } ~HashContext() { /* Just in case the algo has internally allocated resources */ if (context) { unsigned char *dummy = (unsigned char *)malloc(ops->digest_size); ops->hash_final(dummy, context); free(dummy); free(context); } if (key) { memset(key, 0, ops->block_size); free(key); } } HashEnginePtr ops; void *context; int options; char *key; }; StaticString HashContext::s_class_name("Hash Context"); /////////////////////////////////////////////////////////////////////////////// // hash functions Array f_hash_algos() { Array ret; for (HashEngineMap::const_iterator iter = HashEngines.begin(); iter != HashEngines.end(); ++iter) { ret.append(String(iter->first)); } return ret; } static HashEnginePtr php_hash_fetch_ops(CStrRef algo) { HashEngineMap::const_iterator iter = HashEngines.find(StringUtil::ToLower(algo).data()); if (iter == HashEngines.end()) { return HashEnginePtr(); } return iter->second; } static Variant php_hash_do_hash(CStrRef algo, CStrRef data, bool isfilename, bool raw_output) { HashEnginePtr ops = php_hash_fetch_ops(algo); if (!ops) { raise_warning("Unknown hashing algorithm: %s", algo.data()); return false; } Variant f; if (isfilename) { f = f_fopen(data, "rb"); if (same(f, false)) { return false; } } void *context = malloc(ops->context_size); ops->hash_init(context); if (isfilename) { for (Variant chunk = f_fread(f.toResource(), 1024); !is_empty_string(chunk); chunk = f_fread(f.toResource(), 1024)) { String schunk = chunk.toString(); ops->hash_update(context, (unsigned char *)schunk.data(), schunk.size()); } } else { ops->hash_update(context, (unsigned char *)data.data(), data.size()); } String raw = String(ops->digest_size, ReserveString); char *digest = raw.mutableSlice().ptr; ops->hash_final((unsigned char *)digest, context); free(context); raw.setSize(ops->digest_size); if (raw_output) { return raw; } return StringUtil::HexEncode(raw); } Variant f_hash(CStrRef algo, CStrRef data, bool raw_output /* = false */) { return php_hash_do_hash(algo, data, false, raw_output); } Variant f_hash_file(CStrRef algo, CStrRef filename, bool raw_output /* = false */) { return php_hash_do_hash(algo, filename, true, raw_output); } static char *prepare_hmac_key(HashEnginePtr ops, void *context, CStrRef key) { char *K = (char*)malloc(ops->block_size); memset(K, 0, ops->block_size); if (key.size() > ops->block_size) { /* Reduce the key first */ ops->hash_update(context, (unsigned char *)key.data(), key.size()); ops->hash_final((unsigned char *)K, context); /* Make the context ready to start over */ ops->hash_init(context); } else { memcpy(K, key.data(), key.size()); } /* XOR ipad */ for (int i = 0; i < ops->block_size; i++) { K[i] ^= 0x36; } ops->hash_update(context, (unsigned char *)K, ops->block_size); return K; } static void finalize_hmac_key(char *K, HashEnginePtr ops, void *context, char *digest) { /* Convert K to opad -- 0x6A = 0x36 ^ 0x5C */ for (int i = 0; i < ops->block_size; i++) { K[i] ^= 0x6A; } /* Feed this result into the outter hash */ ops->hash_init(context); ops->hash_update(context, (unsigned char *)K, ops->block_size); ops->hash_update(context, (unsigned char *)digest, ops->digest_size); ops->hash_final((unsigned char *)digest, context); /* Zero the key */ memset(K, 0, ops->block_size); free(K); } static Variant php_hash_do_hash_hmac(CStrRef algo, CStrRef data, bool isfilename, CStrRef key, bool raw_output /* = false */) { HashEnginePtr ops = php_hash_fetch_ops(algo); if (!ops) { raise_warning("Unknown hashing algorithm: %s", algo.data()); return false; } Variant f; if (isfilename) { f = f_fopen(data, "rb"); if (same(f, false)) { return false; } } void *context = malloc(ops->context_size); ops->hash_init(context); char *K = prepare_hmac_key(ops, context, key); if (isfilename) { for (Variant chunk = f_fread(f.toResource(), 1024); !is_empty_string(chunk); chunk = f_fread(f.toResource(), 1024)) { String schunk = chunk.toString(); ops->hash_update(context, (unsigned char *)schunk.data(), schunk.size()); } } else { ops->hash_update(context, (unsigned char *)data.data(), data.size()); } String raw = String(ops->digest_size, ReserveString); char *digest = raw.mutableSlice().ptr; ops->hash_final((unsigned char *)digest, context); finalize_hmac_key(K, ops, context, digest); free(context); raw.setSize(ops->digest_size); if (raw_output) { return raw; } return StringUtil::HexEncode(raw); } Variant f_hash_hmac(CStrRef algo, CStrRef data, CStrRef key, bool raw_output /* = false */) { return php_hash_do_hash_hmac(algo, data, false, key, raw_output); } Variant f_hash_hmac_file(CStrRef algo, CStrRef filename, CStrRef key, bool raw_output /* = false */) { return php_hash_do_hash_hmac(algo, filename, true, key, raw_output); } Variant f_hash_init(CStrRef algo, int options /* = 0 */, CStrRef key /* = null_string */) { HashEnginePtr ops = php_hash_fetch_ops(algo); if (!ops) { raise_warning("Unknown hashing algorithm: %s", algo.data()); return false; } if ((options & k_HASH_HMAC) && key.empty()) { raise_warning("HMAC requested without a key"); return false; } void *context = malloc(ops->context_size); ops->hash_init(context); HashContext *hash = new HashContext(ops, context, options); if (options & k_HASH_HMAC) { hash->key = prepare_hmac_key(ops, context, key); } return Resource(hash); } bool f_hash_update(CResRef context, CStrRef data) { HashContext *hash = context.getTyped(); hash->ops->hash_update(hash->context, (unsigned char *)data.data(), data.size()); return true; } bool f_hash_update_file(CResRef init_context, CStrRef filename, CResRef stream_context /* = null */) { Variant f = f_fopen(filename, "rb"); if (same(f, false)) { return false; } HashContext *hash = init_context.getTyped(); for (Variant chunk = f_fread(f.toResource(), 1024); !is_empty_string(chunk); chunk = f_fread(f.toResource(), 1024)) { String schunk = chunk.toString(); hash->ops->hash_update(hash->context, (unsigned char *)schunk.data(), schunk.size()); } return true; } int64_t f_hash_update_stream(CResRef context, CResRef handle, int length /* = -1 */) { HashContext *hash = context.getTyped(); int didread = 0; while (length) { Variant chunk = f_fread(handle, length > 0 ? length : 1024); if (is_empty_string(chunk)) { return didread; } String schunk = chunk.toString(); hash->ops->hash_update(hash->context, (unsigned char *)schunk.data(), schunk.size()); didread += schunk.size(); length -= schunk.size(); } return didread; } String f_hash_final(CResRef context, bool raw_output /* = false */) { HashContext *hash = context.getTyped(); String raw = String(hash->ops->digest_size, ReserveString); char *digest = raw.mutableSlice().ptr; hash->ops->hash_final((unsigned char *)digest, hash->context); if (hash->options & k_HASH_HMAC) { finalize_hmac_key(hash->key, hash->ops, hash->context, digest); hash->key = NULL; } free(hash->context); hash->context = NULL; raw.setSize(hash->ops->digest_size); if (raw_output) { return raw; } return StringUtil::HexEncode(raw); } int64_t f_furchash_hphp_ext(CStrRef key, int len, int nPart) { len = std::max(std::min(len, key.size()), 0); return furc_hash(key.data(), len, nPart); } bool f_furchash_hphp_ext_supported() { return true; } int64_t f_hphp_murmurhash(CStrRef key, int len, int seed) { len = std::max(std::min(len, key.size()), 0); return murmur_hash_64A(key.data(), len, seed); } /////////////////////////////////////////////////////////////////////////////// }