PRNG yếu

Danh mục OWASP: MASVS-CRYPTO: Mật mã học

Tổng quan

Trình tạo số ngẫu nhiên giả (PRNG) là một thuật toán tạo ra các dãy số có thể dự đoán dựa trên một giá trị bắt đầu có tên là nội dung gốc. Dãy số do PRNG tạo có khoảng thuộc tính giống như một dãy số thực sự ngẫu nhiên, nhưng nhanh hơn và ít tốn kém hơn khi tạo.

Nói cách khác, PRNG có mức đảm bảo cao hơn so với các RNG yếu (ví dụ: java.math.Random) xét về sự đồng đều của quá trình phân phối entropy, vốn mô phỏng các dãy số thực sự ngẫu nhiên. Quá trình tạo số thực sự ngẫu nhiên đòi hỏi thiết bị chuyên dụng và thường nằm ngoài phạm vi phát triển thông thường. Bài viết này không đề cập đến việc tạo số thực sự ngẫu nhiên mà chỉ tập trung vào PRNG vì đây là phương pháp tiêu chuẩn được sử dụng.

Lỗ hổng PRNG yếu xảy ra khi các nhà phát triển sử dụng PRNG thông thường cho mục đích mã hoá, thay vì dùng một PRNG được bảo mật bằng mật mã (CSPRNG). CSPRNG có những yêu cầu nghiêm ngặt hơn và khi nội dung gốc không xác định, kẻ tấn công chắc chắn sẽ khó phân biệt dãy đầu ra với dãy ngẫu nhiên thực tế.

Kẻ tấn công cũng có thể đoán dãy số đã tạo khi nội dung gốc có thể dự đoán (chẳng hạn như nội dung do nhà phát triển mã hoá cứng) được dùng để khởi động PRNG hoặc CSPRNG, vì kẻ tấn công có thể đoán nội dung gốc và do đó đoán được dữ liệu đầu ra mà PRNG tạo.

Tác động

Nếu bạn dùng PRNG không được bảo mật bằng mật mã trong ngữ cảnh bảo mật như xác thực, thì kẻ tấn công có thể đoán các số được tạo ngẫu nhiên và giành quyền truy cập vào dữ liệu hoặc chức năng đặc quyền.

Giải pháp giảm thiểu

Chung

java.security.SecureRandom

Nên dùng cho mục đích bảo mật. Nếu phiên bản kernel của Linux là 5.17 trở lên hoặc cho phép việc chặn luồng, hãy đợi tích luỹ đủ entropy trước khi tạo số ngẫu nhiên (tức là sử dụng /dev/random). Để thực hiện việc này, hãy gọi getInstanceStrong():

Kotlin

val rand = SecureRandom.getInstanceStrong()

Java

SecureRandom rand = SecureRandom.getInstanceStrong();

Ngược lại, trên các phiên bản kernel của Linux trước 5.17 mà không cho phép việc chặn luồng khi tạo số ngẫu nhiên, thì hàm khởi tạo SecureRandom sẽ được gọi trực tiếp:

Kotlin

import java.security.SecureRandom

object generateRandom {
    @JvmStatic
    fun main(args: Array<String>) {
        // Create instance of SecureRandom class
        val rand = SecureRandom()

        // Generate random integers in range 0 to 999
        val rand_int = rand.nextInt(1000)

        // Use rand_int for security & authentication
    }
}

Java

import java.security.SecureRandom;

public class generateRandom {

    public static void main(String args[])
    {
        // Create instance of SecureRandom class
        SecureRandom rand = new SecureRandom();

        // Generate random integers in range 0 to 999
        int rand_int = rand.nextInt(1000);

        // Use rand_int for security & authentication
    }
}

SecureRandom lấy nội dung gốc mặc định từ /dev/urandom và tự động được dùng khi khởi tạo hoặc lấy đối tượng này. Vì vậy, bạn không cần phải đưa PRNG vào nội dung gốc một cách rõ ràng. Nhìn chung, chúng tôi không khuyến khích trường hợp sử dụng tất định của SecureRandom (đặc biệt nếu điều này dẫn đến việc cố định giá trị của hạt giống trong mã mà bất cứ ai khi biên dịch ngược ứng dụng đều có thể thấy). Các nhà phát triển muốn tạo dữ liệu đầu ra ngẫu nhiên giả có thể mô phỏng nên sử dụng các dữ liệu nguyên gốc thích hợp hơn như HMAC, HKDF, SHAKE, v.v.

java.util.Random

Tránh dùng cho mục đích bảo mật/xác thực, được phép sử dụng cho mọi mục đích khác.

Kotlin

import java.util.Random

object generateRandom {
    @JvmStatic
    fun main(args: Array<String>) {
        // Create instance of SecureRandom class
        val rand = Random()

        // Generate random integers in range 0 to 999
        val rand_int = rand.nextInt(1000)
    }
}

Java

import java.util.Random;

public class generateRandom {

    public static void main(String args[])
    {
        // Create instance of Random class
        Random rand = new Random();

        // Generate random integers in range 0 to 999
        int rand_int = rand.nextInt(1000);
    }
}

Tài nguyên