概述
密码相似度一般运用在大数据的风险控制领域,当用户登陆是,把当前输入密码与用户历史密码进行做相似度计算,由于相同用户对于自己的密码管理也基本都是比较相似的,相似度过低就被认定当前登录操作有风险。
设计思路
做密码相似度计算是一个抽象的过程,传统的java代码无法处理这种抽象的计算。只有通过数学建模,建立一个比较两对象的相似度模型,把比较的两者密码换算成两个向量,把历史密码中的出现的字符作为向量特征,输入密码字符的个数作为特征的参数,利用两个向量的余弦值来比较相似性。
余弦相似度
余弦相似度,又称为余弦相似性,是通过计算两个向量的夹角余弦值来评估他们的相似度。余弦相似度将向量根据坐标值,绘制到向量空间中,如最常见的二维空间。
余弦相似性通过测量两个向量的夹角的余弦值来度量它们之间的相似性。0度角的余弦值是1,而其他任何角度的余弦值都不大于1;并且其最小值是-1。从而两个向量之间的角度的余弦值确定两个向量是否大致指向相同的方向。两个向量有相同的指向时,余弦相似度的值为1;两个向量夹角为90°时,余弦相似度的值为0;两个向量指向完全相反的方向时,余弦相似度的值为-1。这结果是与向量的长度无关的,仅仅与向量的指向方向相关。余弦相似度通常用于正空间,因此给出的值为-1到1之间。
注意这上下界对任何维度的向量空间中都适用,而且余弦相似性最常用于高维正空间。例如在信息检索中,每个词项被赋予不同的维度,而一个维度由一个向量表示,其各个维度上的值对应于该词项在文档中出现的频率。余弦相似度因此可以给出两篇文档在其主题方面的相似度。
另外,它通常用于文本挖掘中的文件比较。此外,在数据挖掘领域中,会用到它来度量集群内部的凝聚力。
公式如下:
下面是java代码实现:
**
* 用户登陆的密码相似度评估
*/
public class SimilarityEvaluate{
//相似度阈值,相似度大于该阈值,认定没有风险,反之亦然
private Double thresholdSimilarity;
//构造
public SimilarityEvaluate(Double thresholdSimilarity){
this.thresholdSimilarity = thresholdSimilarity;
}
/**
* 逻辑执行,
* @param loginPassword
* @param historyPassword
* @return 返回true,代表输入密码差异过大,登陆有风险
*/
public Boolean doEval(String loginPassword, Set<String> historyPassword){
//如果用户是第一次登陆
if(historyPassword == null || historyPassword.size() == 0){
return false;
}
//获取字符袋
Set<Character> wordBag = new HashSet<>();
historyPassword.stream()
.forEach(c->{
char[] chars = c.toCharArray();
for (char aChar : chars) {
wordBag.add(aChar);//加入到词袋中
}
});
//富华维度,可以把当前输入的字符页添加到字符袋中
char[] chars = loginPassword.toCharArray();
for (Character aChar : chars) {
wordBag.add(aChar);
}
//把字符袋中的字符排序
List<Character> wordList = wordBag.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("字符袋:" + wordList);
//把历史密码转化为向量
List<Integer[]> historyVector = historyPassword.stream()
.map(p -> transformStringToVector(wordList, p))
.collect(Collectors.toList());
//把当前密码转化为向量
Integer[] loginVector = transformStringToVector(wordList, loginPassword);
//获取没有风险的相似度集合
List<Double> similarities = historyVector.stream()
.map(h -> {
Double similarity = calculateSimilarity(h, loginVector);
System.out.println("相似度:" + similarity);
return similarity;
})
.filter(s -> s > thresholdSimilarity)//过滤出有风险的相似度
.collect(Collectors.toList());
return similarities.size() == 0;
}
/**
* 把密码乱序转化为向量
* @param wordBag 所有历史密码组成的排序好的字符袋
* @param password 要转化的密码乱序
* @return 转化后的向量
*/
public Integer[] transformStringToVector(List<Character> wordBag,String password){
//转化后的向量
Integer[] vector = new Integer[wordBag.size()];
//把密码乱序转化为map集合(字符,个数)
Map<Character,Integer> passCharMap = new HashMap<>();
char[] chars = password.toCharArray();
//遍历字符数组
for (Character c : chars) {
//初始数量
Integer count = 1;
//如果map中有该字符
if (passCharMap.containsKey(c)){
count += passCharMap.get(c);
}
//放入到map中
passCharMap.put(c,count);
}
//遍历字符袋,如果密码map中有该字符,向量该字符特征的属性就+1,
for (int i = 0; i < wordBag.size(); i++) {
Character c = wordBag.get(i);
vector[i] = passCharMap.containsKey(c) ? passCharMap.get(c) : 0;
}
return vector;
}
/**
* 计算两个向量的相似度
* @param v1 v1向量
* @param v2 v2向量
* @return 返回相似度
*/
public Double calculateSimilarity(Integer[] v1,Integer[] v2){
//计算两个向量的夹角余弦,向量点乘/模的乘积
//向量的点乘:v1 * v2
Double dotMulti = 0.0;
for (int i = 0; i < v1.length; i++) {
dotMulti += v1[i] * v2[i];
}
//计算两个向量的模的平方
Integer length1 = Arrays.stream(v1)
.map(v -> v * v)
.reduce((i1, i2) -> i1 + i2)
.get();
Integer length2 = Arrays.stream(v2)
.map(v -> v * v)
.reduce((i1, i2) -> i1 + i2)
.get();
//计算余弦
return dotMulti / (Math.sqrt(length1) * Math.sqrt(length2));
}
public static void main(String[] args) {
//乱序密码
/*String pwd="123456abc";
char[] chars = pwd.toCharArray();
List<Character> characters=new ArrayList<>();
for (char aChar : chars) {
characters.add(aChar);
}
String ordernessPassword = characters.stream()
.sorted((o1, o2)-> new Integer[]{-1, 0, 1}[new Random().nextInt(3)])
.map(c -> c + "")
.reduce((v1, v2) -> v1 + v2)
.get();
System.out.println(ordernessPassword);*/
//测试密码相似度
SimilarityEvaluate similarityEvaluate = new SimilarityEvaluate(0.98);
HashSet<String> pwds = new HashSet<>();
pwds.add("456vcfg");
pwds.add("ct12345");
pwds.add("hjk8901");
System.out.println(similarityEvaluate.doEval("vcfg451",pwds));
}
}