I'm able to achieve non-repeating Random alphanumeric URL slug part with Base58, something like this,
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);
}
But how to achieve secure-randomness with it?
If we do
Hashing.sha256().hashString(genSlug(priimaryKeyId), StandardCharsets.UTF_8).toString();
the slug becomes 64 characters , that's too long (expecting it to be the same length as the genSlug, which is between 1 and 12 chars long)..
You could certainly truncate the output of the hash function and it would still appear random, but the possibility of a hash collision will rise. Since your constraint is a maximum output of 12 characters, this means that you have to truncate the hash output to 70 bits ( 12/8 * log(256)/log(58)
). Due to the birthday paradox, it is likely that you get a collision after 2 70/2 such hashes.
Since you need guaranteed uniqueness, you can use a pseudo-random permutation (PRP) to transform priimaryKeyId
into a random token. A block cipher is such a PRP. Since the maximum size of the block size is 70, you can safely use Triple DES for this use case. It has a block size of 64 bit which is also the size of a long
.
Example (not thoroughly tested):
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);
}
The main thing you need to do is to keep the key safe.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.