随机数与安全

[TOC]

浅谈随机数与安全性

强弱随机数

真正的随机数是使用武物理现象产生的,例如投掷硬币,转轮,电子白噪声等等,这些随机数的发生器都是物理性的,计算机很难去模拟着种情况。

在实际的应用过程当中,往往使用伪随机数就足够了。这些数外表看起来是随机的,但是他们都是固定的可以重复计算出来的。这就造成了一定的安全隐患。

例如,网站通过发送电子信箱的链接来重置密码,等等。

随机数又有强弱之分,比如弱一点的可以使用当前的时间戳来作为随机数,而起那个随机数则相对比价安全,例如使用系统当前内存占用情况作为随机数。

在Java中,弱伪随机数一般使用java.util.Random类来生成

        Random rondom = new Random(25);
        byte[] bytes = new byte[24];
        rondom.nextBytes(bytes);

我们来看一下内部的原理。

首先是初始化Random对象的时候,传入了一个seed

    /**
     * Construct a random generator with the given {@code seed} as the
     * initial state. Equivalent to {@code Random r = new Random(); r.setSeed(seed);}.
     *
     * <p>This constructor is mainly useful for <i>predictability</i> in tests.
     * The default constructor is likely to provide better randomness.
     */
    public Random(long seed) &#123;
        setSeed(seed);
    &#125;

//看一下setSeed源码

    /**
     * Modifies the seed using a linear congruential formula presented in <i>The
     * Art of Computer Programming, Volume 2</i>, Section 3.2.1.
     */
    public synchronized void setSeed(long seed) &#123;
        this.seed = (seed ^ multiplier) & ((1L << 48) - 1);
        haveNextNextGaussian = false;
    
    &#125;


//multiplier这个是一个静态常量
      private static final long multiplier = 0x5deece66dL;


//再看一下nextBytes的实现

    public void nextBytes(byte[] buf) &#123;
        int rand = 0, count = 0, loop = 0;
        while (count < buf.length) &#123;
            if (loop == 0) &#123;
                rand = nextInt();
                loop = 3;
            &#125; else &#123;
                loop--;
            &#125;
            buf[count++] = (byte) rand;
            rand >>= 8;
        &#125;
    &#125;


//这里面用到了nextInt,再跟踪一下代码
    /**
     * Returns a pseudo-random uniformly distributed &#123;@code int&#125;.
     */
    public int nextInt() &#123;
        return next(32);
    &#125;
//继续跟到next
    protected synchronized int next(int bits) &#123;
        seed = (seed * multiplier + 0xbL) & ((1L << 48) - 1);
        return (int) (seed >>> (48 - bits));
    &#125;
//其实这个就是最基本的实现了
//原理上就是通过种子数的操作,重新生成一个处理后返回,然后种子数继续用,所以如果知道你的种子数,那么这个随机数就是可预测的了。

如果想使用相对安全的随机数,可以使用java.security.SecureRandom类


String tempId = "";
byte[] tempIdBytes = new byte[50];
byte[] leftBytes = new byte[24];
byte[] rightBytes = new byte[24];
//生成48位强伪随机数
random.nextBytes(leftBytes);
random.nextBytes(rightBytes);
      

Java UUID

GUID

GUID是一个128位长的数字,一般用16进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成GUID。从理论上讲,如果一台机器每秒产生10000000个GUID,则可以保证(概率意义上)3240年不重复。

UUID

UID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。由以下几部分的组合:当前日期和时间(UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同),时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得),UUID的唯一缺陷在于生成的结果串会比较长。

可以看出,UUID 是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字

  UUID由以下几部分的组合:

  (1)当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。

  (2)时钟序列

  (3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。

UUID的版本

UID具有多个版本,每个版本的算法不同,应用范围也不同。

首先是一个特例--Nil UUID--通常我们不会用到它,它是由全为0的数字组成,如下:

00000000-0000-0000-0000-000000000000

UUID Version 1:基于时间的UUID

基于时间的UUID通过计算当前时间戳、随机数和机器MAC地址得到。由于在算法中使用了MAC地址,这个版本的UUID可以保证在全球范围的唯一性。但与此同时,使用MAC地址会带来安全性问题,这就是这个版本UUID受到批评的地方。如果应用只是在局域网中使用,也可以使用退化的算法,以IP地址来代替MAC地址--Java的UUID往往是这样实现的(当然也考虑了获取MAC的难度)。

UUID Version 2:DCE安全的UUID

DCE(Distributed Computing Environment)安全的UUID和基于时间的UUID算法相同,但会把时间戳的前4位置换为POSIX的UID或GID。这个版本的UUID在实际中较少用到。

UUID Version 3:基于名字的UUID(MD5)

基于名字的UUID通过计算名字和名字空间的MD5散列值得到。这个版本的UUID保证了:相同名字空间中不同名字生成的UUID的唯一性;不同名字空间中的UUID的唯一性;相同名字空间中相同名字的UUID重复生成是相同的。

UUID Version 4:随机UUID

根据随机数,或者伪随机数生成UUID。这种UUID产生重复的概率是可以计算出来的,但随机的东西就像是买彩票:你指望它发财是不可能的,但狗屎运通常会在不经意中到来。

UUID Version 5:基于名字的UUID(SHA1)

和版本3的UUID算法类似,只是散列值计算使用SHA1(Secure Hash Algorithm 1)算法。

参考文档:

JAVA UUID的生成

Comments

⬆︎TOP