我能够使用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个字符之间)..
答案 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);
}
您需要做的主要是保持密钥的安全。