1 /*
2 * Copyright 2010-2026 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.mybatis.caches.ehcache;
17
18 import java.io.Serializable;
19
20 /**
21 * Key wrapper that applies Murmur3 32-bit finalizer bit-mixing to {@link Object#hashCode()} before the value is exposed
22 * to Ehcache 3's internal hash structures.
23 * <p>
24 * Ehcache 3 uses the raw {@code hashCode()} of stored keys without any internal spreading. When the cache is
25 * <em>bounded</em> (i.e. a maximum entry count or size has been configured), an attacker who can influence the cache
26 * keys — for example by controlling SQL query parameters that end up in MyBatis's {@code CacheKey} — can craft a set of
27 * keys that all share the same raw hash, degrading every cache operation from O(1) to O(n) and causing a
28 * <strong>hash-flooding denial of service</strong>.
29 * </p>
30 * <p>
31 * Wrapping every key in this class ensures that Ehcache 3 always sees a <em>mixed</em> hash value. The mixing function
32 * (Murmur3 fmix32) has strong avalanche properties: a single-bit difference in the input changes roughly half of the
33 * output bits, making it computationally infeasible to produce many keys with the same mixed hash.
34 * </p>
35 *
36 * @see <a href="https://github.com/jhipster/generator-jhipster/issues/28546">jhipster/generator-jhipster #28546</a>
37 * @see <a href="https://github.com/mybatis/ehcache-cache/issues/61">mybatis/ehcache-cache #61</a>
38 */
39 final class HashKeyWrapper implements Serializable {
40
41 private static final long serialVersionUID = 1L;
42
43 /** The original, unwrapped cache key. */
44 private final Object key;
45
46 /**
47 * The pre-mixed hash code, computed once at construction time. Using a {@code final} field avoids re-computing the
48 * mixing on every lookup.
49 */
50 private final int hash;
51
52 /**
53 * Wraps {@code key}, pre-computing its mixed hash.
54 *
55 * @param key
56 * the original cache key; may be {@code null}
57 */
58 HashKeyWrapper(Object key) {
59 this.key = key;
60 this.hash = fmix32(key == null ? 0 : key.hashCode());
61 }
62
63 /**
64 * Returns the original cache key that this wrapper was constructed with.
65 *
66 * @return the original key, possibly {@code null}
67 */
68 Object getKey() {
69 return key;
70 }
71
72 /**
73 * Returns the pre-mixed hash code of the wrapped key.
74 * <p>
75 * The value is derived by applying the Murmur3 32-bit finalizer (fmix32) to {@code key.hashCode()}. The finalizer has
76 * excellent avalanche properties: every output bit depends on every input bit, so an attacker who knows the raw
77 * {@code hashCode()} of a key cannot predict which Ehcache 3 internal bucket the mixed hash will land in.
78 * </p>
79 */
80 @Override
81 public int hashCode() {
82 return hash;
83 }
84
85 /**
86 * Two {@code HashKeyWrapper} instances are equal when their wrapped keys are equal according to
87 * {@link Object#equals}.
88 */
89 @Override
90 public boolean equals(Object obj) {
91 if (this == obj) {
92 return true;
93 }
94 if (!(obj instanceof HashKeyWrapper)) {
95 return false;
96 }
97 HashKeyWrapper other = (HashKeyWrapper) obj;
98 return key == null ? other.key == null : key.equals(other.key);
99 }
100
101 @Override
102 public String toString() {
103 return "HashKeyWrapper{" + key + "}";
104 }
105
106 /**
107 * Murmur3 32-bit finalizer (fmix32). Applies three xor-shift-multiply rounds that give near-perfect bit avalanche.
108 *
109 * @param h
110 * raw hash value to mix
111 *
112 * @return mixed hash value
113 */
114 static int fmix32(int h) {
115 h ^= h >>> 16;
116 h *= 0x85ebca6b;
117 h ^= h >>> 13;
118 h *= 0xc2b2ae35;
119 h ^= h >>> 16;
120 return h;
121 }
122
123 }