Java中hash+salt的加密算法

一般我们存储密码的时候,使用hash算法进行存储,但是,这样做是不安全的,虽然不能反向生成密码,但是可以通过彩虹法和反向查表法高效的猜解出密码。

比较安全的做法就是使用hash+salt的加密算法。

这里使用了RFC2898标准。

看代码:

Rfc2898DeriveBytes.java

package com.poreader.common;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

/**
 * This implementation follows RFC 2898 recommendations. See
 * http://www.ietf.org/rfc/Rfc2898.txt
 */
public class Rfc2898DeriveBytes {
	private static final int BLOCK_SIZE = 20;
	private static Random random = new Random();
	private Mac hmacsha1;
	private byte[] salt;
	private int iterations;
	private byte[] buffer = new byte[BLOCK_SIZE];
	private int startIndex = 0;
	private int endIndex = 0;
	private int block = 1;

	/**
	 * Creates new instance.
	 * 
	 * @param password
	 *            The password used to derive the key.
	 * @param salt
	 *            The key salt used to derive the key.
	 * @param iterations
	 *            The number of iterations for the operation.
	 * @throws NoSuchAlgorithmException
	 *             HmacSHA1 algorithm cannot be found.
	 * @throws InvalidKeyException
	 *             Salt must be 8 bytes or more. -or- Password cannot be null.
	 */
	public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations) throws NoSuchAlgorithmException,
			InvalidKeyException {
		this.salt = salt;
		this.iterations = iterations;
		this.hmacsha1 = Mac.getInstance("HmacSHA1");
		this.hmacsha1.init(new SecretKeySpec(password, "HmacSHA1"));
	}

	/**
	 * Creates new instance.
	 * 
	 * @param password
	 *            The password used to derive the key.
	 * @param salt
	 *            The key salt used to derive the key.
	 * @param iterations
	 *            The number of iterations for the operation.
	 * @throws NoSuchAlgorithmException
	 *             HmacSHA1 algorithm cannot be found.
	 * @throws InvalidKeyException
	 *             Salt must be 8 bytes or more. -or- Password cannot be null.
	 * @throws UnsupportedEncodingException
	 */
	public Rfc2898DeriveBytes(String password, int saltSize, int iterations) throws NoSuchAlgorithmException,
			InvalidKeyException, UnsupportedEncodingException {
		this.salt = randomSalt(saltSize);
		this.iterations = iterations;
		this.hmacsha1 = Mac.getInstance("HmacSHA1");
		this.hmacsha1.init(new SecretKeySpec(password.getBytes("UTF-8"), "HmacSHA1"));
		this.buffer = new byte[BLOCK_SIZE];
		this.block = 1;
		this.startIndex = this.endIndex = 1;
	}

	/**
	 * Creates new instance.
	 * 
	 * @param password
	 *            The password used to derive the key.
	 * @param salt
	 *            The key salt used to derive the key.
	 * @param iterations
	 *            The number of iterations for the operation.
	 * @throws NoSuchAlgorithmException
	 *             HmacSHA1 algorithm cannot be found.
	 * @throws InvalidKeyException
	 *             Salt must be 8 bytes or more. -or- Password cannot be null.
	 * @throws UnsupportedEncodingException
	 */
	public Rfc2898DeriveBytes(String password, int saltSize) throws NoSuchAlgorithmException, InvalidKeyException,
			UnsupportedEncodingException {
		this(password, saltSize, 1000);
	}

	/**
	 * Creates new instance.
	 * 
	 * @param password
	 *            The password used to derive the key.
	 * @param salt
	 *            The key salt used to derive the key.
	 * @param iterations
	 *            The number of iterations for the operation.
	 * @throws NoSuchAlgorithmException
	 *             HmacSHA1 algorithm cannot be found.
	 * @throws InvalidKeyException
	 *             Salt must be 8 bytes or more. -or- Password cannot be null.
	 * @throws UnsupportedEncodingException
	 *             UTF-8 encoding is not supported.
	 */
	public Rfc2898DeriveBytes(String password, byte[] salt, int iterations) throws InvalidKeyException,
			NoSuchAlgorithmException, UnsupportedEncodingException {
		this(password.getBytes("UTF8"), salt, iterations);
	}

	public byte[] getSalt() {
		return this.salt;
	}

	public String getSaltAsString() {
		return Base64.encodeBase64String(this.salt);
	}

	/**
	 * Returns a pseudo-random key from a data, salt and iteration count.
	 * 
	 * @param cb
	 *            Number of bytes to return.
	 * @return Byte array.
	 */
	public byte[] getBytes(int cb) {
		byte[] result = new byte[cb];
		int offset = 0;
		int size = this.endIndex - this.startIndex;
		if (size > 0) { // if there is some data in buffer
			if (cb >= size) { // if there is enough data in buffer
				System.arraycopy(this.buffer, this.startIndex, result, 0, size);
				this.startIndex = this.endIndex = 0;
				offset += size;
			} else {
				System.arraycopy(this.buffer, this.startIndex, result, 0, cb);
				startIndex += cb;
				return result;
			}
		}

		while (offset < cb) {
			byte[] block = this.func();
			int remainder = cb - offset;
			if (remainder > BLOCK_SIZE) {
				System.arraycopy(block, 0, result, offset, BLOCK_SIZE);
				offset += BLOCK_SIZE;
			} else {
				System.arraycopy(block, 0, result, offset, remainder);
				offset += remainder;
				System.arraycopy(block, remainder, this.buffer, startIndex, BLOCK_SIZE - remainder);
				endIndex += (BLOCK_SIZE - remainder);
				return result;
			}
		}
		return result;
	}

	public static byte[] randomSalt(int size) {
		byte[] salt = new byte[size];
		random.nextBytes(salt);
		return salt;
	}

	/**
	 * Generate random Salt
	 * 
	 * @param size
	 * @return
	 */
	public static String generateSalt(int size) {
		byte[] salt = randomSalt(size);
		return Base64.encodeBase64String(salt);
	}

	private byte[] func() {
		this.hmacsha1.update(this.salt, 0, this.salt.length);
		byte[] tempHash = this.hmacsha1.doFinal(getBytesFromInt(this.block));

		this.hmacsha1.reset();
		byte[] finalHash = tempHash;
		for (int i = 2; i <= this.iterations; i++) {
			tempHash = this.hmacsha1.doFinal(tempHash);
			for (int j = 0; j < 20; j++) {
				finalHash[j] = (byte) (finalHash[j] ^ tempHash[j]);
			}
		}
		if (this.block == 2147483647) {
			this.block = -2147483648;
		} else {
			this.block += 1;
		}
		return finalHash;
	}

	private static byte[] getBytesFromInt(int i) {
		return new byte[] { (byte) (i >>> 24), (byte) (i >>> 16), (byte) (i >>> 8), (byte) i };
	}
}


CryptoUtils.java

package com.poreader.common;

import org.apache.commons.codec.binary.Base64;

public class CryptoUtils {
	private static int saltSize = 32;
	private static int iterations = 1000;
	private static int subKeySize = 32;

	/**
	 * 获取 Salt
	 * @return
	 */
	public static String getSalt() {
		return Rfc2898DeriveBytes.generateSalt(saltSize);
	}

	/**
	 * 获取hash后的密码
	 * @param password
	 * @param salt
	 * @return
	 */
	public static String getHash(String password, String salt) {
		Rfc2898DeriveBytes keyGenerator = null;
		try {
			keyGenerator = new Rfc2898DeriveBytes(password + salt, saltSize, iterations);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		byte[] subKey = keyGenerator.getBytes(subKeySize);
		byte[] bSalt = keyGenerator.getSalt();
		byte[] hashPassword = new byte[1 + saltSize + subKeySize];
		System.arraycopy(bSalt, 0, hashPassword, 1, saltSize);
		System.arraycopy(subKey, 0, hashPassword, saltSize + 1, subKeySize);
		return Base64.encodeBase64String(hashPassword);
	}

	/**
	 * 验证密码
	 * @param hashedPassword
	 * @param password
	 * @param salt
	 * @return
	 */
	public static boolean verify(String hashedPassword, String password, String salt) {
		byte[] hashedPasswordBytes = Base64.decodeBase64(hashedPassword);
		if (hashedPasswordBytes.length != (1 + saltSize + subKeySize) || hashedPasswordBytes[0] != 0x00) {
			return false;
		}

		byte[] bSalt = new byte[saltSize];
		System.arraycopy(hashedPasswordBytes, 1, bSalt, 0, saltSize);
		byte[] storedSubkey = new byte[subKeySize];
		System.arraycopy(hashedPasswordBytes, 1 + saltSize, storedSubkey, 0, subKeySize);
		Rfc2898DeriveBytes deriveBytes = null;
		try {
			deriveBytes = new Rfc2898DeriveBytes(password + salt, bSalt, iterations);
		} catch (Exception e) {
			e.printStackTrace();
		}
		byte[] generatedSubkey = deriveBytes.getBytes(subKeySize);
		return byteArraysEqual(storedSubkey, generatedSubkey);
	}

	private static boolean byteArraysEqual(byte[] storedSubkey, byte[] generatedSubkey) {
		int size = storedSubkey.length;
		if (size != generatedSubkey.length) {
			return false;
		}

		for (int i = 0; i < size; i++) {
			if (storedSubkey[i] != generatedSubkey[i]) {
				return false;
			}
		}
		return true;
	}

}

验证:

	public static void main(String[] args) throws NoSuchAlgorithmException {
		String salt = CryptoUtils.getSalt();
		String password = "admin123";
		String hashPassword = CryptoUtils.getHash(password, salt);
		System.out.println("hashPassword:" + hashPassword);
		System.out.println("salt:" + salt);
		System.out.println("password:" + password);
		// verify
		boolean result = CryptoUtils.verify(hashPassword, password, salt);
		System.out.println("Verify:" + result);

	}



  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值