邀请码实现方案

实现方案

需求

前提条件:
1、id是一个整数
2、code码是整数或大写字母

实现需求
1、唯一性(必须保证)
2、id与code能相互解析转换
3、code长度适中 6-10 位
4、生成的code需要一定程度的保密(规律不能太明显,轻易解析出id来)

实现逻辑

代码实现(1-3)

public class ShareCodeUtils {

}

如何保密

密码学当中,混淆(confusion)与扩散(diffusion)是设计密码学算法的两种主要方法。这样的定义最早出现在克劳德·香农1945年的论文《密码学的数学理论》当中[1]

在克劳德·香农的定义之中,混淆主要是用来使密文对称式加密方法中密钥的关系变得尽可能的复杂;而扩散则主要是用来使用明文和密文的关系变得尽可能的复杂,明文中任何一点小更动都会使得密文有很大的差异。

那么可以应用混淆与扩散使得id与code的关系更加复杂一点

1、扩散(Diffusion)

  • 扩大id及加固定盐值的方式,将其影响传播到生成的 b 数组中的每个元素。
int pid = id * PRIME1 + SALT; // 扩大 + 盐值
  • 添加校验位,计算每个元素 b[i] 的累加,既能校验加密的正确性,还能进一步增强了扩散的效果。
b[CODE_LENGTH - 1] = (b[CODE_LENGTH - 1] * PRIME1) % CODE_LENGTH;

2、混淆(Confusion)

  • b[i] 的生成过程中,在当前元素的基础上添加一定的偏移。
b[i] = (b[i] + i * b[0]) % BASE_NUMBER;
  • 在将 b 数组的元素转换为最终的输出字符时,使用 洗牌 的方式进一步的混淆。
for (int i = 0; i < CODE_LENGTH; i++) {
	res.append(CHARS.charAt(b[(i * PRIME2) % CODE_LENGTH])); // 洗牌
}

最终算法实现

public class ShareCodeUtils {
	
    /**
     * code码备选字符串
     */
    private static final String CHARS = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
    
    /**
     * 生成的code码长度
     * 原始映射空间 0 - BASE_NUMBER的CODE_LENGTH - 1
     * eg:32进制6位 = 1,073,741,824 - 1 = 1,073,741,823
     * <p>
     * 但考虑到混淆与扩散,值空间会比这个小
     */
    private static final int CODE_LENGTH = 6;
    
    private static final int SALT = 233;
    /**
     * 两个互质的数, ( id * m ) % p
     * 用来放大
     */
    private static final int PRIME1 = 7;
    private static final int PRIME2 = 5;
    
    private static final int BASE_NUMBER = CHARS.length();
    private static final int MAX_VALUE = ((int) Math.pow(32, 6) - 1) / PRIME1;
    
    //public static void main(String[] args) {
    //    String str = encode(38);
    //    System.out.println(decode(str));
    //}
    
    public static String encode(int id) {
        int[] b = new int[CODE_LENGTH];
        StringBuilder res = new StringBuilder();
    
        int pid = id * PRIME1 + SALT; // 扩大
        b[0] = pid;
        for (int i = 0; i < CODE_LENGTH - 1; i++) {
            b[i + 1] = b[i] / BASE_NUMBER;
            b[i] = (b[i] + i * b[0]) % BASE_NUMBER;
        }
    
        // 校验位
        for (int i = 0; i < CODE_LENGTH - 1; i++) {
            b[CODE_LENGTH - 1] += b[i];
        }
        b[CODE_LENGTH - 1] = (b[CODE_LENGTH - 1] * PRIME1) % CODE_LENGTH;
    
        for (int i = 0; i < CODE_LENGTH; i++) {
            res.append(CHARS.charAt(b[(i * PRIME2) % CODE_LENGTH])); // 洗牌
        }
    
        return res.toString();
    }
    
    public static int decode(String code) {
        if (code.length() != CODE_LENGTH) {
            return -1;
        }
        int[] b = new int[CODE_LENGTH];
    
        // 反洗牌
        for (int i = 0; i < CODE_LENGTH; i++) {
            b[(i * PRIME2) % CODE_LENGTH] = i;
        }
    
        // 转换回 CHARS 下标
        for (int i = 0; i < CODE_LENGTH; i++) {
            int j = CHARS.indexOf(code.charAt(b[i]));
            if (j == -1) {
                return -1; // 非法字符检查
            }
            b[i] = j;
        }
    
        // 校验
        int expect = 0;
        for (int i = 0; i < CODE_LENGTH - 1; i++) {
            expect += b[i];
        }
        expect = (expect * PRIME1) % CODE_LENGTH;
        if (b[5] != expect) {
            return -1;
        }
    
        // 反函数
        for (int i = CODE_LENGTH - 2; i >= 0; i--) {
            b[i] = (b[i] - i * (b[0] - BASE_NUMBER)) % BASE_NUMBER;
        }
        int res = 0;
        for (int i = CODE_LENGTH - 2; i > 0; i--) {
            res += b[i];
            res *= BASE_NUMBER;
        }
    
        // 反放大
        res = ((res + b[0]) - SALT) / PRIME1;
        return res;
    }
    
}

参考