blob: 555b5e36ef8ef3323cb8f6656361fe63f1569514 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Shiro1CryptFormat.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Apache Shiro :: Core</a> &gt; <a href="index.source.html" class="el_package">org.apache.shiro.crypto.hash.format</a> &gt; <span class="el_source">Shiro1CryptFormat.java</span></div><h1>Shiro1CryptFormat.java</h1><pre class="source lang-java linenums">/*
* 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
* &quot;License&quot;); 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
* &quot;AS IS&quot; 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.codec.Base64;
import org.apache.shiro.crypto.hash.Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.StringUtils;
/**
* The {@code Shiro1CryptFormat} is a fully reversible
* &lt;a href=&quot;http://packages.python.org/passlib/modular_crypt_format.html&quot;&gt;Modular Crypt Format&lt;/a&gt; (MCF). Because it is
* fully reversible (i.e. Hash -&amp;gt; String, String -&amp;gt; Hash), it does NOT use the traditional MCF encoding alphabet
* (the traditional MCF encoding, aka H64, is bit-destructive and cannot be reversed). Instead, it uses fully
* reversible Base64 encoding for the Hash digest and any salt value.
* &lt;h2&gt;Format&lt;/h2&gt;
* &lt;p&gt;Hash instances formatted with this implementation will result in a String with the following dollar-sign ($)
* delimited format:&lt;/p&gt;
* &lt;pre&gt;
* &lt;b&gt;$&lt;/b&gt;mcfFormatId&lt;b&gt;$&lt;/b&gt;algorithmName&lt;b&gt;$&lt;/b&gt;iterationCount&lt;b&gt;$&lt;/b&gt;base64EncodedSalt&lt;b&gt;$&lt;/b&gt;base64EncodedDigest
* &lt;/pre&gt;
* &lt;p&gt;Each token is defined as follows:&lt;/p&gt;
* &lt;table&gt;
* &lt;tr&gt;
* &lt;th&gt;Position&lt;/th&gt;
* &lt;th&gt;Token&lt;/th&gt;
* &lt;th&gt;Description&lt;/th&gt;
* &lt;th&gt;Required?&lt;/th&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;1&lt;/td&gt;
* &lt;td&gt;{@code mcfFormatId}&lt;/td&gt;
* &lt;td&gt;The Modular Crypt Format identifier for this implementation, equal to &lt;b&gt;{@code shiro1}&lt;/b&gt;.
* ( This implies that all {@code shiro1} MCF-formatted strings will always begin with the prefix
* {@code $shiro1$} ).&lt;/td&gt;
* &lt;td&gt;true&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;2&lt;/td&gt;
* &lt;td&gt;{@code algorithmName}&lt;/td&gt;
* &lt;td&gt;The name of the hash algorithm used to perform the hash. This is an algorithm name understood by
* {@code MessageDigest}.{@link java.security.MessageDigest#getInstance(String) getInstance}, for example
* {@code MD5}, {@code SHA-256}, {@code SHA-256}, etc.&lt;/td&gt;
* &lt;td&gt;true&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;3&lt;/td&gt;
* &lt;td&gt;{@code iterationCount}&lt;/td&gt;
* &lt;td&gt;The number of hash iterations performed.&lt;/td&gt;
* &lt;td&gt;true (1 &lt;= N &lt;= Integer.MAX_VALUE)&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;4&lt;/td&gt;
* &lt;td&gt;{@code base64EncodedSalt}&lt;/td&gt;
* &lt;td&gt;The Base64-encoded salt byte array. This token only exists if a salt was used to perform the hash.&lt;/td&gt;
* &lt;td&gt;false&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;5&lt;/td&gt;
* &lt;td&gt;{@code base64EncodedDigest}&lt;/td&gt;
* &lt;td&gt;The Base64-encoded digest byte array. This is the actual hash result.&lt;/td&gt;
* &lt;td&gt;true&lt;/td&gt;
* &lt;/tr&gt;
* &lt;/table&gt;
*
* @see ModularCryptFormat
* @see ParsableHashFormat
*
* @since 1.2
*/
public class Shiro1CryptFormat implements ModularCryptFormat, ParsableHashFormat {
public static final String ID = &quot;shiro1&quot;;
public static final String MCF_PREFIX = TOKEN_DELIMITER + ID + TOKEN_DELIMITER;
<span class="fc" id="L93"> public Shiro1CryptFormat() {</span>
<span class="fc" id="L94"> }</span>
public String getId() {
<span class="fc" id="L97"> return ID;</span>
}
public String format(Hash hash) {
<span class="fc bfc" id="L101" title="All 2 branches covered."> if (hash == null) {</span>
<span class="fc" id="L102"> return null;</span>
}
<span class="fc" id="L105"> String algorithmName = hash.getAlgorithmName();</span>
<span class="fc" id="L106"> ByteSource salt = hash.getSalt();</span>
<span class="fc" id="L107"> int iterations = hash.getIterations();</span>
<span class="fc" id="L108"> StringBuilder sb = new StringBuilder(MCF_PREFIX).append(algorithmName).append(TOKEN_DELIMITER).append(iterations).append(TOKEN_DELIMITER);</span>
<span class="fc bfc" id="L110" title="All 2 branches covered."> if (salt != null) {</span>
<span class="fc" id="L111"> sb.append(salt.toBase64());</span>
}
<span class="fc" id="L114"> sb.append(TOKEN_DELIMITER);</span>
<span class="fc" id="L115"> sb.append(hash.toBase64());</span>
<span class="fc" id="L117"> return sb.toString();</span>
}
public Hash parse(String formatted) {
<span class="fc bfc" id="L121" title="All 2 branches covered."> if (formatted == null) {</span>
<span class="fc" id="L122"> return null;</span>
}
<span class="fc bfc" id="L124" title="All 2 branches covered."> if (!formatted.startsWith(MCF_PREFIX)) {</span>
//TODO create a HashFormatException class
<span class="fc" id="L126"> String msg = &quot;The argument is not a valid '&quot; + ID + &quot;' formatted hash.&quot;;</span>
<span class="fc" id="L127"> throw new IllegalArgumentException(msg);</span>
}
<span class="fc" id="L130"> String suffix = formatted.substring(MCF_PREFIX.length());</span>
<span class="fc" id="L131"> String[] parts = suffix.split(&quot;\\$&quot;);</span>
//last part is always the digest/checksum, Base64-encoded:
<span class="fc" id="L134"> int i = parts.length-1;</span>
<span class="fc" id="L135"> String digestBase64 = parts[i--];</span>
//second-to-last part is always the salt, Base64-encoded:
<span class="fc" id="L137"> String saltBase64 = parts[i--];</span>
<span class="fc" id="L138"> String iterationsString = parts[i--];</span>
<span class="fc" id="L139"> String algorithmName = parts[i];</span>
<span class="fc" id="L141"> byte[] digest = Base64.decode(digestBase64);</span>
<span class="fc" id="L142"> ByteSource salt = null;</span>
<span class="fc bfc" id="L144" title="All 2 branches covered."> if (StringUtils.hasLength(saltBase64)) {</span>
<span class="fc" id="L145"> byte[] saltBytes = Base64.decode(saltBase64);</span>
<span class="fc" id="L146"> salt = ByteSource.Util.bytes(saltBytes);</span>
}
int iterations;
try {
<span class="fc" id="L151"> iterations = Integer.parseInt(iterationsString);</span>
<span class="fc" id="L152"> } catch (NumberFormatException e) {</span>
<span class="fc" id="L153"> String msg = &quot;Unable to parse formatted hash string: &quot; + formatted;</span>
<span class="fc" id="L154"> throw new IllegalArgumentException(msg, e);</span>
<span class="fc" id="L155"> }</span>
<span class="fc" id="L157"> SimpleHash hash = new SimpleHash(algorithmName);</span>
<span class="fc" id="L158"> hash.setBytes(digest);</span>
<span class="fc bfc" id="L159" title="All 2 branches covered."> if (salt != null) {</span>
<span class="fc" id="L160"> hash.setSalt(salt);</span>
}
<span class="fc" id="L162"> hash.setIterations(iterations);</span>
<span class="fc" id="L164"> return hash;</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.eclemma.org/jacoco">JaCoCo</a> 0.7.7.201606060606</span></div></body></html>