2021SC@SDUSC
上文主要对该模块openmeetins-util的结构进行梳理,开始源码的分析工作。本篇文章将继续分析openmeetings-util模块里面的类。上文已经将src文件下的其他文件分析完毕,现在主要对src下的main文件展开分析。
目录
main
首先观察main文件下的目录结构:包括子目录下的class文件,数了数总共有25个类,其中包括接口类和类,其中根目录下有12个类,crypt文件下有6个类,mail文件下有3个类,process文件下有3个类,ws文件下有一个接口类。
crypt
crypt文件夹下总共有五个类:分别是ICrypt接口,CryptProvider类、MD5类、MD5Implementation类、SCryptImplementation类以及SHA256Implementation类。
Icrypt接口
public interface ICrypt {
String hash(String str);
boolean verify(String str, String hash);
boolean fallback(String str, String hash);
String randomPassword(int length);
}
该接口里面有三个四个抽象方法,分别是hash()、verify()、fallback()和randomPassword()方法,其中hash()方法主要是实现:创建给定字符串的哈希值,参数为字符串类型->给定要计算哈希值的字符串,然后返回值为传递的字符串散列。
然后verify()方法主要是实现:验证传递的字符串是否匹配给定的哈希字符串,参数为两个字符串,str为要检查哈希的字符串,hash为要比较的哈希字符串,返回值为布尔型变量,如果字符串和哈希字符串符合则返回true,否则返回false。
然后fallback()方法主要是实现:验证传递的字符串是否匹配给定的哈希字符串,使用回退crypt机制,其中参数str为要检查哈希的字符串,hash为要比较的哈希字符串,返回值为布尔型变量,如果字符串和哈希字符串符合则返回true,否则返回false。
最后randomPassword()方法主要实现:对传递的参数进行编码,返回一个字符串类型。
SCryptImplementation类
代码:
public class SCryptImplementation implements ICrypt {
private static final Logger log = Red5LoggerFactory.getLogger(SCryptImplementation.class, getWebAppRootKey());
private static final ThreadLocal<SecureRandom> rnd = new ThreadLocal<SecureRandom>() {
@Override
protected SecureRandom initialValue() {
SecureRandom sr;
try {
sr = SecureRandom.getInstance(SECURE_RND_ALG);
} catch (NoSuchAlgorithmException e) {
log.error("Failed to get instance of SecureRandom {}", SECURE_RND_ALG);
sr = new SecureRandom();
}
return sr;
}
};
private static final String SECURE_RND_ALG = "SHA1PRNG";
private static final int COST = 1024 * 16;
private static final int KEY_LENGTH = 512;
private static final int SALT_LENGTH = 200;
private static byte[] getSalt(int length) {
byte[] salt = new byte[length];
rnd.get().nextBytes(salt);
return salt;
}
private static String hash(String str, byte[] salt) {
byte[] dk = SCrypt.generate(str.getBytes(UTF_8), salt, COST, 8, 8, KEY_LENGTH);
return Base64.encodeBase64String(dk);
}
@Override
public String hash(String str) {
if (str == null) {
return null;
}
byte[] salt = getSalt(SALT_LENGTH);
String h = hash(str, salt);
return String.format("%s:%s", h, Base64.encodeBase64String(salt));
}
@Override
public boolean verify(String str, String hash) {
if (str == null) {
return hash == null;
}
if (hash == null) {
return false;
}
String[] ss = hash.split(":");
if (ss.length != 2) {
return false;
}
try {
String h1 = ss[0];
byte[] salt = Base64.decodeBase64(ss[1]);
String h2 = hash(str, salt);
return h2.equals(h1);
} catch (Exception e) {
return false;
}
}
@Override
public boolean fallback(String str, String hash) {
if (SHA256Implementation.verify(str, hash)) {
return true;
}
if (MD5Implementation.verify(str, hash)) {
return true;
}
return false;
}
@Override
public String randomPassword(int length) {
return Base64.encodeBase64String(getSalt(length));
}
}
SCryptImplementation类实现了ICrypt接口类,并实现了其中的抽象方法。首先定义了一个Logger日志对象,方便调试的时候观察和检查错误。然后定义了一个ThreadLocal类对象,什么是ThreadLocal?
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。
private static final ThreadLocal<SecureRandom> rnd = new ThreadLocal<SecureRandom>() {
@Override
protected SecureRandom initialValue() {
SecureRandom sr;
try {
sr = SecureRandom.getInstance(SECURE_RND_ALG);
} catch (NoSuchAlgorithmException e) {
log.error("Failed to get instance of SecureRandom {}", SECURE_RND_ALG);
sr = new SecureRandom();
}
return sr;
}
};
这段代码里面还有一个重要的SecureRandom类,SecureRandom类提供加密的强随机数生成器。SecureRandom和Random都是,也是如果种子一样,产生的随机数也一样,因为种子确定,随机数算法也确定,因此输出是确定的。SecureRandom类收集了一些随机事件,比如鼠标点击,键盘点击等等,SecureRandom 使用这些随机事件作为种子。这意味着种子是不可预测的,而不像Random默认使用系统当前时间的毫秒数作为种子,有规律可寻。
即这里的ThreadLocal类变量rnd为SecureRandom类调用getInstance(参数为常量SECURE_RND_ALG)产生的随机数。
然后getSalt()方法,首先定义一个byte数组salt,然后根据rnd调用get方法获取在当前线程中保存的变量,然后接着调用nextBytes方法将获取的变量填充到salt数据里面,再返回salt数组变量。
后来的hash(String str, byte[] salt)方法是利用Scrypt算法将输入的字符串转换为哈希值,然后对其进行Base64编码并返回该编码值。
(The scrypt algorithm generates a very large pseudo-random number sequence. This random number sequence will be used in the subsequent key generation process, so generally a RAM is required for storage. This is why the scrypt algorithm requires large memory.)
之后的hash方法,是对接口里面的抽象方法的实现,接受传进来的参数,获取salt值,并获取该字符串生成的哈希字符串,并将字符串和哈希字符串一起返回。
后面的verify(String str, String hash)方法同样是对接口类里面的抽象方法的实现,即判断传入的字符串str与传入的哈希字符串值是否相等,逻辑很简单,利用equals语句。
后面的fallback(String str, String hash)方法是利用if语句分别调用SHA256Implementation类和MD5Implementation类的verify()方法,同样是判断。
最后的randomPassword(int length)方法返回Base64编码后的字符串,参数为rnd保存的变量的salt_length。
SHA256Implementation类
class SHA256Implementation {
private static final int KEY_LENGTH = 128 * 8;
private SHA256Implementation() {}
private static String hash(String str, byte[] salt, int iter) {
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest());
gen.init(str.getBytes(StandardCharsets.UTF_8), salt, iter);
byte[] dk = ((KeyParameter) gen.generateDerivedParameters(KEY_LENGTH)).getKey();
return Base64.encodeBase64String(dk);
}
static boolean verify(String str, String hash) {
if (str == null) {
return hash == null;
}
if (hash == null) {
return false;
}
String[] ss = hash.split(":");
if (ss.length != 3) {
return false;
}
try {
int iter = Integer.parseInt(ss[0]);
String h1 = ss[1];
byte[] salt = Base64.decodeBase64(ss[2]);
String h2 = hash(str, salt, iter);
return h2.equals(h1);
} catch (Exception e) {
return false;
}
}
}
该类里面有两个方法,hash和verify方法,功能与SCryptImplementation类里面实现的功能相似,封装私有的SHA256实现可以进行身份验证。不同的是hash方法里面使用了PKCS5S2ParametersGenerator类,查阅该类的官方文档:Generate a key parameter derived from the password, salt, and iteration count we are currently initialised with。从我们当前初始化时使用的密码、salt和迭代计数派生一个密钥参数。具体里面的代码与前面相似,不再一一赘述。
MD5类
public class MD5 {
private MD5() {}
public static String checksum(String data) throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] b = data == null ? new byte[0] : data.getBytes(UTF_8);
md5.update(b, 0, b.length);
return Hex.encodeHexString(md5.digest());
}
}
MD5类里面只有一个方法,引入包import java.security.MessageDigest; MessageDigest 类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。 首先调用getInstance方法返回指定摘要算法的MessageDigest对象,这里指定的的算法为MD5。然后调用update方法使用指定的数据组更新摘要,最后调用digest方法所重置的摘要利用Hex.encodeHexString转换为16进制字符串打包返回。
补充MD5算法:
MD5功能:
输入任意长度的信息,经过处理,输出为128位的信息(数字指纹);
不同的输入得到的不同的结果(唯一性);
根据128位的输出结果不可能反推出输入的信息(不可逆);
MD5用途:防止被篡改,防止直接看到铭文,防止抵赖等。
MD5Implementation类
class MD5Implementation {
private static final Logger log = Red5LoggerFactory.getLogger(MD5Implementation.class, getWebAppRootKey());
private MD5Implementation() {}
private static String hash(String str) {
String passPhrase = null;
try {
passPhrase = MD5.checksum(str);
} catch (NoSuchAlgorithmException e) {
log.error("Error", e);
}
return passPhrase;
}
static boolean verify(String str, String hash) {
return hash != null && hash.equals(hash(str));
}
}
MD5Implementation类里面同样是实现了两个方法:hash和verify方法,hash方法里面,形成哈希字符串的方法为MD5调用checksum方法并返回。
CryptProvider类
public class CryptProvider {
private static final Logger log = Red5LoggerFactory.getLogger(CryptProvider.class, getWebAppRootKey());
private static volatile ICrypt crypt;
private CryptProvider() {}
public static ICrypt get() {
if (crypt == null) {
synchronized (CryptProvider.class) {
if (crypt == null) {
String clazz = getCryptClassName();
try {
log.debug("getInstanceOfCrypt:: configKeyCryptClassName: {}", clazz);
crypt = clazz == null ? null : (ICrypt) Class.forName(clazz).newInstance();
} catch (Exception err) {
log.error("[getInstanceOfCrypt]", err);
}
}
}
}
return crypt;
}
public static synchronized void reset() {
crypt = null;
}
}
最后我们来看CryptProvider类,定义了Logger日志对象方便记录,然后定义了ICrypt对象crypt,里面的get方法即获取该类里面的crypt对象。然后reset方法即重置crypt,将其设为null值,该类就实现提供ICrypt类对象的作用。
总结
这篇文章主要对crypt文件里面的六个类进行了分析,该文件的主要功能即实现对字符串的哈希编码形成哈希字符串返回,并用来实现加密功能。之后将继续分析util文件下的其他类。