All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.nremond.PBKDF2.scala Maven / Gradle / Ivy

/**
 *  Copyright 2012-2014 Nicolas Rémond (@nremond)
 *
 *  Licensed 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 io.github.nremond

import java.nio.{ ByteBuffer, IntBuffer }
import javax.crypto

object PBKDF2 {

  private[this] def bytesFromInt(i: Int) = ByteBuffer.allocate(4).putInt(i).array

  private[this] def xor(buff: Array[Int], a2: Array[Byte]) {
    val b2 = ByteBuffer.wrap(a2).asIntBuffer

    val len = buff.array.size
    var i = 0
    while (i < len) {
      buff(i) ^= b2.get(i)
      i += 1
    }
  }

  /**
   * Implements PBKDF2 as defined in RFC 2898, section 5.2
   *
   * HMAC+SHA256 is used as the default pseudo random function.
   *
   * Right now 20,000 iterations is the strictly recommended default minimum. It takes 100ms on a i5 M-580 2.6GHz CPU.
   * The minimum increases every year, please keep that in mind.
   *
   * @param password the password to encrypt
   * @param salt the NIST recommends salt that is at least 128 bits(16 bytes) long (http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf)
   * @param iterations the number of encryption iterations
   * @param dkLength derived-key length
   * @param cryptoAlgo HMAC+SHA512 is the default and note that HMAC+SHA1 is now considered weak
   * @return the hashed password
   */
  def apply(password: Array[Byte], salt: Array[Byte], iterations: Int = 20000, dkLength: Int = 32, cryptoAlgo: String = "HmacSHA512"): Array[Byte] = {

    val mac = crypto.Mac.getInstance(cryptoAlgo)
    mac.init(new crypto.spec.SecretKeySpec(password, "RAW"))

      // pseudo-random function defined in the spec
      @inline def prf(buff: Array[Byte]) = mac.doFinal(buff)

      // this is a translation of the helper function "F" defined in the spec
      def calculateBlock(blockNum: Int): Array[Byte] = {
        // u_1
        val u_1 = prf(salt ++ bytesFromInt(blockNum))

        val buff = IntBuffer.allocate(u_1.length / 4).put(ByteBuffer.wrap(u_1).asIntBuffer).array.clone
        var u = u_1
        var iter = 1
        while (iter < iterations) {
          // u_2 through u_c : calculate u_n and xor it with the previous value
          u = prf(u)
          xor(buff, u)
          iter += 1
        }

        val ret = ByteBuffer.allocate(u_1.length)
        ret.asIntBuffer.put(buff)
        ret.array
      }

    // how many blocks we'll need to calculate (the last may be truncated)
    val blocksNeeded = (dkLength.toFloat / 20).ceil.toInt

    (1 to blocksNeeded).iterator.map(calculateBlock).flatten.take(dkLength).toArray
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy