View Javadoc
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 }