我必须生成唯一的 URL 部分,该部分将“不可猜测”并且“抵抗”暴力攻击。它还必须尽可能短:)并且所有生成的值必须具有相同的长度。我正在考虑使用 UUID V4,它可以由 32 个(没有连字符)十六进制字符
de305d5475b4431badb2eb6b9e546014
表示,但它有点太长了。所以我的问题是如何生成一些unqiue,可以用url字符表示,每个生成的值具有相同的length并且短于32个字符。 (在node.js或pgpsql中)
v4()
将生成一个大数字,并将其转换为十六进制字符串。在 Node.js 中,您可以使用 Buffer
将字符串转换为更小的 Base64 编码:
import { v4 } from 'uuid';
function getRandomName() {
const hexString = v4();
console.log("hex: ", hexString);
// remove decoration
const hexStringUndecorated = hexString.replace(/-/g, "");
const base64String = Buffer
.from(hexStringUndecorated, 'hex')
.toString('base64')
console.log("base64:", base64String);
return base64String;
}
产生:
hex: 6fa1ca99-a92b-4d2a-aac2-7c7977119ebc
base64: b6HKmakr
hex: bd23c8fd-0f62-49f4-9e51-8b5c97601a16
base64: vSPI/Q9i
UUID v4 本身实际上并不能保证唯一性。两个随机生成的 UUID 发生冲突的可能性非常非常小。这就是为什么它们需要这么长——这减少了冲突的机会。 所以你可以让它更短,但你做得越短,它实际上就越有可能不是唯一的。 UUID v4 的长度为 128 位,因为这通常被认为“足够唯一”。
short-uuid模块就是这样做的。
“生成标准 UUID 并将其转换为更短的或只是不同的格式,然后再转换回来。”
它接受自定义字符集(并提供一些)来来回转换 UUID。
您还可以对 uuid 进行 Base64 处理,将其缩短为 22。这是一个 playground。
这完全取决于它的可猜测性/独特性。
我的建议是生成 128 个随机位,然后使用 base36 对其进行编码。这会给你一个“好”的 URL,而且它是独一无二的,并且可能足够难以猜测。
如果你想要更短,可以使用base64,但base64需要包含两个非字母数字字符。
这是一个相当古老的线程,但我想指出最重要的答案并没有产生它声称的结果。它实际上会生成约 32 个字符长的字符串,但示例声称有 8 个字符。如果您想要更多压缩,请使用此函数将 uuid 转换为基数 90。
使用Base64每3个字节需要4个字符,而Hex(Base16)每个字节需要2个字符。这意味着 Base64 的存储大小比十六进制好约 67%,但如果我们可以增加字符/字节比率,我们可以获得更好的压缩。因此,Base90 提供了稍微更多的压缩。
const hex = "0123456789abcdef";
const base90 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+-./:<=>?@[]^_`{|}~";
/**
* Convers a Base16 (Hex) represented string to a Base 90 String.
* @param {String} number Hex represented string
* @returns A Base90 representation of the hex string
*/
function convertToBase90(number) {
var i,
divide,
newlen,
numberMap = {},
fromBase = hex.length,
toBase = base90.length,
length = number.length,
result = typeof number === "string" ? "" : [];
for (i = 0; i < length; i++) {
numberMap[i] = hex.indexOf(number[i]);
}
do {
divide = 0;
newlen = 0;
for (i = 0; i < length; i++) {
divide = divide * fromBase + numberMap[i];
if (divide >= toBase) {
numberMap[newlen++] = parseInt(divide / toBase, 10);
divide = divide % toBase;
} else if (newlen > 0) {
numberMap[newlen++] = 0;
}
}
length = newlen;
result = base90.slice(divide, divide + 1).concat(result);
} while (newlen !== 0);
return result;
}
/**
* Compresses a UUID String to base 90 resulting in a shorter UUID String
* @param {String} uuid The UUID string to compress
* @returns A compressed UUID String.
*/
function compressUUID(uuid) {
uuid = uuid.replace(/-/g, "");
return convertToBase90(uuid);
}
超过几百万个随机 uuid,这不会生成重复项和以下输出:
Lengths:
Avg: 19.959995 Max: 20 Min: 17
Examples:
Hex: 68f75ee7-deb6-4c5c-b315-3cc6bd7ca0fd
Base 90: atP!.AcGJh1(eW]1LfAh
Hex: 91fb8316-f033-40d1-974d-20751b831c4e
Base 90: ew-}Kv&nK?y@~xip5/0e
Hex: 4cb167ee-eb4b-4a76-90f2-6ced439d5ca5
Base 90: 7Ng]V/:0$PeS-K?!uTed
UUID 长度为 36 个字符,如果您希望保存将其转换回来的功能并确保 URL 安全,则可以将其缩短为 22 个字符(约 30%)。
这里是base64 url安全字符串的纯节点解决方案:
type UUID = string;
type Base64UUID = string;
/**
* Convert uuid to base64url
*
* @example in: `f32a91da-c799-4e13-aa17-8c4d9e0323c9` out: `8yqR2seZThOqF4xNngMjyQ`
*/
export function uuidToBase64(uuid: UUID): Base64UUID {
return Buffer.from(uuid.replace(/-/g, ''), 'hex').toString('base64url');
}
/**
* Convert base64url to uuid
*
* @example in: `8yqR2seZThOqF4xNngMjyQ` out: `f32a91da-c799-4e13-aa17-8c4d9e0323c9`
*/
export function base64toUUID(base64: Base64UUID): UUID {
const hex = Buffer.from(base64, 'base64url').toString('hex');
return `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(
12,
16,
)}-${hex.substring(16, 20)}-${hex.substring(20)}`;
}
测试:
import { randomUUID } from "crypto";
// f32a91da-c799-4e13-aa17-8c4d9e0323c9
const uuid = randomUUID();
// 8yqR2seZThOqF4xNngMjyQ
const base64 = uuidToBase64(uuid);
// f32a91da-c799-4e13-aa17-8c4d9e0323c9
const uuidFromBase64 = base64toUUID(base64);