Java 安全的非重复随机字母数字URL段塞

Java 安全的非重复随机字母数字URL段塞,java,security,cryptography,slug,Java,Security,Cryptography,Slug,我能够用Base58实现不重复的随机字母数字URL段塞部分,类似这样的东西 private static final char[] BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); private static final int LENGTH = BASE58_CHARS.length; public static String genSlug(long prii

我能够用Base58实现不重复的随机字母数字URL段塞部分,类似这样的东西

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个字符之间。

您当然可以截断哈希函数的输出,它仍然看起来是随机的,但哈希冲突的可能性会增加。由于您的约束是12个字符的最大输出,这意味着您必须将哈希输出截断为70位12/8*log256/log58。由于生日悖论,在270/2这样的散列之后很可能会发生冲突

因为您需要保证唯一性,所以可以使用伪随机置换PRP将priimaryKeyId转换为随机令牌。分组密码就是这样的PRP。因为块大小的最大大小是70,所以您可以安全地在这个用例中使用tripledes。它的块大小为64位,这也是长文件的大小

未彻底测试的示例:

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);
}

您需要做的主要事情是确保密钥的安全。

@Artjom,更新了问题,13+字符太长了,可能有点挑剔,但请不要再推荐TripleDES作为默认值–即使它可能适合此用例,通常没有理由使用它,但有很多理由不利于it安全,性能较差,…@K.Biermann您的看法是正确的,现在不应该使用DES或Triple DES进行加密。不过,这里的用例不同,因为需要64位分组密码。由于Triple-DES提供了112位的安全性,所以仍然没有那么糟糕。其他分组密码包括河豚、斑点、匆忙布丁等。