如何生成固定长度唯一随机字符串? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xfund4
V2EX    PHP

如何生成固定长度唯一随机字符串?

  •  
  •   xfund4 2018-01-15 17:41:45 +08:00 14420 次点击
    这是一个创建于 2831 天前的主题,其中的信息可能已经有所发展或是发生改变。
    1. 字符串长度固定在 8 位[a-zA-Z0-9],仅要求千万数据量下保证唯一
    2. 不需要根据随机串解码出原值。
    3. 随机串不能被猜测出。
    4. 有自增 ID 可以使用,但是随机串不能有规律。
    第 1 条附言    2018-01-16 14:53:26 +08:00
    谢谢各位提供的方案。

    预生成的方案应该是最合理的 能满足真随机和唯一。 不过暂时不会考虑预生成所以排除了。

    由于可能发生 批量生成的场景(抽奖活动预创建 1W 个兑换码)所以想避开检查碰撞的查询数据库操作。

    至于随机和唯一 两者的数学上的冲突 不在讨论范围内,我想表达的是 看似随机 无法被猜测这个要求。

    自增长 ID 辅助生成已经可以满足我的需求。 另外想了解下 如果没有依赖自增长 ID。 这种场景 常用的方案是哪些
    57 条回复    2018-01-17 05:00:43 +08:00
    maemual
        1
    maemual  
       2018-01-15 17:43:08 +08:00
    自增 ID 改成随机自增 x
    Sypher
        2
    Sypher  
       2018-01-15 18:00:56 +08:00
    丢 list 里啊,生成的时候检查下 list.contains(newStr)。
    Keyes
        3
    Keyes  
       2018-01-15 18:01:36 +08:00 via Android   1
    生成订单号吗?参考京东做法,订单号有一个用户 id 作为 parent,随便猜,没有用,而且订单号可以做很短,客服和客户可以很容易识别
    bearice
        4
    bearice  
       2018-01-15 18:03:37 +08:00
    XTEA 加密
    xfund4
        5
    xfund4  
    OP
       2018-01-15 18:05:38 +08:00
    @Keyes 类似于 生成邀请码,兑换码。
    porrat
        6
    porrat  
       2018-01-15 18:07:12 +08:00
    sha1(random_chars)
    scriptB0y
        7
    scriptB0y  
       2018-01-15 18:07:20 +08:00
    uuid.uuid4()
    porrat
        8
    porrat  
       2018-01-15 18:07:57 +08:00
    看错了,以为是 40 位,可以再编码一次
    xfund4
        9
    xfund4  
    OP
       2018-01-15 18:08:27 +08:00
    @Sypher 类似兑换码的业务,数据库压力很低, 不想借助 redis 之类的服务。

    另外,生成的随机串要求上面给错了。 是 [A-Z1-9]
    chinvo
        10
    chinvo  
       2018-01-15 18:09:10 +08:00
    ksuid K-Sortable Globally Unique IDs 长度太长不符合楼主要求

    shortid 7-14 位,位数不固定

    hashids 同位数不固定(非传统意义 ID,而是将数字 ID 加密,可逆算法
    lululau
        11
    lululau  
       2018-01-15 18:10:51 +08:00
    邀请码兑换码肯定要存库的,这个直接随机就好了吧,随机完了查下库里是不是有这个数了,有的话就重新随机一个,一个 alphanum 字符可以编码 5 位,就按 4 位算,8 个字符可以编码 4 个字节了,4 个字节怎么还不够千万;还是说不知道怎么把一个整数转换成 alphanum 字符串?
    xfund4
        12
    xfund4  
    OP
       2018-01-15 18:11:27 +08:00
    @chinvo 由于数据量是可控的, 所以额外要求了生成的串 必须是 8 位 [A-Z1-9]
    honeycomb
        13
    honeycomb  
       2018-01-15 18:13:43 +08:00 via Android
    最笨的一个办法是用 csprng 导出二进制数,再转换成楼主需要的 36 进制
    tabris17
        14
    tabris17  
       2018-01-15 18:14:24 +08:00
    根据自增 ID,用 skip32 加密,然后 base62 编码
    moult
        15
    moult  
       2018-01-15 18:14:28 +08:00
    自增 ID 从 10 进制转到 62 进制。然后再追加随机字符串补全 8 位。这样可以不用去数据库校验是否重复,虽然前面几位有规律,但是后面是随机的,也能做到猜不出。
    http://php.net/manual/en/function.random-bytes.php
    chinvo
        16
    chinvo  
       2018-01-15 18:14:41 +08:00
    @xfund4 hashids 可以控制最短位数,如果你的数据(数值型 id )没有超过一定限制,那么你固定最短 8 位就可以保证输出的字符串时 8 位的。另外 hashids 字符串范围可控,在初始化的时候传入一个 string 就好。

    我也没有其他更好的方案了,你可以先试一下这个。
    xfund4
        17
    xfund4  
    OP
       2018-01-15 18:16:53 +08:00
    @moult 是的,我不希望再查一次数据库防碰撞。62 进制补足 8 位 这个随机补位有可能会造成重复啊。
    lululau
        18
    lululau  
       2018-01-15 18:19:11 +08:00
    要随机就免不了碰撞,要不想检查碰撞就不可能随机。。。
    Magnus1k
        19
    Magnus1k  
       2018-01-15 18:24:23 +08:00   3
    不想碰撞就把所有符合的字符串全部生成了,然后随机挑一点出来用。
    daodao
        20
    daodao  
       2018-01-15 18:26:16 +08:00
    hash
    zjp
        21
    zjp  
       2018-01-15 18:31:46 +08:00 via Android
    我用的是时间戳加 4 位随机数,限制 8 个字符的话时间戳范围取小一点不知道够不够
    toan
        22
    toan  
       2018-01-15 18:44:48 +08:00 via Android
    @Magnus1k 赞同。不想碰撞的话,先生成,后随机挑。
    目前我这做过一个实例,先生成可用数据池数据,比如先生出 2 万,生成的时候进行唯一检验,使用的时候从该池子里随机挑选,增加使用取数的效率。当池子数据量低于某个阈值了,就重新生成补满池子。
    ylsc633
        23
    ylsc633  
       2018-01-15 18:48:45 +08:00
    用这个吧 hashids

    我用过,感觉还不错,没有应用到大项目里,所以 测不出性能消耗

    这个只需要你保管好自己的 salt 就行了!

    用的 这个包 https://github.com/ivanakimov/hashids.php

    具体实践 类似于 https://www.g9zz.com/post/6ravkEd7bx 这种吧 后面是定长的,且没有特殊字符,还可反解出来
    xfund4
        24
    xfund4  
    OP
       2018-01-15 18:58:20 +08:00
    谢谢诸位提供的方案, 因为有自增 ID 来为保证唯一。确实 hashids 的方案比较适合。

    @chinvo @ylsc633
    xfund4
        25
    xfund4  
    OP
       2018-01-15 18:59:52 +08:00
    @chinvo @Magnus1k @toan 如果没有自增 ID,并且不希望是预生成的。 有没有更好的办法呢
    l1093178
        26
    l1093178  
       2018-01-15 19:01:03 +08:00
    完全随机的话,到 sqrt(62 ^ 8) ~= 14, 000, 000 这个数量级就会出现冲突( https://zh.wikipedia.org/wiki/生日),所以说只能考虑除了完全随机之外的方案
    可以考虑用这个库: https://github.com/c2h5oh/hide
    Kilerd
        27
    Kilerd  
       2018-01-15 19:03:07 +08:00
    sha3
    moult
        28
    moult  
       2018-01-15 19:03:11 +08:00
    @xfund4 不想数据库查询的话,肯定要基于一个现有的 ID 来生成了。
    1、基于数据库的自增 ID 来生成,就我#15 给你的办法,比如 1-5 位由自增 ID 转到 62 进制,不够 5 位就补 0,后面三位随机生成来避免猜测性。这样肯定不会重复的。
    2、基于时间戳+用户 ID,也是将随机串的其中几位来保存时间戳和用户 ID。虽然无法避免统一秒同一用户发多个请求,但是后面还有随机串在,生成重复的概率可以忽略不计。另外时间戳没必要 1970 开始,就从 2018 开始就好了,这样时间戳会小很多。
    SunnyMeow
        29
    SunnyMeow  
       2018-01-15 19:05:24 +08:00 via iPad
    kaneg
        30
    kaneg  
       2018-01-15 19:08:04 +08:00 via iPhone
    我的一个不成熟的想法:A-Z,0-9,共 36 位,每个 ID8 位,则共有 36^8 种可能,而你的需求只要千万数量级,那么可以把总的取值范围等分为千万块,每一块大概有上万个值。使用的时候先用你的自增 ID 取一个块,然后在这一块里随机取一个值。这样的结果是每个值都不会重复,每个值是万分之一的随机,所以被猜测的可能性也很小
    xupefei
        31
    xupefei  
       2018-01-15 19:14:29 +08:00
    8 位千万数据量不算很大,直接 rand 然后查重就可以做到。
    查重有很多办法可以用。不过上千万的数据,普通的哈希表已经慢到不行了。可以用 bloom filter 和各种 data sketch 算法。
    toan
        32
    toan  
       2018-01-15 19:16:09 +08:00 via Android
    @xfund4 碰撞问题没有好的办法避免。
    geelaw
        33
    geelaw  
       2018-01-15 19:17:27 +08:00
    用安全的随机置换即可。
    owenliang
        34
    owenliang  
       2018-01-15 19:35:40 +08:00 via Android
    随机生成字节系列,转 16 进制,去重保存
    renyijiu
        35
    renyijiu  
       2018-01-15 19:40:25 +08:00
    时间戳加一定长度字符串
    viko16
        36
    viko16  
       2018-01-15 19:49:45 +08:00
    实际应用上还得考虑把 "iIl1" 这类不易辨识的字母组合移除..
    innoink
        37
    innoink  
       2018-01-15 20:08:20 +08:00
    既然已经知道总数,那么预先生成存起来,然后随用随取
    innoink
        38
    innoink  
       2018-01-15 20:09:25 +08:00
    检查的时候用 bloomfilter
    qfdk
        39
    qfdk  
    PRO
       2018-01-15 20:35:32 +08:00 via iPhone
    md5 (数据+ 随便)取指定长度
    julyclyde
        40
    julyclyde  
       2018-01-15 20:36:29 +08:00
    固定长度就不可能唯一啊
    yingfengi
        41
    yingfengi  
       2018-01-15 21:05:34 +08:00
    非程序员,写过一些小东西,我记得 php 有个获取当前时间戳生成一个 ID 的函数还是啥,生成的这个 ID 会是毫秒级的,反正是这么一个东西,之前用过,生成 ID 后 md5 作为一个 key,当初是这样只玩的。
    你可以,md5 之后随机取 8 位啥的
    以上,仅供参考
    billlee
        42
    billlee  
       2018-01-15 21:52:46 +08:00
    block_ciper(counter())
    fuyufjh
        43
    fuyufjh  
       2018-01-15 21:56:46 +08:00
    最科学的方法:把输出看成一个(26+26+10)进制、8 位的数

    random.randint(0, (26+26+10)**8)
    nccer
        44
    nccer  
       2018-01-15 22:41:01 +08:00   1
    生成个池子,用的时候在里面选一个.
    zhx1991
        45
    zhx1991  
       2018-01-16 00:13:40 +08:00
    用上面说的某种碰撞很低但不是没有的随机策略

    然后在库里把这个字段设成唯一

    插入重复数据会报错(非常罕见), 捕获相应异常重来
    janxin
        46
    janxin  
       2018-01-16 11:05:06 +08:00
    提前生成随机字母数字池,过滤清洗重复数据。这段时间不占用业务响应时间。使用的时候取出任意(从头部或尾部取出),销毁之。
    w3sy
        47
    w3sy  
       2018-01-16 11:28:56 +08:00
    @Magnus1k 完全正确,数学问题,不用讨论了
    janxin
        48
    janxin  
       2018-01-16 11:32:26 +08:00
    刚刚说的还是比较业务偷懒了,其实技术一点的方法是设计一个映射算法即可。随便想了一个简单的:保证安全性前提下可以使用带校验位方式,可以占一个字母;为了混淆,可随机生成 2 位随机位,也可以用于后续 ID 的运算随机位可重复没关系,占用两个字节,不要求安全就随机三个字节;自增 ID 占用 5 位即可满足现有数量级需求,可按位或其他方式进行运算得到一个,还可以带入之前取到的随机数进行,凯撒之类的简单算法就可以。顺序还可以随机排序组合,能识别的前提下 XD
    raptor
        49
    raptor  
       2018-01-16 11:53:40 +08:00
    千万级就是小于 1 亿,对应二进制不到 27 位。
    A-Z1-9 8 位,相当于 35 进制的 8 位,对应二进制 41 位多点。
    生成一个 14 位的随机数,左移 27 位,和 ID 组成一个 41 位二进制数。
    自己设置一个密钥,再用 RC4 加密这个 41 位二进制数。
    最后把这个加密后的二进制数转为 35 进制的 A-Z1-9。

    这样可以保证唯一,猜不到,不用查数据库三个要求。

    解出 ID 的方法:
    字符串转成二进制,RC4 解密,把 41 位二进制最高 14 位填充成 0,再补 23 位 0 填充成 64 位,转成整数即是 ID。
    mingl0280
        50
    mingl0280  
       2018-01-16 12:23:36 +08:00
    SHA1/MD5 一个随机值(真随机值)取其中随意八位,然后查库有没有碰撞到的……
    其实讲道理这个碰撞在千万量级应该是不可能的……
    chuhemiao
        51
    chuhemiao  
       2018-01-16 13:10:45 +08:00
    是时候上区块链思想了
    lbp0200
        52
    lbp0200  
       2018-01-16 15:10:28 +08:00
    从老外那,抄来的
    od -x /dev/urandom | head -1 | awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}'
    pheyx
        53
    pheyx  
       2018-01-16 16:16:10 +08:00
    @honeycomb 你这个想法确实挺笨又 low 的
    mengzhuo
        54
    mengzhuo  
       2018-01-16 17:10:27 +08:00
    总空间只有 = 218,340,105,584,896
    你的要求是 = 000,000,0xx,xxx,xxx

    正好对半开
    把时间填在前面,唯一 id 填到后面,然后随便位移,置换,异或一下就好了(反正没人会真的看你的算法)
    SooHoo
        55
    SooHoo  
       2018-01-16 17:54:33 +08:00
    这两天公司业务需求,提现码(不能重复,不能被猜测到,不能太长,最多 6 位)
    大概说下生成规则

    先将数字小写字母大写字母打乱
    然后左边取 20 个字符作为 randomKey
    剩下右边给 numKey

    将用户 id 进行 numKey.length 进制换算,生成字符串 result

    长度不足 6 个,从 randomKey 随机取 6 个字符,插入 result 字符串 随机位置
    SooHoo
        56
    SooHoo  
       2018-01-16 17:57:40 +08:00
    @SooHoo

    接上 ,没发完 不小心发出来了
    ----------
    大概思想就是把 uid 放入到串里面,然后,随机插入 不包括 uid 的字符

    为了让用户稍微难些分析。就加入了进制转换,然后随机插入位置。

    可能有 BUG,欢迎指正。哈哈

    -----------------------------------------



    /**
    *
    * @param count 字符个数
    * @param uid 用户 id
    * @return
    */
    public static String randomString(int count, int uid) {

    String randomKey = CODE.substring(0, 20);
    String numKey = CODE.substring(20);

    StringBuilder result = new StringBuilder();

    while (uid > 0) { //转成 numKey.length 进制
    int p = uid % numKey.length();
    result.append(numKey.substring(p, p + 1));
    uid = uid / numKey.length();
    }

    if (result.length() < count) {//字数不足,随机字符补全
    Random random = new Random();
    int size = count - result.length();
    for (int i = 0; i < size; i++) {
    int r = random.nextInt(randomKey.length()); //随机取一个字符
    int p = random.nextInt(result.length() + 1);//随机一个位置
    result.insert(p, randomKey.substring(r, r + 1));
    }
    }
    return result.toString();
    }
    wwhc
        57
    wwhc  
       2018-01-17 05:00:43 +08:00
    md6sum -d32 用户 ID/随机数
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1149 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 23:35 PVG 07:35 LAX 16:35 JFK 19:35
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86