安全的非重复随机字母数字URL slug

时间:2017-08-20 16:46:56

标签: java security cryptography slug

我能够使用Base58实现非重复随机字母数字URL slug 部分,类似这样,

private static final char[] BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
private static final int LENGTH = BASE58_CHARS.length;
public static String genSlug(long priimaryKeyId) {
    char[] buffer = new char[20];
    int index = 0;
    do {
        int i = (int) (priimaryKeyId % LENGTH);
        buffer[index++] = BASE58_CHARS[i];
        priimaryKeyId = priimaryKeyId / LENGTH;
    } while (priimaryKeyId > 0);
    return new String(buffer, 0, index);
}

但如何实现安全随机性呢?

如果我们这样做

Hashing.sha256().hashString(genSlug(priimaryKeyId), StandardCharsets.UTF_8).toString();

slug变为64个字符,这太长了(期望它与genSlug的长度相同,长度在1到12个字符之间)..

1 个答案:

答案 0 :(得分:1)

你当然可以截断哈希函数的输出,但它仍然是随机的,但哈希冲突的可能性会上升。由于约束是12个字符的最大输出,这意味着您必须将哈希输出截断为70 12/8 * log(256)/log(58))。由于生日悖论,你可能会在2 70/2 这样的哈希之后发生碰撞。

由于您需要保证唯一性,因此您可以使用伪随机置换(PRP)将priimaryKeyId转换为随机令牌。分组密码就是这样的PRP。由于块大小的最大大小为70,因此您可以安全地将Triple DES用于此用例。它的块大小为64位,也是long的大小。

示例(未经过全面测试):

private static final char[] BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
private static final int LENGTH = BASE58_CHARS.length;
private static final BigInteger LENGTH_BI = BigInteger.valueOf(LENGTH);

// TODO: CHANGE THE KEY TO SOMETHING RANDOM!
private static final byte[] KEY = new byte {1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8};

public static String genSlug(long priimaryKeyId) {
    ByteBuffer bb = ByteBuffer.allocate(8);
    bb.putLong(priimaryKeyId);

    Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(KEY, "DESede"));
    byte[] encrypted = cipher.doFinal(bb.array());
    BigInteger bi = new BigInteger(1, encrypted);

    char[] buffer = new char[20];
    int index = 0;
    do {
        BigInteger i = bi.mod(LENGTH_BI);
        buffer[index++] = BASE58_CHARS[i.intValue()];
        bi = bi.divide(LENGTH_BI);
    } while (bi.compareTo(BigInteger.ZERO) == 1);
    return new String(buffer, 0, index);
}

您需要做的主要是保持密钥的安全。