001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.shiro.crypto.hash; 021 022import org.apache.shiro.lang.codec.Base64; 023import org.apache.shiro.lang.codec.Hex; 024import org.apache.shiro.lang.util.ByteSource; 025 026import java.io.Serializable; 027import java.nio.charset.StandardCharsets; 028import java.util.Arrays; 029import java.util.Locale; 030import java.util.Objects; 031import java.util.StringJoiner; 032import java.util.regex.Pattern; 033 034import static java.util.Objects.requireNonNull; 035 036/** 037 * Abstract class for hashes following the posix crypt(3) format. 038 * 039 * <p>These implementations must contain a salt, a salt length, can format themselves to a valid String 040 * suitable for the {@code /etc/shadow} file.</p> 041 * 042 * <p>It also defines the hex and base64 output by wrapping the output of {@link #formatToCryptString()}.</p> 043 * 044 * <p>Implementation notice: Implementations should provide a static {@code fromString()} method.</p> 045 * 046 * @since 2.0 047 */ 048public abstract class AbstractCryptHash implements Hash, Serializable { 049 050 protected static final Pattern DELIMITER = Pattern.compile("\\$"); 051 052 private static final long serialVersionUID = 2483214646921027859L; 053 054 private final String algorithmName; 055 private final byte[] hashedData; 056 private final ByteSource salt; 057 058 /** 059 * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead. 060 */ 061 private String hexEncoded; 062 /** 063 * Cached value of the {@link #toBase64() toBase64()} call so multiple calls won't incur repeated overhead. 064 */ 065 private String base64Encoded; 066 067 /** 068 * Constructs an {@link AbstractCryptHash} using the algorithm name, hashed data and salt parameters. 069 * 070 * <p>Other required parameters must be stored by the implementation.</p> 071 * 072 * @param algorithmName internal algorithm name, e.g. {@code 2y} for bcrypt and {@code argon2id} for argon2. 073 * @param hashedData the hashed data as a byte array. Does not include the salt or other parameters. 074 * @param salt the salt which was used when generating the hash. 075 * @throws IllegalArgumentException if the salt is not the same size as {@link #getSaltLength()}. 076 */ 077 public AbstractCryptHash(final String algorithmName, final byte[] hashedData, final ByteSource salt) { 078 this.algorithmName = algorithmName; 079 this.hashedData = Arrays.copyOf(hashedData, hashedData.length); 080 this.salt = requireNonNull(salt); 081 checkValid(); 082 } 083 084 protected final void checkValid() { 085 checkValidAlgorithm(); 086 087 checkValidSalt(); 088 } 089 090 /** 091 * Algorithm-specific checks of the algorithm’s parameters. 092 * 093 * <p>While the salt length will be checked by default, other checks will be useful. 094 * Examples are: Argon2 checking for the memory and parallelism parameters, bcrypt checking 095 * for the cost parameters being in a valid range.</p> 096 * 097 * @throws IllegalArgumentException if any of the parameters are invalid. 098 */ 099 protected abstract void checkValidAlgorithm(); 100 101 /** 102 * Default check method for a valid salt. Can be overridden, because multiple salt lengths could be valid. 103 * <p> 104 * By default, this method checks if the number of bytes in the salt 105 * are equal to the int returned by {@link #getSaltLength()}. 106 * 107 * @throws IllegalArgumentException if the salt length does not match the returned value of {@link #getSaltLength()}. 108 */ 109 protected void checkValidSalt() { 110 int length = salt.getBytes().length; 111 if (length != getSaltLength()) { 112 String message = String.format( 113 Locale.ENGLISH, 114 "Salt length is expected to be [%d] bytes, but was [%d] bytes.", 115 getSaltLength(), 116 length 117 ); 118 throw new IllegalArgumentException(message); 119 } 120 } 121 122 /** 123 * Implemented by subclasses, this specifies the KDF algorithm name 124 * to use when performing the hash. 125 * 126 * <p>When multiple algorithm names are acceptable, then this method should return the primary algorithm name.</p> 127 * 128 * <p>Example: Bcrypt hashed can be identified by {@code 2y} and {@code 2a}. The method will return {@code 2y} 129 * for newly generated hashes by default, unless otherwise overridden.</p> 130 * 131 * @return the KDF algorithm name to use when performing the hash. 132 */ 133 @Override 134 public String getAlgorithmName() { 135 return this.algorithmName; 136 } 137 138 /** 139 * The length in number of bytes of the salt which is needed for this algorithm. 140 * 141 * @return the expected length of the salt (in bytes). 142 */ 143 public abstract int getSaltLength(); 144 145 @Override 146 public ByteSource getSalt() { 147 return this.salt; 148 } 149 150 /** 151 * Returns only the hashed data. Those are of no value on their own. If you need to serialize 152 * the hash, please refer to {@link #formatToCryptString()}. 153 * 154 * @return A copy of the hashed data as bytes. 155 * @see #formatToCryptString() 156 */ 157 @Override 158 public byte[] getBytes() { 159 return Arrays.copyOf(this.hashedData, this.hashedData.length); 160 } 161 162 @Override 163 public boolean isEmpty() { 164 return false; 165 } 166 167 /** 168 * Returns a hex-encoded string of the underlying {@link #formatToCryptString()} formatted output}. 169 * <p/> 170 * This implementation caches the resulting hex string so multiple calls to this method remain efficient. 171 * 172 * @return a hex-encoded string of the underlying {@link #formatToCryptString()} formatted output}. 173 */ 174 @Override 175 public String toHex() { 176 if (this.hexEncoded == null) { 177 this.hexEncoded = Hex.encodeToString(this.formatToCryptString().getBytes(StandardCharsets.UTF_8)); 178 } 179 return this.hexEncoded; 180 } 181 182 /** 183 * Returns a Base64-encoded string of the underlying {@link #formatToCryptString()} formatted output}. 184 * <p/> 185 * This implementation caches the resulting Base64 string so multiple calls to this method remain efficient. 186 * 187 * @return a Base64-encoded string of the underlying {@link #formatToCryptString()} formatted output}. 188 */ 189 @Override 190 public String toBase64() { 191 if (this.base64Encoded == null) { 192 //cache result in case this method is called multiple times. 193 this.base64Encoded = Base64.encodeToString(this.formatToCryptString().getBytes(StandardCharsets.UTF_8)); 194 } 195 return this.base64Encoded; 196 } 197 198 /** 199 * This method <strong>MUST</strong> return a single-lined string which would also be recognizable by 200 * a posix {@code /etc/passwd} file. 201 * 202 * @return a formatted string, e.g. {@code $2y$10$7rOjsAf2U/AKKqpMpCIn6e$tuOXyQ86tp2Tn9xv6FyXl2T0QYc3.G.} for bcrypt. 203 */ 204 public abstract String formatToCryptString(); 205 206 /** 207 * Returns {@code true} if the specified object is an AbstractCryptHash and its 208 * {@link #formatToCryptString()} formatted output} is identical to 209 * this AbstractCryptHash's formatted output, {@code false} otherwise. 210 * 211 * @param other the object (AbstractCryptHash) to check for equality. 212 * @return {@code true} if the specified object is a AbstractCryptHash 213 * and its {@link #formatToCryptString()} formatted output} is identical to 214 * this AbstractCryptHash's formatted output, {@code false} otherwise. 215 */ 216 @Override 217 public boolean equals(final Object other) { 218 if (other instanceof AbstractCryptHash) { 219 final AbstractCryptHash that = (AbstractCryptHash) other; 220 return this.formatToCryptString().equals(that.formatToCryptString()); 221 } 222 return false; 223 } 224 225 /** 226 * Hashes the formatted crypt string. 227 * 228 * <p>Implementations should not override this method, as different algorithms produce different output formats 229 * and require different parameters.</p> 230 * 231 * @return a hashcode from the {@link #formatToCryptString() formatted output}. 232 */ 233 @Override 234 public int hashCode() { 235 return Objects.hash(this.formatToCryptString()); 236 } 237 238 /** 239 * Simple implementation that merely returns {@link #toHex() toHex()}. 240 * 241 * @return the {@link #toHex() toHex()} value. 242 */ 243 @Override 244 public String toString() { 245 return new StringJoiner(", ", AbstractCryptHash.class.getSimpleName() + "[", "]") 246 .add("super=" + super.toString()) 247 .add("algorithmName='" + algorithmName + "'") 248 .add("hashedData=" + Arrays.toString(hashedData)) 249 .add("salt=" + salt) 250 .add("hexEncoded='" + hexEncoded + "'") 251 .add("base64Encoded='" + base64Encoded + "'") 252 .toString(); 253 } 254}