1. 什么是OTP
一次性密码(One Time Password,简称OTP),又称“一次性口令”,是指只能使用一次的密码。
1
2. OTP原理
动态密码的产生方式,主要是以时间差做为服务器与密码产生器的同步条件。在需要登录的时候,就利用密码产生器产生动态密码,OTP一般分为计次使用以及计时使用两种,计次使用的OTP产出后,可在不限时间内使用;计时使用的OTP则可设置密码有效时间,从30秒到两分钟不等,而OTP在进行认证之后即废弃不用,下次认证必须使用新的密码,增加了试图不经授权访问有限制资源的难度。
计算公式:
OTP(K,C) = Truncate(HMAC-SHA-1(K,C))
3.Java实现全代码TOTP
该代码可以直接运行
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.util.Date;
public class TOTP {
public static void main(String[] args) {
try {
for (int j = 0; j < 10; j++) {
String totp = generateMyTOTP("account01", "12345");
System.out.println(String.format("加密后: %s", totp));
Thread.sleep(1000);
}
} catch (final Exception e) {
e.printStackTrace();
}
}
/**
* 共享密钥
*/
private static final String SECRET_KEY = "ga35sdia43dhqj6k3f0la";
/**
* 时间步长 单位:毫秒 作为口令变化的时间周期
*/
private static final long STEP = 5000;
/**
* 转码位数 [1-8]
*/
private static final int CODE_DIGITS = 8;
/**
* 初始化时间
*/
private static final long INITIAL_TIME = 0;
/**
* 柔性时间回溯
*/
private static final long FLEXIBILIT_TIME = 5000;
/**
* 数子量级
*/
private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
private TOTP() {
}
/**
* 生成一次性密码
*
* @param code 账户
* @param pass 密码
* @return String
*/
public static String generateMyTOTP(String code, String pass) {
// if (EmptyUtil.isEmpty(code) || EmptyUtil.isEmpty(pass)) {
// throw new RuntimeException("账户密码不许为空");
// }
long now = new Date().getTime();
String time = Long.toHexString(timeFactor(now)).toUpperCase();
return generateTOTP(code + pass + SECRET_KEY, time);
}
/**
* 刚性口令验证
*
* @param code 账户
* @param pass 密码
* @param totp 待验证的口令
* @return boolean
*/
public static boolean verifyTOTPRigidity(String code, String pass, String totp) {
return generateMyTOTP(code, pass).equals(totp);
}
/**
* 柔性口令验证
*
* @param code 账户
* @param pass 密码
* @param totp 待验证的口令
* @return boolean
*/
public static boolean verifyTOTPFlexibility(String code, String pass, String totp) {
long now = new Date().getTime();
String time = Long.toHexString(timeFactor(now)).toUpperCase();
String tempTotp = generateTOTP(code + pass + SECRET_KEY, time);
if (tempTotp.equals(totp)) {
return true;
}
String time2 = Long.toHexString(timeFactor(now - FLEXIBILIT_TIME)).toUpperCase();
String tempTotp2 = generateTOTP(code + pass + SECRET_KEY, time2);
return tempTotp2.equals(totp);
}
/**
* 获取动态因子
*
* @param targetTime 指定时间
* @return long
*/
private static long timeFactor(long targetTime) {
return (targetTime - INITIAL_TIME) / STEP;
}
/**
* 哈希加密
*
* @param crypto 加密算法
* @param keyBytes 密钥数组
* @param text 加密内容
* @return byte[]
*/
private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
try {
Mac hmac;
hmac = Mac.getInstance(crypto);
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "AES");
hmac.init(macKey);
return hmac.doFinal(text);
} catch (GeneralSecurityException gse) {
throw new UndeclaredThrowableException(gse);
}
}
private static byte[] hexStr2Bytes(String hex) {
byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
byte[] ret = new byte[bArray.length - 1];
System.arraycopy(bArray, 1, ret, 0, ret.length);
return ret;
}
private static String generateTOTP(String key, String time) {
return generateTOTP(key, time, "HmacSHA1");
}
private static String generateTOTP256(String key, String time) {
return generateTOTP(key, time, "HmacSHA256");
}
private static String generateTOTP512(String key, String time) {
return generateTOTP(key, time, "HmacSHA512");
}
private static String generateTOTP(String key, String time, String crypto) {
StringBuilder timeBuilder = new StringBuilder(time);
while (timeBuilder.length() < 16)
timeBuilder.insert(0, "0");
time = timeBuilder.toString();
byte[] msg = hexStr2Bytes(time);
byte[] k = key.getBytes();
byte[] hash = hmac_sha(crypto, k, msg);
return truncate(hash);
}
/**
* 截断函数
*
* @param target 20字节的字符串
* @return String
*/
private static String truncate(byte[] target) {
StringBuilder result;
int offset = target[target.length - 1] & 0xf;
int binary = ((target[offset] & 0x7f) << 24)
| ((target[offset + 1] & 0xff) << 16)
| ((target[offset + 2] & 0xff) << 8) | (target[offset + 3] & 0xff);
int otp = binary % DIGITS_POWER[CODE_DIGITS];
result = new StringBuilder(Integer.toString(otp));
while (result.length() < CODE_DIGITS) {
result.insert(0, "0");
}
return result.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
该OTP运行后,得到的是可以校验是否有效的口令,但是无法判断这个口令属于哪一个用户,像付款码,当使用人数不断增多时,单纯该数字无法确认哪一个人的付款码,则现在需要在这串数据中,融入用户ID,其计算公式可以:
4、付款码计算与提取
付款码=TOTP * 质数 + 用户ID
质数取值需要大于最大用户ID,如:当前系统设计最大承受用户有9900人,则质数可以设定必须大于9900的数值(9991).
1
2
提起用户ID
用户ID = 付款码 % 质数
“%”表示取余
1
2
提取TOTP
TOTP=付款码 / 质数
当付款码除质数后取整,则该数值为当前付款码中的OTP。
1
2
当服务器获取到付款码时:
1、首先提取TOTP,判断该口令是否有效,如果校验失败,则可以接口返回,付款码错误。
2、第一步校验通过后,查找缓存,判断在规定的一段时间内是否有使用过该付款码,并更新缓存,把该次记录写到缓存中。
3、第一步和第二步通过后,获取付款码中用户ID,将用户ID进行扣款操作。
4、服务器继续走剩下流程。
————————————————
原文链接:https://blog.csdn.net/qq_43655984/java/article/details/105598603
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=112
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=113
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=114
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=115
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=116
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=81
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=82
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=83
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=84
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=85
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=86
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=88
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=89
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=90
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=91
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=92
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=78
https://jlwz.cn/album/albumlist.aspx?siteid=1000&classid=0&smalltypeid=79
什么是OTP:Java一次动态密码、付款码原理
原文:https://www.cnblogs.com/dasdfdfecvcx/p/12763569.html