动态码算法
两次加密一次混淆,破解难度高,在加入时间参数和随机位的情况下,只会增加 5 个长度位
算法定义
TOTP = SHUFFLE(M, skey)
M = MIX(S)
S = SHUFFLE(C, skey)
C = CONCAT(K, T)
K = targetCode
T = (currentUnixTime - epochUnixTime) / stepLen
模型设计
相关函数
- CONCAT 拼接函数,将静态码和动态码进行拼接
- MIX 混淆函数,将静态码按随机数动态变化,但是能够进行反解析
- SHUFFLE 对称加密函数,安全的根本,只要密钥不丢失,外界无法破解
相关参数
- currentUnixTime 当前系统的 Unix 时间
- epochUnixTime 双方约定的时间原点,用来计算时间差值,有利于降低动态位的长度,也算是一层简单的加密
- stepLen 时间步长,单位为秒,这个值越大的容错度就越高,相反越小超安全
- skey 双方约定的密钥,在默认情况下由平台帮忙生成一个指定字符集的密码本,双方必须保证一致,这是安全的根本,否由无法正确的加解密
BASE62 算法应用:四则运算、压缩
-
码长度最大可以压缩一半的空间
比如说数字 141802 采用 BASE62 算法转换后的值为 at8,节省了一半的空间
-
62 进制奇偶加减运算将动态码变的更加分散
算法执行结果样本
我们以原码『ABC123』为例,时间步长为 10 秒,动态码前缀为「D」,生成 8 组的结果如下
DZCkKF5qCh3c
D2ch0xHBcwsy
D8cu07HTc3sF
Dpcd02HIc6sZ
D3mknF9qmhfc
DIjkZFhqjhUb
Dic70lHScksQ
DXzkNFjqzhSb
从结果可以看出来按这种算法,生成出来的结果是完全随机加密的,安全性较高。
使用 DEMO
@Test
public void testTotpTable() throws ParseException, InterruptedException {
for (int i = 0; i < 128; i++) {
TOTP totp = new TOTP();
totp.setStepLen(10);
//totp.setPrefix("D");
String marshal = totp.marshal("ABC123");
System.out.println("i = " + i + ", secret = " + marshal + ", "
+ totp.unmarshal(marshal) + ", validate = " + totp.validate(marshal));
TimeUnit.SECONDS.sleep(1);
}
}
代码
BASE62 混淆算法
/**
* Created by yunchow.zy on 18/1/18.
*/
public class SimpleMixFunction implements MixFunction {
@Override
public String mix(String source) {
Objects.nonNull(source);
final Character mixFactor62 = BASE62.singleRandom();
TOTPDebug.info("mixFactor62 = " + mixFactor62);
final int mixFactor10 = BASE62.parseInt("" + mixFactor62);
StringBuilder sb = new StringBuilder(source);
for (int i = mixFactor10 % 2; i < sb.length(); i = i + 2) {
char c = sb.charAt(i);
sb.setCharAt(i, BASE62.singleAdd(c, mixFactor62, c));
}
return mixFactor62 + sb.toString();
}
@Override
public String unmix(String target) {
if (target == null || target.length() < 2) {
throw new IllegalArgumentException("target length illegal");
}
StringBuilder sb = new StringBuilder(target);
final Character mixFactor62 = sb.charAt(0);
final int mixFactor10 = BASE62.parseInt("" + mixFactor62);
sb.deleteCharAt(0);
for (int i = mixFactor10 % 2; i < sb.length(); i = i + 2) {
char c = sb.charAt(i);
sb.setCharAt(i, BASE62.singleSubtract(c, mixFactor62, c));
}
return sb.toString();
}
}
密码表加密算法
/**
* Created by yunchow.zy on 18/1/17.
*/
public final class SecretTable {
// ==================================================================//
// ================ 这里的参数请根据自己的情况进行修改 ====================//
/**
* 每个客户端的密码表不同,请注意保密
*/
private String PASSWORD_TABLE_STRING = "Ef7cRHupZLQaF5BGCjKemSDWx0lkM2OPvI6Yishnbgy3t1w9Jq8rTd4UVXANoz";
// ======================= 结束 ==========================//
// ==================================================================//
private static final String SOURCE_ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final Integer PASSWORD_TABLE_LEN = SOURCE_ALPHABETS.length();
private static final Map<Character, Integer> SOURCE_TO_TARGET_INDEX_MAPPING = new HashMap<>(PASSWORD_TABLE_LEN);
private static final Map<Character, Integer> TARGET_TO_SOURCE_INDEX_MAPPING = new HashMap<>(PASSWORD_TABLE_LEN);
private static final Character[] PASSWORD_INDEX = new Character[PASSWORD_TABLE_LEN];
private static final Character[] BASIC_SORT_INDEX = new Character[PASSWORD_TABLE_LEN];
public SecretTable () {
this(null);
}
public SecretTable (String skey) {
if (skey != null) {
PASSWORD_TABLE_STRING = skey;
}
if (PASSWORD_TABLE_STRING.length() != PASSWORD_TABLE_LEN) {
throw new IllegalArgumentException("PASSWORD_TABLE_STRING length must be " + PASSWORD_TABLE_LEN);
}
char[] chars = PASSWORD_TABLE_STRING.toCharArray();
for (int i = 0; i < chars.length; i++) {
SOURCE_TO_TARGET_INDEX_MAPPING.put(chars[i], i);
PASSWORD_INDEX[i] = chars[i];
}
chars = SOURCE_ALPHABETS.toCharArray();
for (int i = 0; i < chars.length; i++) {
BASIC_SORT_INDEX[i] = chars[i];
TARGET_TO_SOURCE_INDEX_MAPPING.put(chars[i], i);
}
}
/**
* 加密
* @param source
* @return
*/
public String encode(String source) {
if (source == null) {
return source;
}
List<String> list = new ArrayList<>();
char[] chars = source.toCharArray();
for (Character aChar : chars) {
Integer index = SOURCE_TO_TARGET_INDEX_MAPPING.get(aChar);
if (index != null) {
Character targetChar = BASIC_SORT_INDEX[index];
list.add(String.valueOf(targetChar));
} else {
list.add(String.valueOf(aChar));
}
}
return list.stream().collect(Collectors.joining());
}
/**
* 解密
* @param target
* @return
*/
public String decode(String target) {
if (target == null) {
return target;
}
List<String> list = new ArrayList<>();
char[] chars = target.toCharArray();
for (Character aChar : chars) {
Integer index = TARGET_TO_SOURCE_INDEX_MAPPING.get(aChar);
if (index != null) {
Character targetChar = PASSWORD_INDEX[index];
list.add(String.valueOf(targetChar));
} else {
list.add(String.valueOf(aChar));
}
}
return list.stream().collect(Collectors.joining());
}
public static void main(String[] args) {
SecretTable st = new SecretTable();
System.out.println("1->" + st.encode("ABCDEF123=阿里巴巴@="));
System.out.println("2->" + st.encode(st.encode("ABCDEF123")));
System.out.println("3->" + st.encode(st.encode(st.encode("ABCDEF123"))));
System.out.println("4->" + st.encode(st.encode(st.encode(st.encode("ABCDEF123")))));
System.out.println("5->" + st.encode(st.encode(st.encode(st.encode(st.encode("ABCDEF123"))))));
System.out.println("1->" + st.decode("wEGM0CjTh+阿里&"));
System.out.println("2->" + st.decode(st.decode("k0FSPGHqc")));
System.out.println("3->" + st.decode(st.decode(st.decode("RPCLVF5n3"))));
System.out.println("4->" + st.decode(st.decode(st.decode(st.decode("4VG9uCDdh")))));
System.out.println("5->" + st.decode(st.decode(st.decode(st.decode(st.decode("suFl6GMrc"))))));
}
/**
* 随机生成一个新的密码表
* @return
*/
public static String makePasswordTable() {
char[] chars = SOURCE_ALPHABETS.toCharArray();
List<String> list = new ArrayList<>();
for (char aChar : chars) {
list.add(String.valueOf(aChar));
}
Collections.shuffle(list);
return list.stream().collect(Collectors.joining());
}
}
BASE62 算法
/**
* Created by yunchow.zy on 18/1/17.
*/
public final class BASE62 {
private static char[] BASE_TABLE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
private static final Integer SCALE = BASE_TABLE.length;
private static final Map<Character, Integer> INDEX_MAP = new HashMap<Character, Integer>() {
private static final long serialVersionUID = 3807502752985392746L;
{
for (int i = 0; i < BASE_TABLE.length; i++) {
put(BASE_TABLE[i], i);
}
}
};
/**
* 62进制相减运算
* @param c1
* @param c2
* @param defaultC 计算失败返回的值
* @return
*/
public static Character singleSubtract(Character c1, Character c2, Character defaultC) {
Integer i1 = INDEX_MAP.get(c1);
Integer i2 = INDEX_MAP.get(c2);
if (i1 == null || i2 == null) {
return defaultC;
}
i1 = i1 < i2 ? i1 += SCALE : i1;
int k = i1 - i2;
if (SCALE <= k || k < 0) {
return defaultC;
}
return BASE_TABLE[k];
}
/**
* 62进制相加运算
* @param c1
* @param c2
* @param defaultC 计算失败返回的值
* @return
*/
public static Character singleAdd(Character c1, Character c2, Character defaultC) {
Integer i1 = INDEX_MAP.get(c1);
Integer i2 = INDEX_MAP.get(c2);
if (i1 == null || i2 == null) {
return defaultC;
}
int k = i1 + i2;
k = k >= SCALE ? k - SCALE : k;
if (SCALE <= k || k < 0) {
return defaultC;
}
return BASE_TABLE[k];
}
/**
* 随机得到一个 【1 ~ 61】 之间的一个数,如果得到的是0,重新再计算一遍
* @return
*/
public static Character singleRandom() {
int index = new SecureRandom().nextInt(SCALE);
if (index == 0) {
return singleRandom();
}
return BASE_TABLE[index];
}
/**
* 将10进制的数转换成62进制数
* @param num
* @return
*/
public static String valueOf(long num) {
StringBuilder sb = new StringBuilder();
long remainder = 0;
do {
remainder = num % SCALE;
sb.append(BASE_TABLE[(int) remainder]);
num = num / SCALE;
}
while (num > SCALE - 1);
sb.append(BASE_TABLE[(int) num]);
List<String> list = new ArrayList<>();
for (int i = 0; i < sb.length(); i++) {
list.add("" + sb.charAt(i));
}
Collections.reverse(list);
return list.stream().collect(Collectors.joining());
}
public static int parseInt(String target) {
return (int) parseLong(target);
}
/**
* 将62进制数转换为10进制数
* @param target
* @return
*/
public static long parseLong(String target) {
long num = 0;
int index = 0;
for(int i = 0; i < target.length(); i++) {
index = INDEX_MAP.get(target.charAt(i));
num += (long)(index * (Math.pow(SCALE, target.length() - i - 1)));
}
return num;
}
}
TOTP
package com.totpdemo.test.totp;
/**
*
* TOTP 算法定义如下
*
* TOTP = SHUFFLE(MIX(SHUFFLE(CONCAT(K, T), skey)), skey)
*
* <blockquote><pre>
* TOTP = SHUFFLE(M, skey)
* M = MIX(S)
* S = SHUFFLE(C, skey)
* C = CONCAT(K, T)
* K = targetCode
* T = (currentUnixTime - epochUnixTime) / stepLen
* </pre></blockquote>
*
* 一个最基本的用法示例如下,其他用法请参考方法注释
*
* <blockquote><pre>
* TOTP totp = new TOTP();
* totp.setStepLen(180);
* totp.setPrefix("D");
* totp.marshal("ABC123abc321");
* totp.unmarshal("DShkBFDqhEPhhqDhpr");
* </pre></blockquote>
*
* 例如静态码 ABC123 的时间动态码的输出如下,时间步长为 10 秒
*
* <blockquote><pre>
* DZCkKF5qCh3c
* D2ch0xHBcwsy
* D8cu07HTc3sF
* Dpcd02HIc6sZ
* D3mknF9qmhfc
* DIjkZFhqjhUb
* Dic70lHScksQ
* DXzkNFjqzhSb
* </pre></blockquote>
*
* Created by yunchow.zy on 18/1/16.
*/
public final class TOTP {
/**
* 默认时间起点为:2018-01-01 08:00:00
*/
public static final long DEFAULT_EPOCH_TIME = 1514764800;
/**
* 当前时间
*/
private long currentUnixTime = System.currentTimeMillis() / 1000;
/**
* 时间起点
*/
private long epochUnixTime = DEFAULT_EPOCH_TIME;
/**
* 秒单位步长
*/
private int stepLen = 120;
/**
* 密钥
*/
private String skey = "Ef7cRHupZLQaF5BGCjKemSDWx0lkM2OPvI6Yishnbgy3t1w9Jq8rTd4UVXANoz";
/**
* 容错窗口大小
*/
private Integer faultWindowSize = 1;
/**
* 前缀
*/
private String prefix = "";
/**
* 拼接函数,客户端可以实现自已的函数
*/
private ConcatFunction concatFunction = new SimpleConcatFunction();
/**
* 加密函数,支持客户端自定义扩展
*/
private Shuffle shuffle = new SecretTableShuffle();
/**
* 混淆函数,将原有的静态码随机变换,显的没有规律
*/
private MixFunction mixFunction = new SimpleMixFunction();
/**
* 根据指定静态码得到一个 TOTP 动态编码
* @param source 静态源码
* @return
*/
public String marshal(String source) {
TOTPDebug.info("currentUnixTime = " + currentUnixTime);
TOTPDebug.info("epochUnixTime = " + epochUnixTime);
TOTPDebug.info("stepLen = " + stepLen);
DynamicCode dynamicCode = new DynamicCode();
dynamicCode.setStaticPart(source);
dynamicCode.setDynamicPart("" + currentDynamicTime());
String join = concatFunction.join(dynamicCode);
TOTPDebug.info("concatFunctionJoin = " + join);
String enshuff = shuffle.enshuff(join, skey); // 一次加密
TOTPDebug.info("enshuffFirst = " + enshuff);
String mix = mixFunction.mix(enshuff);
TOTPDebug.info("mix = " + mix);
enshuff = shuffle.enshuff(mix, skey); // 二次加密
TOTPDebug.info("enshuffSecond = " + enshuff);
return prefix + enshuff;
}
/**
* 当前动态时间
* @return
*/
public Long currentDynamicTime() {
Long currentDynamicTime = (currentUnixTime - epochUnixTime) / stepLen;
TOTPDebug.info("currentDynamicTime = " + currentDynamicTime);
return currentDynamicTime;
}
/**
* 验证指定的动态码是否有效
* @param target 动态码
* @return
*/
public boolean validate(String target) {
DynamicCode unmarshal = unmarshal(target);
return validate(unmarshal);
}
/**
* 验证指定的动态码是否有效
* @param dynamicCode 动态码对象
* @return
*/
public boolean validate(DynamicCode dynamicCode) {
Integer dynamicPart = Integer.valueOf(dynamicCode.getDynamicPart());
return validate(dynamicPart);
}
/**
* 验证指定的动态码是否有效
* @param dynamicPart 从动态码里面解析出来的时间串
* @return
*/
public boolean validate(Integer dynamicPart) {
return Math.abs(currentDynamicTime() - dynamicPart) <= faultWindowSize;
}
/**
* 解析一个已被加密的动态TOTP码
* @param target
* @param concatFunction
* @param shuffle
* @return
*/
public DynamicCode unmarshal(String target) {
if (target == null) {
return null;
}
if (prefix != null && prefix.length() > 0 && target.startsWith(prefix)) {
target = target.substring(prefix.length());
}
String deshuff = shuffle.deshuff(target, skey); // 一次解密
String unmix = mixFunction.unmix(deshuff);
deshuff = shuffle.deshuff(unmix, skey); // 二次解密
DynamicCode dynamicCode = concatFunction.split(deshuff);
return dynamicCode;
}
}