mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Ludovic Poitou
22.03.2016 beb573f909c6460b1335518120185f3f49565242
Cleanup and alignment with newer version of the Apache sha2crypt.java file.
Note that this version is in public domain, so it doesn't have any initial copyright.
1 files modified
232 ■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/extensions/Sha2Crypt.java 232 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/Sha2Crypt.java
@@ -11,8 +11,8 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Portions Copyright 2012 Dariusz Janny <dariusz.janny@gmail.com>
 * Copyright 2013 ForgeRock AS.
 * Portions Copyright 2013-2016 ForgeRock AS.
 *
 * 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.
@@ -31,25 +31,22 @@
package org.opends.server.extensions;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.codec.Charsets;
/**
 * SHA2-based Unix crypt implementation.
 *
 * <p>
 * Based on the C implementation released into the Public Domain by Ulrich
 * Drepper &lt;drepper@redhat.com&gt;
 * Based on the C implementation released into the Public Domain by Ulrich Drepper &lt;drepper@redhat.com&gt;
 * http://www.akkadia.org/drepper/SHA-crypt.txt
 * </p>
 *
 * <p>
 * Conversion to Kotlin and from there to Java in 2012 by Christian Hammers
 * &lt;ch@lathspell.de&gt; and likewise put into the Public Domain.
 * </p>
 *
 * Conversion to Kotlin and from there to Java in 2012 by Christian Hammers &lt;ch@lathspell.de&gt; and likewise put
 * into the Public Domain.
 * <p>
 * This class is immutable and thread-safe.
 * </p>
@@ -79,7 +76,7 @@
   * @version $Id$
   * @since 1.7
   */
  static class B64 {
  private static class B64 {
      /**
       * Table with characters for Base64 transformation.
@@ -138,138 +135,132 @@
   */
  private static final int ROUNDS_DEFAULT = 5000;
  /**
   * Maximum number of rounds.
   */
  /** Maximum number of rounds. */
  private static final int ROUNDS_MAX = 999999999;
  /**
   * Minimum number of rounds.
   */
  /** Minimum number of rounds. */
  private static final int ROUNDS_MIN = 1000;
  /**
   * Prefix for optional rounds specification.
   */
  /** Prefix for optional rounds specification. */
  private static final String ROUNDS_PREFIX = "rounds=";
  /**
   * The MessageDigest algorithms.
   */
  /** The MessageDigest algorithms. */
  private static final String SHA256_ALGORITHM = "SHA-256";
  private static final String SHA512_ALGORITHM = "SHA-512";
  /**
   * The number of bytes the final hash value will have.
   */
  /** The number of bytes the final hash value will have. */
  private static final int SHA256_BLOCKSIZE = 32;
  /** The prefixes that can be used to identify this crypt() variant (SHA-256). */
  private static final String SHA256_PREFIX = "$5$";
  /** The number of bytes the final hash value will have (SHA-512 variant). */
  private static final int SHA512_BLOCKSIZE = 64;
  /**
   * The prefixes that can be used to identify this crypt() variant.
   */
  static final String SHA256_PREFIX = "$5$";
  /** The prefixes that can be used to identify this crypt() variant (SHA-512). */
  private static final String SHA512_PREFIX = "$6$";
  static final String SHA512_PREFIX = "$6$";
  /** The pattern to match valid salt values. */
  private static final Pattern SALT_PATTERN = Pattern
          .compile("^\\$([56])\\$(rounds=(\\d+)\\$)?([\\.\\/a-zA-Z0-9]{1,16}).*");
  /**
   * Returns the magic string denoting the SHA-256 scheme is being used.
   *
   * @return the magic string
   */
  public static String getMagicSHA256Prefix()
  static String getMagicSHA256Prefix()
  {
    return SHA256_PREFIX;
  }
  /**
   * Returns the magic string denoting the SHA-512 scheme is being used.
   *
   * @return the magic string
   */
  public static String getMagicSHA512Prefix()
  static String getMagicSHA512Prefix()
  {
    return SHA512_PREFIX;
  }
  /**
   * Generates a libc crypt() compatible "$5$" hash value with random salt.
   *
   * <p>
   * See {@link Crypt#crypt(String, String)} for details.
   *
   * @param keyBytes
   *          The plaintext that should be hashed.
   * @throws Exception exception
   * @return compatible "$5$" hash value with random sal
   *            plaintext to hash
   * @return complete hash value
   * @throws RuntimeException
   *             when a {@link java.security.NoSuchAlgorithmException} is caught.
   */
  public static String sha256Crypt(byte[] keyBytes) throws Exception {
  static String sha256Crypt(final byte[] keyBytes) {
    return sha256Crypt(keyBytes, null);
  }
  /**
   * Generates a libc6 crypt() compatible "$5$" hash value.
   *
   * <p>
   * See {@link Crypt#crypt(String, String)} for details.
   *
   * @param keyBytes
   *          The plaintext that should be hashed.
   *            plaintext to hash
   * @param salt
   *          The real salt value without prefix or "rounds=".
   * @throws Exception exception
   * @return compatible "$5$" hash value
   *            real salt value without prefix or "rounds="
   * @return complete hash value including salt
   * @throws IllegalArgumentException
   *             if the salt does not match the allowed pattern
   * @throws RuntimeException
   *             when a {@link java.security.NoSuchAlgorithmException} is caught.
   */
  public static String sha256Crypt(byte[] keyBytes, String salt)
      throws Exception {
  static String sha256Crypt(final byte[] keyBytes, String salt) {
    if (salt == null) {
      salt = SHA256_PREFIX + B64.getRandomSalt(8);
    }
    return sha2Crypt(keyBytes, salt, SHA256_PREFIX, SHA256_BLOCKSIZE,
        SHA256_ALGORITHM);
    return sha2Crypt(keyBytes, salt, SHA256_PREFIX, SHA256_BLOCKSIZE, SHA256_ALGORITHM);
  }
  /**
   * Generates a libc6 crypt() compatible "$5$" or "$6$" SHA2 based hash value.
   *
   * This is a nearly line by line conversion of the original C function. The
   * numbered comments are from the algorithm description, the short C-style
   * ones from the original C code and the ones with "Remark" from me.
   *
   * <p>
   * This is a nearly line by line conversion of the original C function. The numbered comments are from the algorithm
   * description, the short C-style ones from the original C code and the ones with "Remark" from me.
   * <p>
   * See {@link Crypt#crypt(String, String)} for details.
   *
   * @param keyBytes
   *          The plaintext that should be hashed.
   * @param salt_string
   *          The real salt value without prefix or "rounds=".
   *            plaintext to hash
   * @param salt
   *            real salt value without prefix or "rounds="
   * @param saltPrefix
   *          Either $5$ or $6$.
   *            either $5$ or $6$
   * @param blocksize
   *          A value that differs between $5$ and $6$.
   *            a value that differs between $5$ and $6$
   * @param algorithm
   *          The MessageDigest algorithm identifier string.
   * @throws Exception exception
   * @return The complete hash value including prefix and salt.
   *            {@link MessageDigest} algorithm identifier string
   * @return complete hash value including prefix and salt
   * @throws IllegalArgumentException
   *             if the given salt is <code>null</code> or does not match the allowed pattern
   * @throws IllegalArgumentException
   *             when a {@link NoSuchAlgorithmException} is caught
   * @see MessageDigestAlgorithms
   */
  private static String sha2Crypt(byte[] keyBytes, String salt,
      String saltPrefix, int blocksize, String algorithm) throws Exception {
    int keyLen = keyBytes.length;
  private static String sha2Crypt(final byte[] keyBytes, final String salt, final String saltPrefix,
                                  final int blocksize, final String algorithm) {
    final int keyLen = keyBytes.length;
    // Extracts effective salt and the number of rounds from the given salt.
    int rounds = ROUNDS_DEFAULT;
    boolean roundsCustom = false;
    if (salt == null) {
      throw new IllegalArgumentException("Invalid salt value: null");
      throw new IllegalArgumentException("Salt must not be null");
    }
    Pattern p = Pattern
        .compile("^\\$([56])\\$(rounds=(\\d+)\\$)?([\\.\\/a-zA-Z0-9]{1,16}).*");
    Matcher m = p.matcher(salt);
    if (m == null || !m.find()) {
    final Matcher m = SALT_PATTERN.matcher(salt);
    if (!m.find()) {
      throw new IllegalArgumentException("Invalid salt value: " + salt);
    }
    if (m.group(3) != null) {
@@ -277,13 +268,13 @@
      rounds = Math.max(ROUNDS_MIN, Math.min(ROUNDS_MAX, rounds));
      roundsCustom = true;
    }
    String saltString = m.group(4);
    byte[] saltBytes = saltString.getBytes("UTF-8");
    int saltLen = saltBytes.length;
    final String saltString = m.group(4);
    final byte[] saltBytes = saltString.getBytes(Charsets.UTF_8);
    final int saltLen = saltBytes.length;
    // 1. start digest A
    // Prepare for the real work.
    MessageDigest ctx = MessageDigest.getInstance(algorithm);
    MessageDigest ctx = getDigest(algorithm);
    // 2. the password string is added to digest A
    /*
@@ -301,18 +292,17 @@
    // specification should never be added this would also create an
    // inconsistency.
    /*
     * The last part is the salt string. This must be at most 16 characters and
     * it ends at the first `$' character (for compatibility with existing
     * implementations).
         * The last part is the salt string. This must be at most 16 characters and it ends at the first `$' character
         * (for compatibility with existing implementations).
     */
    ctx.update(saltBytes);
    // 4. start digest B
    /*
     * Compute alternate sha512 sum with input KEY, SALT, and KEY. The final
     * result will be added to the first context.
         * Compute alternate sha512 sum with input KEY, SALT, and KEY. The final result will be added to the first
         * context.
     */
    MessageDigest altCtx = MessageDigest.getInstance(algorithm);
    MessageDigest altCtx = getDigest(algorithm);
    // 5. add the password to digest B
    /*
@@ -367,8 +357,8 @@
    // NB: this step differs significantly from the MD5 algorithm. It
    // adds more randomness.
    /*
     * Take the binary representation of the length of the key and for every 1
     * add the alternate sum, for every 0 the key.
         * Take the binary representation of the length of the key and for every 1 add the alternate sum, for every 0
         * the key.
     */
    cnt = keyBytes.length;
    while (cnt > 0) {
@@ -390,7 +380,7 @@
    /*
     * Start computation of P byte sequence.
     */
    altCtx = MessageDigest.getInstance(algorithm);
    altCtx = getDigest(algorithm);
    // 14. for every byte in the password (excluding the terminating NUL byte
    // in the C representation of the string)
@@ -419,7 +409,7 @@
    /*
     * Create byte sequence P.
     */
    byte[] pBytes = new byte[keyLen];
    final byte[] pBytes = new byte[keyLen];
    int cp = 0;
    while (cp < keyLen - blocksize) {
      System.arraycopy(tempResult, 0, pBytes, cp, blocksize);
@@ -431,7 +421,7 @@
    /*
     * Start computation of S byte sequence.
     */
    altCtx = MessageDigest.getInstance(algorithm);
    altCtx = getDigest(algorithm);
    // 18. repeast the following 16+A[0] times, where A[0] represents the first
    // byte in digest A interpreted as an 8-bit unsigned value
@@ -461,7 +451,7 @@
     * Create byte sequence S.
     */
    // Remark: The salt is limited to 16 chars, how does this make sense?
    byte[] sBytes = new byte[saltLen];
    final byte[] sBytes = new byte[saltLen];
    cp = 0;
    while (cp < saltLen - blocksize) {
      System.arraycopy(tempResult, 0, sBytes, cp, blocksize);
@@ -478,15 +468,14 @@
    // produced in step 21.h. The following text uses the notation
    // "digest A/C" to describe this behavior.
    /*
     * Repeatedly run the collected hash value through sha512 to burn CPU
     * cycles.
         * Repeatedly run the collected hash value through sha512 to burn CPU cycles.
     */
    for (int i = 0; i <= rounds - 1; i++) {
      // a) start digest C
      /*
       * New context.
       */
      ctx = MessageDigest.getInstance(algorithm);
      ctx = getDigest(algorithm);
      // b) for odd round numbers add the byte sequence P to digest C
      // c) for even round numbers add digest A/C
@@ -548,9 +537,14 @@
    /*
     * Now we can construct the result string. It consists of three parts.
     */
    StringBuilder buffer = new StringBuilder(saltPrefix
        + (roundsCustom ? ROUNDS_PREFIX + rounds + "$" : "")
        + saltString + "$");
    final StringBuilder buffer = new StringBuilder(saltPrefix);
    if (roundsCustom) {
      buffer.append(ROUNDS_PREFIX);
      buffer.append(rounds);
      buffer.append("$");
    }
    buffer.append(saltString);
    buffer.append("$");
    // e) the base-64 encoded final C digest. The encoding used is as
    // follows:
@@ -574,8 +568,7 @@
    // characters as output for SHA-512 and SHA-512 respectively.
    // This was just a safeguard in the C implementation:
    // int buflen = salt_prefix.length() - 1 + ROUNDS_PREFIX.length() + 9 + 1 +
    // salt_string.length() + 1 + 86 + 1;
    // int buflen = salt_prefix.length() - 1 + ROUNDS_PREFIX.length() + 9 + 1 + salt_string.length() + 1 + 86 + 1;
    if (blocksize == 32) {
      B64.b64from24bit(altResult[0], altResult[10], altResult[20], 4, buffer);
@@ -615,8 +608,8 @@
    }
    /*
     * Clear the buffer for the intermediate result so that people attaching to
     * processes or reading core dumps cannot get any information.
         * Clear the buffer for the intermediate result so that people attaching to processes or reading core dumps
         * cannot get any information.
     */
    // Is there a better way to do this with the JVM?
    Arrays.fill(tempResult, (byte) 0);
@@ -632,36 +625,47 @@
  /**
   * Generates a libc crypt() compatible "$6$" hash value with random salt.
   *
   * <p>
   * See {@link Crypt#crypt(String, String)} for details.
   *
   * @param keyBytes
   *          The plaintext that should be hashed.
   * @throws Exception exception
   * @return compatible "$6$" hash value with random salt
   *            plaintext to hash
   * @return complete hash value
   * @throws RuntimeException
   *             when a {@link java.security.NoSuchAlgorithmException} is caught.
   */
  public static String sha512Crypt(byte[] keyBytes) throws Exception {
  static String sha512Crypt(final byte[] keyBytes) {
    return sha512Crypt(keyBytes, null);
  }
  /**
   * Generates a libc6 crypt() compatible "$6$" hash value.
   *
   * <p>
   * See {@link Crypt#crypt(String, String)} for details.
   *
   * @param keyBytes
   *          The plaintext that should be hashed.
   *            plaintext to hash
   * @param salt
   *          The real salt value without prefix or "rounds=".
   * @throws Exception exception
   * @return compatible "$6$" hash value
   *            real salt value without prefix or "rounds="
   * @return complete hash value including salt
   * @throws IllegalArgumentException
   *             if the salt does not match the allowed pattern
   * @throws RuntimeException
   *             when a {@link java.security.NoSuchAlgorithmException} is caught.
   */
  public static String sha512Crypt(byte[] keyBytes, String salt)
      throws Exception {
  static String sha512Crypt(final byte[] keyBytes, String salt) {
    if (salt == null) {
      salt = SHA512_PREFIX + B64.getRandomSalt(8);
    }
    return sha2Crypt(keyBytes, salt, SHA512_PREFIX, SHA512_BLOCKSIZE,
        SHA512_ALGORITHM);
    return sha2Crypt(keyBytes, salt, SHA512_PREFIX, SHA512_BLOCKSIZE, SHA512_ALGORITHM);
  }
  private static MessageDigest getDigest(final String algorithm) {
    try {
      return MessageDigest.getInstance(algorithm);
    } catch (final NoSuchAlgorithmException e) {
      throw new IllegalArgumentException(e);
    }
  }
}