简体   繁体   中英

Secure non-repeating Random alphanumeric URL slug

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM