安全随机数在密码技术中非常重要,它可以决定保密信息是否能被破解。计算机中的安全随机数通常是指随机的熵够高的、不是单纯通过时间生成的伪随机数。本小节从实践的角度分析了如何简单生成安全度更好的随机数的方法,包括客户端生成随机数的方法和服务器端生成随机数的方法。
随机数是指一种不可预知、没有规律的数值。众所周知,由于单纯计算机因素产生的随机数是有规律可以分析的,很难实现真正意义上的随机,这也是这种随机数被称为 伪随机数 的原因。真正的随机数是需要环境或者人的配合才能产生的。
在随机数检测国家规范中有详细的检测标准,需要从频率、游程、连续性和信息熵等方面进行评估,还会进行二进制矩阵测试、傅里叶变换测试和普通统计测试等多种测试验证方法,经过综合分析来判断随机数的合格性。
在密码应用方面,随机数是保证密码算法安全、生成密钥的主要组成。通过代码实现安全随机数(Secure Random)生成是一个非常重要的问题。针对这个问题,根据项目实践环境的不同,结合编者多年来的工程编码经验,此处提出两种足够安全且方便实施的安全随机数生成方案,供读者在项目实践中参考。
第一种方案,适用于需要在客户端应用环境中生成安全随机数的场景。可采用提取鼠标位置来作为随机因子,然后把该随机因子作为原始数据提交给杂凑函数,生成最终的杂凑值作为安全随机数在密码技术中使用;第二种方案,适用于在服务器端生成安全随机数的场景。由于服务器端通常无界面无鼠标,可采用CPU利用率、温度、内存使用等环境噪声数据作为原始随机种子,再进行杂凑来生成安全随机数。在我国商用密码相关VPN产品技术等密码技术标准中,有“随机数应由多路硬件噪声源产生”的要求。本书采用CPU利用率、温度、内存生成安全随机数的思路,就是应用软件对标准的一个软件代码实践。
密码技术对于使用随机数的安全还有很多其他要求,由于篇幅所限本书不再展开细谈,有兴趣且数学功底不错的读者可以参考标准GM/T 0005—2021《随机性检测规范》获取更详细信息。
下面几个小节通过代码示例的方式向读者展示客户端和服务器端产生安全随机数的方法。当然最可靠便捷的获得随机数的方式还是通过密码设备,并且该密码设备获得了密码管理部门的认证检测。
随机数在很多算法和协议中都有着非常重要的作用,而后面的很多算法中也要用到随机数来生成各种参数值,比如分组加密中可能要用随机数作为初始化向量IV等。Java开发工具包JDK中提供了一个随机数类java.security.SecureRandom产生随机数生成器(RNG)。但是SecureRandom()使用伪随机序列作为随机种子生成的方法,没有利用多路噪声源,因此生成的随机数是伪随机数,并不符合GM/T 0005—2021《随机性检测规范》的要求。
(1)实现代码
(2)程序运行效果
因为是随机,所以读者运行的结果和本书肯定不同。
注意 : 上面代码中的BigInteger是大整数类,是密码算法中经常用到的一个类 ,读者可自行查找其用法,此处不再详述。
读者发现了前面用大整数打印出来的数还有符号,此处是个负数,而在密码技术中用的密钥、杂凑结果、大素数等都是无符号数,多用十六进制进行展示或使用,所以本书此后的代码会使用十六进制的输出方法。
客户端安全随机数生成的实现方案是通过获取客户端鼠标坐标位置作为噪声源产生随机种子后,再输入到MessageDigest对象中进行digest杂凑运算,并将输出的杂凑结果作为随机数。这种方案的优点是生成随机种子的环境噪声源比较安全。
(1)实现步骤
1)构建double2bytes()方法,用于将一个double值的数转换成字节数组。
首先用Eclipse添加一个类,类名字定为SecRandom,为了方便可以直接选择main()方法选项。在类中添加一个成员方法double2bytes(),并编写如下的代码,这个方法将一个double值的数转换成字节数组返回。
该方法内部使用Double类的doubleToRawLongBits()方法将double转换成long,再定义一个8字节的byte数组,然后把long的每个字节填充到byte数组中,最后返回这个字节数组。
在类的main()方法(如果没有,可以手动添加)中添加如下代码:
这两行是添加BC的provider代码库到工程中,因为此处代码中使用的Hex类就是来自BC库中的类,而且这里使用SM3杂凑算法(杂凑算法在随后的章节会进行详细讲解)也在BC代码库中。
2)获取鼠标坐标作为随机种子。通过鼠标包装类MouseInfo的getPointerInfo()方法返回位置类对象,然后通过位置类对象pi的getLocation()方法来获得鼠标位置点并存在Point类对象p中,最后通过getX()和getY()分别返回鼠标位置点的X坐标和Y坐标。
把X坐标和Y坐标通过方法double2bytes()转换成字节数组。因为杂凑方法的参数接收的是字节数组而不是double。
用两个坐标的字节数组连接起来形成一个长的字符串myRandom,它作为调用杂凑的原始数据。为了算法使用,需要再将原始数据的形态转换成字节数组data。
3)构建SM3算法实例。输入随机种子进行SM3杂凑运算后,获得杂凑结果。
声明一个杂凑类MessageDigest的对象sm3,通过调用它的静态方法getInstance(),参数传递算法名称“SM3”,所以内部是使用该算法对data数据进行运算。
通过sm3对象的update()方法将数据data输入进去,启动运算的状态。接着直接调用digest()方法开始进行杂凑运算,并将结果值返回到out字节数组中。
4)打印输出随机数。将杂凑的结果,也就是示例需要的“随机数”打印输出,这个随机数在安全性上要比直接用Java类生成的更强壮。
程序运行后的结果如下:
(2)实现代码
在客户端生成安全随机数的方案中,可以使用鼠标位置作为一个随机的种子,但在服务器端这种没有鼠标的环境中要怎么办呢?这里给大家提供一个思路和简单的做法。在工程项目中通常可以将两个因子揉在一起作为随机种子,比如可以将Random类和UUID类产生的两个结果进行糅合,再通过杂凑函数进行单向运算后,将生成的杂凑结果作为随机数使用。
下面的方法是提取服务器的CPU使用百分比和UUID两个因素产生的结果进行揉合作为随机种子,再输入到杂凑SM3进行运算,把杂凑结果当成随机数使用的实践过程。服务器通常24小时运行着业务系统的各种后台服务,CPU时刻都在运算当中,百分比也在不停地变化。UUID是个全球唯一的标识,它由时钟、MAC地址等关键信息进行计算而来。
(1)实现步骤
1)构建serverGetRandom类,实现服务器端计算机CPU使用百分比的提取功能。在Eclipse添加一个类serverGetRandom,在类中添加一个方法getCpuLoadPercentage()来获取CPU的使用百分比,由于编者使用的是Windows10的环境,所以该方法依赖于Windows系统,根据这一思路,如读者在Linux或UNIX平台提取CPU百分比会更简单,可以查阅top命令的使用方法,这里关键是理解这种思路。
通过Runtime类的getRuntime()方法获取一个运行时的实例,然后使用该实例对象的exec()方法执行一个命令,此示例是在Windows平台执行,所以采用了wmic命令,命令参数为“wmic cpu get LoadPercentage”。命令的执行结果通过进程对象process返回。
接着调用了进程对象的getInputStream()方法返回一个输入流is,最后通过这个输入流构造一个输入流读取对象InputStreamReader,再将输入流对象作为参数构造一个缓冲区读对象BufferedReader,以便对读取的内容进行处理。
根据反馈的格式进行处置,通过readLine()把标题和空行读取出来,然后再读取百分比内容,将结果保存在字符串对象percentageLine中。
最后对百分比进行判断,如果读取出错,就返回0,否则就把结果转换成double类型返回给调用者。
2)使用serverGetRandom方法和UUID的方法生成随机种子,验证CPU使用百分比提取功能的效果。在类serverGetRandom中添加main方法。添加如下两行代码,为工程使用BC库中的SM3方法提供支持环境。
调用前面定义的serverGetRandom方法,把方法的返回值保存在变量cpuPercent中。然后通过String类的valueOf方法把double类型转换成字符串类型。通过UUID类的随机产生UUID的方法randomUUID()生成唯一的标识,然后用toString()将标识转换成字符串,最后再用字符串类的getBytes()生成字节数组myRandom。
3)构建SM3算法实例,输入随机种子进行SM3杂凑运算后,获得杂凑结果。定义杂凑对象SM3的实例对象,通过调用其静态方法getInstance,然后将前面得到的UUID变量myRandom和CPU百分比的字节数组形式strCpu.getBytes()都传递给杂凑对象的update()方法,做杂凑计算的数据准备工作。
调用杂凑对象的digest()方法生成结果杂凑值,并把结果返回到变量output中,最后把结果转换成十六进制形式输出,它就是示例生成的安全随机数。
(2)实现代码
(3)代码结果输出