| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.shiro.crypto.hash.format; |
| |
| import org.apache.shiro.crypto.hash.AbstractCryptHash; |
| import org.apache.shiro.crypto.hash.Hash; |
| import org.apache.shiro.crypto.hash.HashProvider; |
| import org.apache.shiro.crypto.hash.HashSpi; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| /** |
| * The {@code Shiro2CryptFormat} is a fully reversible |
| * <a href="http://packages.python.org/passlib/modular_crypt_format.html">Modular Crypt Format</a> (MCF). It is based |
| * on the posix format for storing KDF-hashed passwords in {@code /etc/shadow} files on linux and unix-alike systems. |
| * <h2>Format</h2> |
| * <p>Hash instances formatted with this implementation will result in a String with the following dollar-sign ($) |
| * delimited format:</p> |
| * <pre> |
| * <b>$</b>mcfFormatId<b>$</b>algorithmName<b>$</b>algorithm-specific-data. |
| * </pre> |
| * <p>Each token is defined as follows:</p> |
| * <table> |
| * <tr> |
| * <th>Position</th> |
| * <th>Token</th> |
| * <th>Description</th> |
| * <th>Required?</th> |
| * </tr> |
| * <tr> |
| * <td>1</td> |
| * <td>{@code mcfFormatId}</td> |
| * <td>The Modular Crypt Format identifier for this implementation, equal to <b>{@code shiro2}</b>. |
| * ( This implies that all {@code shiro2} MCF-formatted strings will always begin with the prefix |
| * {@code $shiro2$} ).</td> |
| * <td>true</td> |
| * </tr> |
| * <tr> |
| * <td>2</td> |
| * <td>{@code algorithmName}</td> |
| * <td>The name of the hash algorithm used to perform the hash. Either a hash class exists, or |
| * otherwise a {@link UnsupportedOperationException} will be thrown. |
| * <td>true</td> |
| * </tr> |
| * <tr> |
| * <td>3</td> |
| * <td>{@code algorithm-specific-data}</td> |
| * <td>In contrast to the previous {@code shiro1} format, the shiro2 format does not make any assumptions |
| * about how an algorithm stores its data. Therefore, everything beyond the first token is handled over |
| * to the Hash implementation.</td> |
| * </tr> |
| * </table> |
| * |
| * @see ModularCryptFormat |
| * @see ParsableHashFormat |
| * @since 2.0 |
| */ |
| public class Shiro2CryptFormat implements ModularCryptFormat, ParsableHashFormat { |
| |
| /** |
| * Identifier for the shiro2 crypt format. |
| */ |
| public static final String ID = "shiro2"; |
| /** |
| * Enclosed identifier of the shiro2 crypt format. |
| */ |
| public static final String MCF_PREFIX = TOKEN_DELIMITER + ID + TOKEN_DELIMITER; |
| |
| public Shiro2CryptFormat() { |
| } |
| |
| @Override |
| public String getId() { |
| return ID; |
| } |
| |
| /** |
| * Converts a Hash-extending class to a string understood by the hash class. Usually this string will follow |
| * posix standards for passwords stored in {@code /etc/passwd}. |
| * |
| * <p>This method should only delegate to the corresponding formatter and prepend {@code $shiro2$}.</p> |
| * |
| * @param hash the hash instance to format into a String. |
| * @return a string representing the hash. |
| */ |
| @Override |
| public String format(final Hash hash) { |
| requireNonNull(hash, "hash in Shiro2CryptFormat.format(Hash hash)"); |
| |
| if (!(hash instanceof AbstractCryptHash)) { |
| throw new UnsupportedOperationException("Shiro2CryptFormat can only format classes extending AbstractCryptHash."); |
| } |
| |
| AbstractCryptHash cryptHash = (AbstractCryptHash) hash; |
| return TOKEN_DELIMITER + ID + cryptHash.formatToCryptString(); |
| } |
| |
| @Override |
| public Hash parse(final String formatted) { |
| requireNonNull(formatted, "formatted in Shiro2CryptFormat.parse(String formatted)"); |
| |
| // backwards compatibility |
| if (formatted.startsWith(Shiro1CryptFormat.MCF_PREFIX)) { |
| return new Shiro1CryptFormat().parse(formatted); |
| } |
| |
| if (!formatted.startsWith(MCF_PREFIX)) { |
| final String msg = "The argument is not a valid '" + ID + "' formatted hash."; |
| throw new IllegalArgumentException(msg); |
| } |
| |
| final String suffix = formatted.substring(MCF_PREFIX.length()); |
| final String[] parts = suffix.split("\\$"); |
| final String algorithmName = parts[0]; |
| |
| HashSpi kdfHash = HashProvider.getByAlgorithmName(algorithmName) |
| .orElseThrow(() -> new UnsupportedOperationException("Algorithm " + algorithmName + " is not implemented.")); |
| return kdfHash.fromString("$" + suffix); |
| } |
| |
| } |