Libc crypt() “$1$” 和 Apache “$apr1$” 是基于 MD5 的哈希算法。
该类基于 Poul-Henning Kamp 的公共领域(“beer-ware”)C 实现,该实现位于:crypt-md5.c@freebsd.org
资源:
$ FreeBSD:src/lib/libcrypt/crypt-md5.c,v 1.1 1999/01/21 13:50:09 brandon Exp $
在 2012 年转换为 Kotlin,然后从 Kotlin 转换到 Java。
注意:此类是不可变的并且是线程安全的。
static String apr1Crypt(String keyBytes, String salt)
生成基于 Apache htpasswd 的兼容 “$apr1$” MD5 的哈希值。该算法与 crypt(3) “$1$” 相同,但是由于盐前缀不同而产生的输出也不同。
参数说明:
keyBytes 要散列的纯文本字符串。
salt 盐字符串,你不需要在盐字符串前面添加类似 “$1$” 字符串的前缀,Md5Crypt 会帮你添加。
异常:
IllegalArgumentException 如果盐与允许的模式不匹配
IllegalArgumentException 当捕获到 NoSuchAlgorithmException 时
演示 Md5Crypt 类的 apr1Crypt() 方法用法,代码如下:
import org.apache.commons.codec.digest.Md5Crypt; import java.security.SecureRandom; import java.util.concurrent.ThreadLocalRandom; /** * Md5Crypt 演示 * @author Administrator * @date 2021/1/25 13:08 */ public class Md5CryptDemo1 { public static void main(String[] args) { String str = "hello world"; byte[] bytes = str.getBytes(); // 默认使用 ThreadLocalRandom 生成随机盐,因此,每次计算的摘要信息是不同 System.out.println(Md5Crypt.apr1Crypt(str)); System.out.println(Md5Crypt.apr1Crypt(bytes)); // 指定固定的盐,apr1Crypt 每次只获取 [0-9a-zA-Z.] 范围内,最大长度为 8 个盐字符串 System.out.println(Md5Crypt.apr1Crypt(str, "0123456789")); // 这里的盐只截取了 “012Ab.” System.out.println(Md5Crypt.apr1Crypt(str, "012Ab.!Bb")); System.out.println(Md5Crypt.apr1Crypt(bytes, "0123456789")); // 手动指定 SecureRandom,每次调用将返回不同的盐(推荐做法) System.out.println(Md5Crypt.apr1Crypt(bytes, new SecureRandom())); // 手动指定一个 ThreadLocalRandom,每次调用将返回不同的盐 System.out.println(Md5Crypt.apr1Crypt(bytes, ThreadLocalRandom.current())); } }
输出结果:
$apr1$o5WfMCfP$XDu47oVgJh/tOHblFrKic1 $apr1$.FMB9eOd$cU7WEufQ13PO9jig7g.Qh. $apr1$01234567$UQphyJiVOJNx1xgKtOKGp. $apr1$012Ab.$UZv8LTS625aTKxdQfysKj. $apr1$01234567$g8ZoSZ3YuhfUUy4ot/7tK1 $apr1$ut.6JkwN$O3/aDZhS593ENLALpS58z/ $apr1$BEPec2bv$1Xk6hEgEtCnceUrpnHPeF0
注意,上面实例中的 Md5Crypt.apr1Crypt(byte[])、Md5Crypt.apr1Crypt(String) 将提供默认随机盐,源码如下:
public static String apr1Crypt(final byte[] keyBytes) { return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8)); }
其中,APR1_PREFIX 为 “$arp1$”,而 B64.getRandomSalt() 方法代码如下:
static String getRandomSalt(final int num) { return getRandomSalt(num, new SecureRandom()); }
生成与 libc6 crypt() 兼容的 “$1$” 哈希值。实例
import org.apache.commons.codec.digest.Md5Crypt; public class Md5CryptDemo2 { public static void main(String[] args) { String str = "hello world"; System.out.println(Md5Crypt.md5Crypt(str.getBytes())); System.out.println(Md5Crypt.md5Crypt(str.getBytes())); // 指定随机盐 System.out.println(Md5Crypt.md5Crypt(str.getBytes(), ThreadLocalRandom.current())); System.out.println(Md5Crypt.md5Crypt(str.getBytes(), new SecureRandom())); } }
输出结果:
$1$k10MhM7H$1jnNZq6j8mCcMZtE/.LBh/ $1$oLExMIb8$N4FIfQqBFTcehL3ZwDRi61 $1$Br0K6Kxd$s1/K8ydTb6X.uvFJFKUHT. $1$KxkU1GsV$fqW.yZRhiF6F40HSBT2V41
根据结果得知,同一个字符串两次哈希值并不同,这是因为 Commons Codec 将默认获取一个随机盐。部分源码如下:
public static String md5Crypt(final byte[] keyBytes) { return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8)); }
其中,MD5_PREFIX 等于 “$1$”,B64.getRandomSalt() 方法代码如下:
static String getRandomSalt(final int num) { return getRandomSalt(num, new SecureRandom()); }
生成与 Libc crypt()兼容的“$1$”基于 MD5 的哈希值。实例如下:
import org.apache.commons.codec.digest.Md5Crypt; public class Md5CryptDemo3 { public static void main(String[] args) { String str = "hello world"; // 盐的格式为 “$**$****” System.out.println(Md5Crypt.md5Crypt(str.getBytes(), "$1$0123456789")); // 下面将抛出如下错误: // java.lang.IllegalArgumentException: Invalid salt value: 0123456789 System.out.println(Md5Crypt.md5Crypt(str.getBytes(), "0123456789")); } }
输出结果:
$1$01234567$hrZxh5BCBsbRPc89Bq0By1 Exception in thread "main" java.lang.IllegalArgumentException: Invalid salt value: 0123456789 at org.apache.commons.codec.digest.Md5Crypt.md5Crypt(Md5Crypt.java:293) at org.apache.commons.codec.digest.Md5Crypt.md5Crypt(Md5Crypt.java:255) at org.apache.commons.codec.digest.Md5Crypt.md5Crypt(Md5Crypt.java:230) at com.huangx.codec.md5crypt.Md5CryptDemo3.main(Md5CryptDemo3.java:15)
生成 Libc6 crypt()“$1$”或 Apache htpasswd“$apr1$”哈希值。实例:
import org.apache.commons.codec.digest.Md5Crypt; public class Md5CryptDemo4 { public static void main(String[] args) { String str = "hello world"; // 盐的格式为 “$**$****” System.out.println(Md5Crypt.md5Crypt(str.getBytes(), "$1$0123456789", "$1$")); // 下面将抛出如下错误: // java.lang.IllegalArgumentException: Invalid salt value: 0123456789 System.out.println(Md5Crypt.md5Crypt(str.getBytes(), "0123456789", "")); } }
输出结果:
$1$01234567$hrZxh5BCBsbRPc89Bq0By1 01234567$jDoD1axfdogcQgMnJ09Qv.
生成 Libc6 crypt()“$1$”或 Apache htpasswd“$apr1$”哈希值。实例:
import org.apache.commons.codec.digest.Md5Crypt; import java.security.SecureRandom; import java.util.concurrent.ThreadLocalRandom; public class Md5CryptDemo5 { public static void main(String[] args) { String str = "hello world"; System.out.println(Md5Crypt.md5Crypt(str.getBytes(), null, "", new SecureRandom())); System.out.println(Md5Crypt.md5Crypt(str.getBytes(), null, "", ThreadLocalRandom.current())); } }
输出结果:
wdrGDxki$PF0EcgoTQVViDrS5xmLlF0 WhnFkCAk$jzPUDFwMuLwCJq691syEi0