Sự khác biệt giữa java.util.Random và java.security.SecureRandom


202

Nhóm của tôi đã bàn giao một số mã phía máy chủ (bằng Java) tạo mã thông báo ngẫu nhiên và tôi có một câu hỏi liên quan đến điều tương tự -

Mục đích của các mã thông báo này khá nhạy cảm - được sử dụng cho id phiên, liên kết đặt lại mật khẩu, v.v. Vì vậy, chúng cần phải được mã hóa ngẫu nhiên để tránh ai đó đoán chúng hoặc vũ phu buộc chúng khả thi. Mã thông báo là "dài" vì vậy nó dài 64 bit.

Mã hiện đang sử dụng java.util.Randomlớp để tạo các mã thông báo này. Các tài liệu cho java.util.Randomrõ ràng sau đây:

Các thực thể của java.util.Random không bảo mật bằng mật mã. Thay vào đó, hãy xem xét sử dụng SecureRandom để có được trình tạo số giả ngẫu nhiên an toàn bằng mật mã để sử dụng cho các ứng dụng nhạy cảm bảo mật.

Tuy nhiên, cách mà mã hiện đang sử dụng java.util.Randomlà thế này - Nó khởi tạo java.security.SecureRandomlớp và sau đó sử dụng SecureRandom.nextLong()phương thức để lấy hạt giống được sử dụng để khởi tạo java.util.Randomlớp. Sau đó, nó sử dụng java.util.Random.nextLong()phương thức để tạo mã thông báo.

Vì vậy, câu hỏi của tôi bây giờ - Có phải vẫn không an toàn khi biết rằng nó java.util.Randomđang được gieo bằng cách sử dụng java.security.SecureRandom? Tôi có cần sửa đổi mã để nó sử dụng java.security.SecureRandomriêng để tạo mã thông báo không?

Hiện tại hạt giống mã là Randommột lần khi khởi động


14
Sau khi được khởi tạo, đầu ra từ java.util.Random là chuỗi số xác định. Bạn có thể không muốn điều đó.
Peter tibraný

1
Liệu mã này có gieo mầm Randommột lần khi khởi động không, hay nó gieo một mã mới cho mỗi mã thông báo? Hy vọng, đây là một câu hỏi ngu ngốc, nhưng tôi nghĩ tôi sẽ kiểm tra.
Tom Anderson

8
Random chỉ có trạng thái bên trong 48 bit và sẽ lặp lại sau 2 ^ 48 cuộc gọi tới nextLong () có nghĩa là nó sẽ không tạo ra tất cả các giá trị có thể longhoặc có thể double.
Peter Lawrey

3
Có một vấn đề nghiêm trọng khác. 64 bit có nghĩa là 1,84 * 10 ^ 19 kết hợp có thể có quá ít để chống lại một cuộc tấn công tinh vi. Có những máy ngoài đó đã bẻ khóa mã DES 56 bit (yếu tố 256 ít hơn) với 90 * 10 ^ 9 phím mỗi giây trong 60 giờ. Sử dụng 128 bit hoặc hai lần dài!
Thorsten S.

Câu trả lời:


232

Việc triển khai Oracle JDK 7 tiêu chuẩn sử dụng cái được gọi là Trình tạo cộng tuyến tuyến tính để tạo ra các giá trị ngẫu nhiên java.util.Random.

Lấy từ java.util.Randommã nguồn (JDK 7u2), từ một nhận xét về phương thức protected int next(int bits), là một trong đó tạo ra các giá trị ngẫu nhiên:

Đây là một trình tạo số giả ngẫu nhiên tuyến tính, theo định nghĩa của DH Lehmer và được mô tả bởi Donald E. Knuth trong Nghệ thuật lập trình máy tính, Tập 3: Thuật toán chuyên đề , phần 3.2.1.

Dự đoán của máy phát điện tuyến tính

Hugo Krawchot đã viết một bài báo khá hay về cách những LCG này có thể được dự đoán ("Cách dự đoán các máy phát đồng quy"). Nếu bạn may mắn và thích thú, bạn vẫn có thể tìm thấy phiên bản miễn phí, có thể tải xuống trên web. Và còn nhiều nghiên cứu nữa cho thấy rõ rằng bạn không bao giờ nên sử dụng LCG cho các mục đích quan trọng về bảo mật. Điều này cũng có nghĩa là số ngẫu nhiên của bạn thể dự đoán được ngay bây giờ, điều mà bạn không muốn cho ID phiên và tương tự.

Làm thế nào để phá vỡ một máy phát công thức tuyến tính

Giả định rằng kẻ tấn công sẽ phải đợi LCG lặp lại sau một chu kỳ đầy đủ là sai. Ngay cả với một chu kỳ tối ưu (mô đun m trong mối quan hệ lặp lại của nó), rất dễ dàng dự đoán các giá trị trong tương lai trong thời gian ít hơn nhiều so với một chu kỳ đầy đủ. Rốt cuộc, đó chỉ là một loạt các phương trình mô-đun cần được giải, điều này trở nên dễ dàng ngay khi bạn quan sát đủ các giá trị đầu ra của LCG.

Bảo mật không cải thiện với hạt giống "tốt hơn". Nó đơn giản là không quan trọng nếu bạn gieo hạt giống với một giá trị ngẫu nhiên được tạo ra bởi SecureRandomhoặc thậm chí tạo ra giá trị đó bằng cách lăn một cái chết nhiều lần.

Kẻ tấn công sẽ đơn giản tính toán hạt giống từ các giá trị đầu ra được quan sát. Điều này mất ít thời gian hơn đáng kể so với 2 ^ 48 trong trường hợp java.util.Random. Những người không tin có thể thử trải nghiệm này , trong đó cho thấy bạn có thể dự đoán các Randomđầu ra trong tương lai chỉ quan sát hai giá trị đầu ra (!) Trong khoảng thời gian khoảng 2 ^ 16. Thậm chí không mất một giây trên một máy tính hiện đại để dự đoán đầu ra của các số ngẫu nhiên của bạn ngay bây giờ.

Phần kết luận

Thay thế mã hiện tại của bạn. Sử dụng SecureRandomđộc quyền. Sau đó, ít nhất bạn sẽ có một chút đảm bảo rằng kết quả sẽ khó dự đoán. Nếu bạn muốn các thuộc tính của PRNG bảo mật bằng mật mã (trong trường hợp của bạn, đó là những gì bạn muốn), thì bạn phải đi cùng SecureRandom. Thông minh về việc thay đổi cách nó được sử dụng sẽ hầu như luôn luôn dẫn đến một cái gì đó kém an toàn ...


4
Rất hữu ích, có thể bạn cũng có thể giải thích cách SecureRandom hoạt động (giống như bạn giải thích cách hoạt động của Random) ..
gresdiplipl

4
Điều đó đánh bại mục đích của SecureRandom
Azulflame

Tôi biết, học bài học đó một cách khó khăn. Nhưng một nguồn cypher khó khăn và khó tìm hoạt động tốt. Notch có thể học được điều gì đó về điều đó (anh ta mã hóa mật khẩu người dùng của mình trong tệp .lastlogin, được mã hóa bằng mã hóa cơ bản bằng cách sử dụng "passwordfile" làm khóa)
Azulflame

1
Câu hỏi thực sự ở đây: nếu java có thể tạo ra một prng an toàn hơn với một API tương tự, tại sao họ không thay thế cái bị hỏng?
Joel Coehoorn

11
@JoelCoehoorn Không phải Randomlà nó bị hỏng - nó chỉ nên được sử dụng trong các tình huống khác nhau. Tất nhiên, bạn luôn có thể sử dụng SecureRandom. Nhưng nói chung, SecureRandomchậm hơn đáng kể so với tinh khiết Random. Và có những trường hợp bạn chỉ quan tâm đến các thuộc tính thống kê tốt và hiệu suất tuyệt vời, nhưng bạn không thực sự quan tâm đến bảo mật: mô phỏng Monte-Carlo là một ví dụ điển hình. Tôi đã nhận xét về điều đó trong một câu trả lời tương tự , có thể bạn sẽ thấy nó hữu ích.
chạm nổi

72

Một ngẫu nhiên chỉ có 48 bit trong đó SecureRandom có ​​thể có tối đa 128 bit. Vì vậy, cơ hội lặp lại trong bảo mật là rất nhỏ.

Ngẫu nhiên sử dụng system clocknhư là hạt giống / hoặc để tạo ra hạt giống. Vì vậy, chúng có thể được sao chép dễ dàng nếu kẻ tấn công biết thời điểm hạt giống được tạo ra. Nhưng SecureRandom lấy Random Datatừ của bạn os(chúng có thể là khoảng giữa các lần nhấn phím, v.v. - hầu hết os thu thập những dữ liệu này lưu trữ chúng trong các tệp - /dev/random and /dev/urandom in case of linux/solaris) và sử dụng đó làm hạt giống.
Vì vậy, nếu kích thước mã thông báo nhỏ là ổn (trong trường hợp Ngẫu nhiên), bạn có thể tiếp tục sử dụng mã của mình mà không có bất kỳ thay đổi nào, vì bạn đang sử dụng SecureRandom để tạo hạt giống. Nhưng nếu bạn muốn mã thông báo lớn hơn (không thể chịu brute force attacks) thì hãy đến với SecureRandom -
Trong trường hợp ngẫu nhiên chỉ cần các 2^48nỗ lực, với các cpu tiên tiến ngày nay, có thể phá vỡ nó trong thời gian thực tế. Nhưng đối với các 2^128nỗ lực của người bảo mật sẽ được yêu cầu, sẽ mất nhiều năm để hòa vốn với các máy móc tiên tiến ngày nay.

Xem liên kết này để biết thêm chi tiết.
EDIT
Sau khi đọc các liên kết được cung cấp bởi @emboss, rõ ràng rằng hạt giống, tuy nhiên có thể ngẫu nhiên, không nên được sử dụng với java.util.Random. Rất dễ dàng để tính toán hạt giống bằng cách quan sát đầu ra.

Truy cập SecureRandom - Sử dụng PRNG gốc (như được đưa ra trong liên kết ở trên) vì nó nhận các giá trị ngẫu nhiên từ /dev/randomtệp cho mỗi cuộc gọi đếnnextBytes(). Bằng cách này, kẻ tấn công quan sát đầu ra sẽ không thể làm ra bất cứ điều gì trừ khi anh ta đang kiểm soát các nội dung của /dev/randomtập tin (mà là rất khó xảy ra)
Các sha1 PRNG thuật toán tính toán hạt giống duy nhất một lần và nếu VM của bạn đang chạy trong nhiều tháng sử dụng cùng một hạt giống, nó có thể bị bẻ khóa bởi một kẻ tấn công đang thụ động quan sát đầu ra.

LƯU Ý - Nếu bạn đang gọi nextBytes()nhanh hơn os của bạn có thể ghi các byte ngẫu nhiên (entropy) vào /dev/random, bạn có thể gặp rắc rối khi sử dụng NATIVE PRNG . Trong trường hợp đó, hãy sử dụng phiên bản SHA1 PRNG của SecureRandom và cứ sau vài phút (hoặc một khoảng thời gian), hãy chọn phiên bản này với giá trị từnextBytes()của một ví dụ PRIVE NATIVE của SecureRandom. Chạy hai thứ này song song sẽ đảm bảo rằng bạn đang gieo hạt thường xuyên với các giá trị ngẫu nhiên thực sự, trong khi cũng không làm cạn kiệt entropy thu được của Hệ điều hành.


Nó đòi hỏi ít hơn 2 ^ 48 để dự đoán a Random, OP hoàn toàn không nên sử dụng Random.
chạm nổi

@emboss: Tôi đang nói về bruteforce.
Ashwin

1
Hãy cẩn thận với Linux: nó có thể đạt đến sự cạn kiệt entropy (nhiều VM hơn là với phần cứng)! Nhìn vào /proc/sys/kernel/random/entropy_availvà kiểm tra với một số chủ đề mà không phải chờ đợi quá lâu khi đọc tiếp/dev/random
Yves Martin

2
Lưu ý rằng Oracle JRE (ít nhất 1.7) hoạt động với / dev / urandom theo mặc định và không / dev / ngẫu nhiên để hậu tố của câu trả lời của bạn không còn đúng nữa. để xác minh kiểm tra $ JAVA_HOME / lib / security / java.securance cho thuộc tính securandom.source
Boaz

1
Tệp java.security của chúng tôi có seckerandom.source = file: / dev / urandom thay vì tệp: /// dev / urandom (hai dấu gạch chéo sau dấu hai chấm cho giao thức tệp, sau đó thêm một dấu gạch chéo cho hệ thống tập tin gốc), khiến nó bị rơi trở lại to / dev / ngẫu nhiên, gây ra vấn đề với cạn kiệt entropy pool. Không thể chỉnh sửa nó, vì vậy phải đặt thuộc tính hệ thống java.security.egd thành quyền khi khởi động ứng dụng.
maxpolk

11

Nếu bạn chạy hai lần java.util.Random.nextLong()với cùng một hạt giống, nó sẽ tạo ra cùng một số. Vì lý do bảo mật mà bạn muốn gắn bó java.security.SecureRandomvì nó ít được dự đoán hơn.

2 Lớp học tương tự, tôi nghĩ rằng bạn chỉ cần thay đổi Randomđể SecureRandomcó một công cụ refactoring và hầu hết các mã hiện tại của bạn sẽ làm việc.


11
Nếu bạn lấy hai bản sao của bất kỳ PRNG nào và chọn nó với cùng một giá trị, bạn luôn nhận được các số ngẫu nhiên giống nhau, ngay cả khi sử dụng SecureRandom cũng không thay đổi điều đó. Tất cả các PRNG đều mang tính quyết định và do đó có thể dự đoán được nếu bạn biết hạt giống.
Robert

1
Có các triển khai SecureRandom khác nhau, một số là PRNG, một số thì không. Mặt khác, java.util.Random luôn là PRNG (như được định nghĩa trong Javadoc của nó).
Peter tibraný

3

Nếu thay đổi mã hiện tại của bạn là một nhiệm vụ phải chăng, tôi khuyên bạn nên sử dụng lớp SecureRandom như được đề xuất trong Javadoc.

Ngay cả khi bạn tìm thấy việc triển khai lớp Ngẫu nhiên sử dụng lớp SecureRandom trong nội bộ. bạn không nên chấp nhận điều đó:

  1. Các triển khai VM khác làm điều tương tự.
  2. Việc triển khai lớp Ngẫu nhiên trong các phiên bản tương lai của JDK vẫn sử dụng lớp SecureRandom

Vì vậy, đó là lựa chọn tốt hơn để làm theo đề xuất tài liệu và truy cập trực tiếp với SecureRandom.


Tôi không tin rằng câu hỏi ban đầu nói rằng việc java.util.Randomtriển khai được sử dụng SecureRandomtrong nội bộ, nó nói rằng mã của họ sử dụng SecureRandomđể gieo mầm Random. Tuy nhiên, tôi đồng ý với cả hai câu trả lời cho đến nay; tốt nhất nên sử dụng SecureRandomđể tránh một giải pháp xác định rõ ràng.
Palpatim

2

Việc thực hiện tham chiếu hiện tại của java.util.Random.nextLong()làm cho hai cuộc gọi đến các phương pháp next(int)trực tiếp cho thấy 32 bit của hạt giống hiện tại:

protected int next(int bits) {
    long nextseed;
    // calculate next seed: ...
    // and store it in the private "seed" field.
    return (int)(nextseed >>> (48 - bits));
}

public long nextLong() {
    // it's okay that the bottom word remains signed.
    return ((long)(next(32)) << 32) + next(32);
}

32 bit trên của kết quả nextLong()là các bit của hạt giống tại thời điểm đó. Vì chiều rộng của hạt giống là 48 bit (theo javadoc), nó đủ * để lặp lại trong 16 bit còn lại (chỉ 65,536 lần thử) để xác định hạt giống tạo ra 32 bit thứ hai.

Khi hạt giống được biết đến, tất cả các mã thông báo sau có thể dễ dàng được tính toán.

Sử dụng đầu ra nextLong()trực tiếp, một phần bí mật của PNG đến một mức độ mà toàn bộ bí mật có thể được tính toán với rất ít hiệu ứng. Nguy hiểm!

* Có một số nỗ lực cần thiết nếu 32 bit thứ hai âm, nhưng người ta có thể tìm ra điều đó.


Chính xác. Xem cách nhanh chóng bẻ khóa java.util.random tại jazzy.id.au/default/2010/09/20/ triệt !
trong

2

Hạt giống là vô nghĩa. Một máy phát ngẫu nhiên tốt khác nhau trong số nguyên thủy được chọn. Mỗi trình tạo ngẫu nhiên bắt đầu từ một số và lặp qua một 'vòng'. Có nghĩa là, bạn đến từ một số tiếp theo, với giá trị nội bộ cũ. Nhưng sau một thời gian bạn lại bắt đầu lại và bắt đầu lại từ đầu. Vì vậy, bạn chạy chu kỳ. (giá trị trả về từ một trình tạo ngẫu nhiên không phải là giá trị bên trong)

Nếu bạn sử dụng số nguyên tố để tạo vòng, tất cả các số trong vòng đó sẽ được chọn, trước khi bạn hoàn thành một chu kỳ đầy đủ thông qua tất cả các số có thể. Nếu bạn lấy số không nguyên tố, không phải tất cả các số đều được chọn và bạn có chu kỳ ngắn hơn.

Số nguyên tố cao hơn có nghĩa là, chu kỳ dài hơn, trước khi bạn quay lại phần tử đầu tiên một lần nữa. Vì vậy, trình tạo ngẫu nhiên an toàn chỉ có một chu kỳ dài hơn, trước khi bắt đầu lại, đó là lý do tại sao nó an toàn hơn. Bạn không thể dự đoán việc tạo số dễ dàng như với các chu kỳ ngắn hơn.

Với những từ khác: Bạn phải thay thế tất cả.


0

Tôi sẽ cố gắng sử dụng các từ rất cơ bản để bạn có thể dễ dàng hiểu được sự khác biệt giữa Random và SecureRandom và tầm quan trọng của Lớp SecureRandom.

Bao giờ tự hỏi làm thế nào OTP (mật khẩu một lần) được tạo ra? Để tạo OTP, chúng tôi cũng sử dụng lớp Random và SecureRandom. Bây giờ để làm cho OTP của bạn mạnh, SecureRandom tốt hơn vì phải mất 2 ^ 128 lần thử, để bẻ khóa OTP gần như không thể bằng máy hiện tại nhưng nếu sử dụng Lớp ngẫu nhiên thì OTP của bạn có thể bị bẻ khóa bởi ai đó có thể làm hại dữ liệu của bạn Chỉ cần 2 ^ 48 thử, để crack.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.