random and seurerandom random number security

Foreword:

        Recently, code audit often sees that random numbers are generated by codes. The scanner prompts that seurerandom should be used to generate random numbers. I haven’t studied in depth how random is unsafe. I have time to study it today. I will record the analysis below.

principle:

Random:

        First look at the source code for generating random numbers:

         If the seed is not set, the default random generated seed code is seedUniquifier() ^ System.nanoTime(), the generation of the seed depends on two values, one is the seedUniquifier of the AtomicLong type and the other is the time, here the initialized value of the seedUniquifier is fixed , the generated result is predictable, and what really affects the latter is System.nanoTime(), where System.nanoTime() generates a millisecond sixteen-digit timestamp, so the seed of Random is mainly based on time Generated, can be predicted.

        Let's take a look at how to generate random numbers with seeds. When we execute nextInt, we can see that the internal code is:

         According to the source code, we can see that the three values ​​of multiplier, addend and mask are all hard-coded. The seed that really affects the generation of random numbers is the seed. When using random again, theoretically, as long as we know the original seed, we You can use (oldseed * multiplier + addend) & mask; to know the latest seed and the random value of the corresponding type.

SecureRandom:

        Before understanding how SecureRandom is generated, you need to understand what is a true random number and what is a pseudo-random number:

        True random numbers are usually derived from hardware random number generators, and the random numbers generated each time are true random numbers, but due to physical factors, the generation time is slow.

        Pseudo-random numbers usually come from a certain generation algorithm. Although the random numbers generated each time are random, they still follow the rules of the generation algorithm. The advantage is that the generation speed is fast.

        We call the module that generates true random numbers a true random number generator (TRNG), and the module that generates pseudo-random numbers a pseudo-random number generator (PRNG) (also called a deterministic random number generator, DRBG), where The seed used for pseudo-random numbers is called an entropy source (entropy pool).

Pseudo-random number generation algorithm, NIST SP 800-90A specification describes three algorithms for generating pseudo-random numbers:

  • Hash_DRBG: Use one-way hash algorithm as the basic algorithm for pseudo-random number generation;
  • HMAC_DRBG: Use the message authentication code algorithm as the basic algorithm for random number generation;
  • CRT_DRBG: Use the counter mode of the block cipher algorithm as the basic algorithm for random number generation

        There are three types of algorithms used by SecureRandom, NativePRNG, DRBG and SHA1PRNG, the code is as follows:

        The default is to use DRBG, and NativePRNG can only be used if specified. At this time, pay attention to:

  • The /dev/random device returns random bytes less than the total noise of the entropy pool. /dev/random generates highly random public keys or one-time pads. If the entropy pool is empty, the read operation on /dev/random will be blocked, and it will wait forever, which forces the JVM to wait, which will cause online problems, so pay attention here.
  • /dev/urandom ("unlocked", non-blocking random number generator) is NativePRNG, which reuses data in the entropy pool to generate pseudo-random data. This means that read operations on /dev/urandom will not block, but the entropy of its output may be less than that of /dev/random.

        The lack of keyboard and mouse input and disk activity in the server can generate the required randomness or entropy, so using /dev/random is likely to cause system problems, so you can use -Djava.security.egd=file:/dev/. /urandom (the file name has multiple u) to force the use of the /dev/urandom file.

      In addition, there is no "file:/dev/random" or "file:/dev/urandom" in windows, so MS CryptoAPI will be used to generate random numbers:

        Let's take a look at how SecureRandom generates seeds under Windows. When we call nextInt, the first entry is Random's nextInt, but the generation of seeds will enter SecureRandom:

        Then determine whether the entropy exists, the first call will generate seeds, and then set instantiated to True, and subsequent nextint will not generate new seeds.

         Seed generation provides the function of information digest algorithm through MessageDigest, and outputs a fixed-length hash value, in which information such as the system configuration and the obtained file name in the system temp file is used as a random value as a string for hash operation as a seed , so as to ensure that the value generated by each machine has a strong correlation with the system, thereby ensuring that the seed is unique and unguessable.

        It can be seen that the way SecureRandom generates seeds mainly uses information such as system information and file names in the trmp file as entropy, and then generates seeds. Due to the unguessable system information and the uncontrollable content of the temp file, the attacker cannot predict the generation of seeds. Naturally, there is no way to attack. However, if the user uses setSeed to set the seed by himself, he will not enter the above-mentioned process of generating the seed, but use the seed set by the user to generate the random number, so the security will be greatly reduced.

test:

Build the spring environment:

        In fact, there is no need to build a spring environment to test, but it is more immersive to simulate a possible real situation. However, after analyzing it, I found that it does not need to be built, but since it is built, I also record the building process. I use The springmvc method is used to build, springboot is easier to build, but I accidentally used springmvc to build, this part is to record the building process, so as not to forget it, you can skip it directly.

        First create a project using Maven, then add web, select file->Project Structure, remember to make sure the webapp directory and web, xml location is correct:

        Then add the required third-party jar package in pom.xml, and pay attention to <packaging>war</packaging> here to set the packaging type to war:

<packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <!-- 作用在打包时确保servlet不会打包进去 -->
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2.1-b03</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

    </dependencies>

    <!-- 插件 -->
    <build>
        <plugins>
            <!-- 编码和编译和JDK版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <!-- 根据自己电脑中的jdk版本选择maven的版本,如果不匹配可能报错 -->
                <version>3.8.1</version>
                <configuration>
                    <!-- 自己电脑中的jdk版本 -->
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <!--tomcat插件-->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <path>/</path>
                    <!-- 可自定义访问端口 -->
                    <port>1234</port>
                </configuration>
            </plugin>
        </plugins>
    </build>

    Then create a spring configuration file, first create applicationContext.xml, add configuration and constraints for the spring container:

        After the creation is successful, write the following content, in which xmlns:context and xsi:schemaLocation are newly added: 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- Spring配置文件:除了控制器之外的bean对象都在这被扫描 -->
    <context:component-scan base-package="org.example.dao"/>
</beans>

        Then create a springmvc configuration file and write the following:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        ">
    <!-- SpringMVC配置文件:控制器的bean对象在这被扫描 -->
    <context:component-scan base-package="org.example.controller"/>
    <!--    启动mvc的注解-->
    <mvc:annotation-driven/>
    <!--    配置视图解析器的配置-->                                  
    <!--    调用视图解析器的方法:InternalResourceViewResolver-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 前缀 默认访问路径是webapp根路径下的,如果webapp下还有其他文件夹就写:/webapp/文件夹名-->
        <property name="prefix" value="/"/>
        <!-- 后缀 如果是index.html文件,就写html -->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

        Then modify web.xml, create a listener to load our applicationContext.xml and load the servlet to read the configuration file springmvc.xml of springmvc:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- Spring配置-->

    <!-- 1、让监听器知道spring的配置文件的位置-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- spring配置文件的文件名 -->
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- 2.创建监听器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- springmvc的 核心\前端\中央 控制器-->

    <!-- servlet的封装-->

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- servlet读取springmvc的配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- springmvc配置文件的文件名 -->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!-- 在容器创建servlet对象的优先级.数字越小越先创建 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!-- 设置访问路径后必须加.do才能进行访问 -->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

</web-app>

         Then create a folder in the code, which can be created according to this template, and it is not fixed:

controller文件夹 :一般是放web控制器的服务类(就是根据前端发来的请求进行数据的处理,然后返回给前端)
dao文件夹 :一般是放数据库的操作类(数据库相关操作数据访问等)
pojo文件夹 :一般是放实体类(用来数据库映射对象)
service文件夹 :一般是做相应的业务逻辑处理的服务类

       Then we have to write our code, first create ChangePasswordImpl in service, the code is as follows:

package org.example.service;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;

public class ChangePasswordImpl{
    public void hello(){
        System.out.println("TeamService---------hello");
    }
    
    public static String SecurRandomscramble(SecureRandom secureRandom, String inputstring){
        char a[] = inputstring.toCharArray();
        for(int i=0; i<a.length; i++){
            int j = secureRandom.nextInt(a.length);
            char temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        return new String(a);
    }
    public String seurerandom(String seed) {
        try {
            String outstr = "name:admin";
            String username = "admin";
            //Random random = new Random();
            SecureRandom secRandom = new SecureRandom();
            if(!seed.equals("")) {
                Long aLong = Long.valueOf(seed).longValue();
                secRandom.setSeed(aLong);
            }
            MessageDigest md = MessageDigest.getInstance("MD5");// 生成一个MD5加密计算摘要
            md.update(username.getBytes());// 计算md5函数
            String hashedPwd = new BigInteger(1, md.digest()).toString(16);// 16是表示转换为16进制数
            outstr = outstr + "</h1><h1>MD5hash:" +hashedPwd;
            String string1 = SecurRandomscramble(secRandom, hashedPwd);
            outstr = outstr + "</h1><h1>encode1:" + string1;
            String string2 = SecurRandomscramble(secRandom, string1);
            outstr = outstr + "</h1><h1>encode2:" + string2;
            return outstr;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }
    
    public static String Randomscramble(Random random, String inputstring){
        char a[] = inputstring.toCharArray();
        for(int i=0; i<a.length; i++){
            int j = random.nextInt(a.length);
            char temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        return new String(a);
    }
    
    public String random(String seed) {
        try {
            String outstr = "name:admin";
            String username = "admin";
            Random random = new Random();
            if(!seed.equals("")) {
                Long aLong = Long.valueOf(seed).longValue();
                random.setSeed(aLong);
            }
            MessageDigest md = MessageDigest.getInstance("MD5");// 生成一个MD5加密计算摘要
            md.update(username.getBytes());// 计算md5函数
            String hashedPwd = new BigInteger(1, md.digest()).toString(16);// 16是表示转换为16进制数
            outstr = outstr + "</h1><h1>MD5hash:" +hashedPwd;
            String string1 = Randomscramble(random, hashedPwd);
            outstr = outstr + "</h1><h1>encode1:" + string1;
            String string2 = Randomscramble(random, string1);
            outstr = outstr + "</h1><h1>encode2:" + string2;
            return outstr;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

}

        Then create ChangePasswordServlet in the controller, the code is as follows:

package org.example.controller;

import org.example.service.ChangePasswordImpl;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServlet;

@Controller
public class ChangePasswordServlet extends HttpServlet {
    ChangePasswordImpl changePassword = new ChangePasswordImpl();
    @RequestMapping("/index.do")//设置请求路的径
    public ModelAndView hello(){
        changePassword.hello();
        ModelAndView mv = new ModelAndView();
        mv.addObject("team", "teamindex----hello");//相当于request.setAttribute("team", "teamindex----hello")
        //经过InternalResourceViewResolver对象处理后前缀加上后缀就变为了:    /文件夹/index.jsp
        mv.setViewName("index");//未来要经过Springmvc的视图解析器处理,转换成物理资源路径。/相当于request.getRequestDispatcher("index.jsp").forward();
        return mv;
    }


    //设置请求路的径  规定请求的方式是post
    @RequestMapping(value = "/seurerandom.do",method = RequestMethod.GET)//请求方式设定后,只能用post的提交方式
    public ModelAndView seurerandom(@RequestParam("key") String key, @RequestParam("seed") String seed){
        String checkkey = "False";
        String back = changePassword.seurerandom(seed);
        ModelAndView mv = new ModelAndView();

        mv.addObject("backinfor", back);
        mv.addObject("mykey", key);
        if(back.contains(key)){
            checkkey = "Success";
        }
        mv.addObject("checkkey", checkkey);
        //经过InternalResourceViewResolver对象处理后前缀加上后缀就变为了:    /jsp/team/update.jsp
        mv.setViewName("/jsp/CheckSecureRandom");//要经过Springmvc的视图解析器处理,转换成物理资源路径。
        return mv;
    }

    @RequestMapping(value = "/random.do",method = RequestMethod.GET)//请求方式设定后,只能用post的提交方式
    public ModelAndView random(@RequestParam("key") String key, @RequestParam("seed") String seed){
        String checkkey = "False";
        String back = changePassword.random(seed);
        ModelAndView mv = new ModelAndView();
        mv.addObject("backinfor", back);
        mv.addObject("mykey", key);
        if(back.contains(key)){
            checkkey = "Success";
        }
        mv.addObject("checkkey", checkkey);
        //经过InternalResourceViewResolver对象处理后前缀加上后缀就变为了:    /jsp/team/update.jsp
        mv.setViewName("/jsp/CheckRandom");//要经过Springmvc的视图解析器处理,转换成物理资源路径。
        return mv;
    }
}

        Then it is necessary to write the corresponding interface. The jsp code is used here. Index.jsp is created in the webapp and the code is added:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>
<h1>${team}</h1>
<form action="/seurerandom.do" method="get">
    <button type="submit">seurerandom提交方式</button>
</form>
<form action="/random.do" method="post">
    <button type="submit">random提交方式</button>
</form>
</body>
</html>

        Then create a jsp folder under webapp, and create two jsp files in it, namely CheckRandom.jsp and CheckSecureRandom.jsp, with the same content:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>Check Random</title>
    <style type="text/css">
      html, body {
        height: 100%;
        overflow: auto;
      }
      body {
        padding: 0;
        margin: 0;
      }
    </style>
  </head>
  <body>
    <div style="width:100%;text-align:center"></div>
    <center>
      <br>
      <br>
      <br>
      <h1>${backinfor}</h1>
      <h1>user post key:${mykey}</h1>
      <h1>check key:${checkkey}</h1>
      <br>
</center>
</body>
</html>

        Then you can run the project through tomcat to access:

Test generating random numbers with seeds:

        We know that when the seed is not set, random generates seeds through time, and SecureRandom generates seeds through system files and system information. Then, is SecureRandom safe when the seeds are set:

package org.example;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;


public class Main {


    public static String scramble(Random random, String inputstring){
        char a[] = inputstring.toCharArray();
        for(int i=0; i<a.length; i++){
            int j = random.nextInt(a.length);
            char temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        return new String(a);
    }

    public static String seurescramble(SecureRandom random, String inputstring){
        char a[] = inputstring.toCharArray();
        for(int i=0; i<a.length; i++){
            int j = random.nextInt(a.length);
            char temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        return new String(a);
    }

    public static String seurerandom(String seed) {
        try {
            String username = "admin";
            SecureRandom secRandom = new SecureRandom();

            if(!seed.equals("")) {
                Long aLong = Long.valueOf(seed).longValue();
                secRandom.setSeed(aLong);
            }
            MessageDigest md = MessageDigest.getInstance("MD5");// 生成一个MD5加密计算摘要
            md.update(username.getBytes());// 计算md5函数
            String hashedPwd = new BigInteger(1, md.digest()).toString(16);// 16是表示转换为16进制数
            System.out.print("admin MD5:" + hashedPwd+ "\n");
            String string1 = seurescramble(secRandom, hashedPwd);
            System.out.print(string1+ "\n");
            String string2 = seurescramble(secRandom, string1);
            System.out.print(string2+ "\n");

            return string2;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String random(String seed) {
        try {
            String username = "admin";
            Random random = new Random();
            if(!seed.equals("")) {
                Long aLong = Long.valueOf(seed).longValue();
                random.setSeed(aLong);
            }
            MessageDigest md = MessageDigest.getInstance("MD5");// 生成一个MD5加密计算摘要
            md.update(username.getBytes());// 计算md5函数
            String hashedPwd = new BigInteger(1, md.digest()).toString(16);// 16是表示转换为16进制数
            System.out.print("admin MD5:" + hashedPwd + "\n");
            String string1 = scramble(random, hashedPwd);
            System.out.print(string1+ "\n");
            String string2 = scramble(random, string1);
            System.out.print(string2+ "\n");
            return string2;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        for (int i =0; i<5; i++) {
            seurerandom("11");
        }
        System.out.print("========================\n");
        for (int i =0; i<5; i++) {
            random("11");
        }
    }

}

        We set the seed to 11, and the result after local execution is:

         It can be seen that the values ​​generated by using random are all the same value, but the values ​​generated by using securerandom are different every time, but whether this random value is really random, we put the code into another system for execution:

javac -encoding utf-8 Main.java

java Main

         The comparison shows that if the seed is set, the value generated in different environments can also be predicted. Even if securerandom is used, if the developer artificially sets the seed to generate the key, etc., we can crack it;

        Here we use the web built above to simulate a password reset function. When the password is forgotten, there will be a password reset link. The link is obtained by using the user name md5 and encrypting it twice with a random number. The code is the code on the Internet, so it is better Representative, let's test whether we can predict:

        When using 11 as the seed, you can see that the result of the first encryption is 9183780aa702a33744252cf524a91aef. Compared with the one we generated before, we find that the third one is 9183780aa702a33744252cf524a91aef, so we generate some more values:

        In this way, we predict that the next one is 1caa4138793af7a449058232520fe27a, and the final generated value is 8204af23e738a15fac59a32407412a79. The following test:

         You can see that the prediction is successful. If the value generated by using random is the same every time, the test will not be done.

Summarize:

        Therefore, the analysis of the source code of random and securerandom shows that by default, that is, random is not safe when the seed is not set, because the seed generation is based on the millisecond time of the system as the seed, so the attacker only needs to use a local time as the seed or The random number security of the system can be broken according to the random number blasting seed, but the time cost is a little higher, but it can be cracked theoretically, but in the case of securerandom, if the seed is not set, it is difficult to generate the seed based on the system information and file name. Predictively, the generated random numbers are more secure.

        However, if the developer sets the seed manually, the security will be greatly reduced. As long as the seed is obtained, the attacker can generate a list of random numbers based on the seed, and then predict what the next random number will be, thereby breaking the random number authentication.

        Therefore, use securerandom to generate random numbers, and do not manually set the seed. If you use securerandom under linux, thread waiting occurs because /dev/random is used as the source of entropy, but the server generates fewer keyboard operations and other operations, which may be caused by too little content. It can be manually set to /dev/urandom to prevent waiting.

Guess you like

Origin blog.csdn.net/GalaxySpaceX/article/details/131961017