Làm thế nào để tạo một chuỗi alpha-số ngẫu nhiên?


1741

Tôi đã tìm kiếm một thuật toán Java đơn giản để tạo ra một chuỗi alpha-số giả ngẫu nhiên. Trong tình huống của tôi, nó sẽ được sử dụng như một mã định danh phiên / khóa duy nhất "có khả năng" là duy nhất qua 500K+thế hệ (nhu cầu của tôi không thực sự đòi hỏi bất cứ điều gì phức tạp hơn nhiều).

Lý tưởng nhất, tôi sẽ có thể chỉ định độ dài tùy thuộc vào nhu cầu duy nhất của tôi. Ví dụ: một chuỗi được tạo có độ dài 12 có thể trông giống như vậy "AEYGF7K0DM1X".



58
Ngay cả khi xem xét nghịch lý sinh nhật, nếu bạn sử dụng 12 ký tự chữ và số (tổng cộng 62), bạn vẫn sẽ cần hơn 34 tỷ chuỗi để đạt được nghịch lý. Và nghịch lý sinh nhật không đảm bảo cho bất kỳ va chạm nào, nó chỉ nói rằng nó có hơn 50% cơ hội.
NullUserException

4
@NullUserException 50% cơ hội thành công (mỗi lần thử) rất cao: ngay cả với 10 lần thử, tỷ lệ thành công là 0,999. Với điều đó và thực tế là bạn có thể thử RẤT NHIỀU trong khoảng thời gian 24 giờ, bạn không cần 34 tỷ chuỗi để chắc chắn đoán được ít nhất một trong số chúng. Đó là lý do tại sao một số mã thông báo phiên nên thực sự, thực sự dài.
Pijusn

16
Tôi đoán 3 mã dòng đơn này rất hữu ích ..Long.toHexString(Double.doubleToLongBits(Math.random())); UUID.randomUUID().toString(); RandomStringUtils.randomAlphanumeric(12);
Manindar

18
@Pijusn Tôi biết điều này đã cũ, nhưng ... "50% cơ hội" trong nghịch lý sinh nhật KHÔNG phải là "mỗi lần thử", đó là "50% cơ hội, trong số (trong trường hợp này) 34 tỷ chuỗi, tồn tại ở ít nhất một cặp trùng lặp ". Bạn sẽ cần 1,6 tháng chín illion - 1.6e21 - mục trong cơ sở dữ liệu của bạn để cho có được một cơ hội 50% mỗi thử.
Tin phù thủy

Câu trả lời:


1541

Thuật toán

Để tạo một chuỗi ngẫu nhiên, nối các ký tự được vẽ ngẫu nhiên từ tập hợp các ký hiệu được chấp nhận cho đến khi chuỗi đạt đến độ dài mong muốn.

Thực hiện

Đây là một số mã khá đơn giản và rất linh hoạt để tạo các định danh ngẫu nhiên. Đọc thông tin sau đây cho các ghi chú ứng dụng quan trọng.

public class RandomString {

    /**
     * Generate a random string.
     */
    public String nextString() {
        for (int idx = 0; idx < buf.length; ++idx)
            buf[idx] = symbols[random.nextInt(symbols.length)];
        return new String(buf);
    }

    public static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    public static final String lower = upper.toLowerCase(Locale.ROOT);

    public static final String digits = "0123456789";

    public static final String alphanum = upper + lower + digits;

    private final Random random;

    private final char[] symbols;

    private final char[] buf;

    public RandomString(int length, Random random, String symbols) {
        if (length < 1) throw new IllegalArgumentException();
        if (symbols.length() < 2) throw new IllegalArgumentException();
        this.random = Objects.requireNonNull(random);
        this.symbols = symbols.toCharArray();
        this.buf = new char[length];
    }

    /**
     * Create an alphanumeric string generator.
     */
    public RandomString(int length, Random random) {
        this(length, random, alphanum);
    }

    /**
     * Create an alphanumeric strings from a secure generator.
     */
    public RandomString(int length) {
        this(length, new SecureRandom());
    }

    /**
     * Create session identifiers.
     */
    public RandomString() {
        this(21);
    }

}

Ví dụ sử dụng

Tạo trình tạo không an toàn cho số nhận dạng 8 ký tự:

RandomString gen = new RandomString(8, ThreadLocalRandom.current());

Tạo một trình tạo an toàn cho các định danh phiên:

RandomString session = new RandomString();

Tạo một trình tạo với các mã dễ đọc để in. Các chuỗi dài hơn các chuỗi chữ và số đầy đủ để bù cho việc sử dụng ít ký hiệu hơn:

String easy = RandomString.digits + "ACEFGHJKLMNPQRUVWXYabcdefhijkprstuvwx";
RandomString tickets = new RandomString(23, new SecureRandom(), easy);

Sử dụng làm định danh phiên

Tạo các mã định danh phiên có khả năng là duy nhất là không đủ hoặc bạn chỉ có thể sử dụng một bộ đếm đơn giản. Kẻ tấn công chiếm quyền điều khiển phiên khi định danh dự đoán được sử dụng.

Có sự căng thẳng giữa chiều dài và an ninh. Các định danh ngắn hơn sẽ dễ đoán hơn, vì có ít khả năng hơn. Nhưng định danh dài hơn tiêu thụ nhiều lưu trữ và băng thông hơn. Một bộ ký hiệu lớn hơn sẽ giúp, nhưng có thể gây ra sự cố mã hóa nếu số nhận dạng được bao gồm trong URL hoặc nhập lại bằng tay.

Nguồn ngẫu nhiên cơ bản, hoặc entropy, cho các định danh phiên nên đến từ một trình tạo số ngẫu nhiên được thiết kế cho mật mã. Tuy nhiên, việc khởi tạo các máy phát này đôi khi có thể tốn kém về mặt tính toán hoặc chậm, vì vậy cần nỗ lực để sử dụng lại chúng khi có thể.

Sử dụng làm định danh đối tượng

Không phải mọi ứng dụng đều yêu cầu bảo mật. Việc gán ngẫu nhiên có thể là một cách hiệu quả để nhiều thực thể tạo ra các định danh trong một không gian chung mà không có bất kỳ sự phối hợp hay phân vùng nào. Phối hợp có thể chậm, đặc biệt là trong một môi trường phân cụm hoặc phân tán và việc phân tách một không gian gây ra vấn đề khi các thực thể kết thúc với các cổ phần quá nhỏ hoặc quá lớn.

Các định danh được tạo mà không thực hiện các biện pháp khiến chúng không thể đoán trước được nên được bảo vệ bằng các phương tiện khác nếu kẻ tấn công có thể xem và thao tác chúng, như xảy ra trong hầu hết các ứng dụng web. Cần có một hệ thống ủy quyền riêng biệt để bảo vệ các đối tượng mà kẻ nhận dạng có thể đoán được kẻ tấn công mà không có quyền truy cập.

Cũng cần phải chú ý sử dụng các số nhận dạng đủ dài để khiến các va chạm không thể xảy ra với tổng số lượng định danh dự đoán. Điều này được gọi là "nghịch lý sinh nhật." Xác suất va chạm, p , xấp xỉ n 2 / (2q x ), trong đó n là số lượng định danh thực sự được tạo ra, q là số ký hiệu riêng biệt trong bảng chữ cái và x là chiều dài của mã định danh. Đây phải là một số rất nhỏ, như 2 ‑50 hoặc ít hơn.

Làm việc này cho thấy khả năng va chạm giữa các số nhận dạng 500k 15 ký tự là khoảng 2 ‑52 , có lẽ ít xảy ra hơn các lỗi không được phát hiện từ các tia vũ trụ, v.v.

So sánh với UUID

Theo đặc điểm kỹ thuật của họ, UUID không được thiết kế để không thể đoán trước và không nên được sử dụng làm định danh phiên.

Các UUID ở định dạng chuẩn của chúng chiếm rất nhiều dung lượng: 36 ký tự chỉ cho 122 bit entropy. (Không phải tất cả các bit của UUID "ngẫu nhiên" đều được chọn ngẫu nhiên.) Một chuỗi ký tự chữ và số được chọn ngẫu nhiên có nhiều entropy hơn chỉ trong 21 ký tự.

UUID không linh hoạt; họ có một cấu trúc và bố cục tiêu chuẩn. Đây là đức tính chính của họ cũng như điểm yếu chính của họ. Khi hợp tác với một bên ngoài, tiêu chuẩn hóa được cung cấp bởi UUID có thể hữu ích. Để sử dụng hoàn toàn nội bộ, chúng có thể không hiệu quả.


6
Nếu bạn cần khoảng trắng trong bạn, bạn có thể giải quyết .replaceAll("\\d", " ");vào cuối return new BigInteger(130, random).toString(32);dòng để thực hiện trao đổi regex. Nó thay thế tất cả các chữ số bằng dấu cách. Hoạt động rất tốt cho tôi: Tôi đang sử dụng cái này để thay thế cho Lorem Ipsum mặt trước
weisjohn

4
@weisjohn Đó là một ý tưởng tốt. Bạn có thể làm một cái gì đó tương tự với phương thức thứ hai, bằng cách loại bỏ các chữ số khỏi symbolsvà sử dụng một khoảng trắng thay thế; bạn có thể kiểm soát độ dài "từ" trung bình bằng cách thay đổi số lượng khoảng trắng trong các ký hiệu (nhiều lần xuất hiện hơn cho các từ ngắn hơn). Đối với một giải pháp văn bản giả mạo thực sự vượt trội, bạn có thể sử dụng chuỗi Markov!
erickson

4
Các định danh này được chọn ngẫu nhiên từ không gian có kích thước nhất định. Chúng có thể dài 1 ký tự. Nếu bạn muốn có độ dài cố định, bạn có thể sử dụng giải pháp thứ hai, với một SecureRandomthể hiện được gán cho randombiến.
erickson

15
Tại sao .toString (32) chứ không phải .toString (36)?
ejain

17
@ bắt đầu vì 32 = 2 ^ 5; mỗi ký tự sẽ đại diện chính xác 5 bit và 130 bit có thể được chia đều thành các ký tự.
erickson

817

Java cung cấp một cách để làm điều này trực tiếp. Nếu bạn không muốn dấu gạch ngang, chúng rất dễ thoát ra. Chỉ dùnguuid.replace("-", "")

import java.util.UUID;

public class randomStringGenerator {
    public static void main(String[] args) {
        System.out.println(generateString());
    }

    public static String generateString() {
        String uuid = UUID.randomUUID().toString();
        return "uuid = " + uuid;
    }
}

Đầu ra:

uuid = 2d7428a6-b58c-4008-8575-f05549f16316

33
Coi chừng giải pháp này chỉ tạo ra một chuỗi ngẫu nhiên với các ký tự thập lục phân. Mà có thể tốt trong một số trường hợp.
Dave

5
Lớp UUID là hữu ích. Tuy nhiên, chúng không nhỏ gọn như các định danh được tạo ra bởi câu trả lời của tôi. Đây có thể là một vấn đề, ví dụ, trong các URL. Phụ thuộc vào nhu cầu của bạn.
erickson

6
@Ruggs - Mục tiêu là các chuỗi alpha-số. Làm thế nào để mở rộng đầu ra cho bất kỳ byte nào có thể phù hợp với điều đó?
erickson

72
Theo RFC4122 sử dụng mã thông báo của UUID là một ý tưởng tồi: Đừng cho rằng UUID khó đoán; chúng không nên được sử dụng làm khả năng bảo mật (ví dụ như số nhận dạng sở hữu chỉ cấp quyền truy cập). Một nguồn số ngẫu nhiên dự đoán sẽ làm trầm trọng thêm tình hình. ietf.org/rfc/rfc4122.txt
Somatik

34
UUID.randomUUID().toString().replaceAll("-", "");làm cho chuỗi alpha-số, theo yêu cầu.
Numid

546
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static SecureRandom rnd = new SecureRandom();

String randomString( int len ){
   StringBuilder sb = new StringBuilder( len );
   for( int i = 0; i < len; i++ ) 
      sb.append( AB.charAt( rnd.nextInt(AB.length()) ) );
   return sb.toString();
}

61
+1, giải pháp đơn giản nhất ở đây để tạo một chuỗi ngẫu nhiên có độ dài được chỉ định (ngoài việc sử dụng RandomStringUtils từ Commons Lang).
Jonik

12
Cân nhắc sử dụng SecureRandomthay vì Randomlớp học. Nếu mật khẩu được tạo trên máy chủ, nó có thể dễ bị tấn công theo thời gian.
phép

8
Tôi cũng sẽ thêm chữ thường: AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";và một số ký tự được phép khác.
ACV

1
Tại sao không đặt static Random rnd = new Random();bên trong phương pháp?
Micro

4
@MicroR Có lý do chính đáng để tạo Randomđối tượng trong mỗi lần gọi phương thức không? Tôi không nghĩ vậy.
cassiomolin

484

Nếu bạn hài lòng khi sử dụng các lớp Apache, bạn có thể sử dụng org.apache.commons.text.RandomStringGenerator (commons-text).

Thí dụ:

RandomStringGenerator randomStringGenerator =
        new RandomStringGenerator.Builder()
                .withinRange('0', 'z')
                .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS)
                .build();
randomStringGenerator.generate(12); // toUpperCase() if you want

Vì commons-lang 3.6, RandomStringUtilskhông được dùng nữa.


22
Vừa nhìn qua lớp nêu trên của Apache Commons Lang 3.3.1thư viện - và nó chỉ được sử dụng java.util.Randomđể cung cấp các chuỗi ngẫu nhiên, vì vậy nó được sản xuất chuỗi không an toàn .
Yuriy Nakonechnyy

16
Hãy chắc chắn rằng bạn sử dụng SecureRandom khi sử dụng RandomStringUtils:public static java.lang.String random(int count, int start, int end, boolean letters, boolean numbers, @Nullable char[] chars, java.util.Random random)
Ruslans Uralovs 3/03/2015

KHÔNG ĐƯỢC DÙNG. Điều này tạo ra các chuỗi không an toàn !
Patrick Favre

110

Bạn có thể sử dụng thư viện Apache cho việc này: RandomStringUtils

RandomStringUtils.randomAlphanumeric(20).toUpperCase();

18
@kamil, tôi đã xem mã nguồn của RandomStringUtils và nó sử dụng một thể hiện của java.util.Random ngay lập tức mà không cần đối số. Tài liệu cho java.util.Random cho biết họ sử dụng thời gian hệ thống hiện tại nếu không có hạt giống nào được cung cấp. Điều này có nghĩa là nó không thể được sử dụng cho các khóa / mã định danh phiên vì kẻ tấn công có thể dễ dàng dự đoán định danh phiên được tạo là gì tại bất kỳ thời điểm nào.
Inshallah

36
@Inshallah: Bạn (không cần thiết) áp đảo hệ thống. Mặc dù tôi đồng ý rằng nó sử dụng thời gian làm hạt giống, kẻ tấn công phải có quyền truy cập vào dữ liệu để thực sự có được những gì anh ta muốn 1. Thời gian đến mili giây chính xác, khi mã được gieo hạt 2. Số lượng cuộc gọi đã xảy ra cho đến nay 3. Tính nguyên tử cho cuộc gọi của chính anh ta (vì vậy số lượng cuộc gọi rất xa nhau) Nếu kẻ tấn công của bạn có tất cả ba điều này, thì bạn có vấn đề lớn hơn nhiều ...
Ajeet Ganga

3
phụ thuộc lớp: compile 'commons-lang:commons-lang:2.6'
younes0

4
@Ajeet điều này không đúng. Bạn có thể lấy trạng thái của trình tạo số ngẫu nhiên từ đầu ra của nó. Nếu kẻ tấn công có thể tạo ra vài nghìn cuộc gọi để tạo mã thông báo API ngẫu nhiên, kẻ tấn công sẽ có thể dự đoán tất cả các mã thông báo API trong tương lai.
Thomas Grainger

3
@AjeetGanga Không có gì để làm với kỹ thuật. Nếu bạn muốn tạo id phiên, bạn cần một trình tạo ngẫu nhiên giả mã hóa. Mỗi prng sử dụng thời gian làm hạt giống là có thể dự đoán được và rất không an toàn cho dữ liệu nên không thể đoán trước. Chỉ cần sử dụng SecureRandomvà bạn là tốt.
Patrick Favre

105

Trong một dòng:

Long.toHexString(Double.doubleToLongBits(Math.random()));

http://mynotes.wordpress.com/2009/07/23/java-generating-random-opes/


9
Nhưng chỉ có 6 chữ cái :(
Moshe Revah

2
Nó cũng giúp tôi nhưng chỉ có các chữ số thập lục phân :(
noquery

@Zippoxer, bạn có thể concat vài lần =)
daniel.bavrin

7
Ví dụ của OP cho thấy Chuỗi sau đây là một ví dụ AEYGF7K0DM1Xkhông phải là thập lục phân. Nó làm tôi lo lắng về việc mọi người thường nhầm chữ và số với hệ thập lục phân. Chúng không giống nhau.
hfontanez

6
Điều này ít ngẫu nhiên hơn nhiều so với độ dài chuỗi vì nó Math.random()tạo ra doubletừ 0 đến 1, vì vậy phần số mũ chủ yếu không được sử dụng. Sử dụng random.nextLongngẫu nhiên longthay vì hack xấu xí này.
maaartinus

80

Điều này có thể dễ dàng đạt được mà không cần bất kỳ thư viện bên ngoài.

1. Tạo dữ liệu giả ngẫu nhiên bằng mật mã

Đầu tiên bạn cần một PRNG mật mã. Java có SecureRandomđiều đó và thường sử dụng nguồn entropy tốt nhất trên máy (ví dụ /dev/random). Đọc thêm tại đây.

SecureRandom rnd = new SecureRandom();
byte[] token = new byte[byteLength];
rnd.nextBytes(token);

Ghi chú: SecureRandom là cách chậm nhất, nhưng an toàn nhất trong Java khi tạo các byte ngẫu nhiên. Tuy nhiên, tôi khuyên bạn KHÔNG nên xem xét hiệu suất ở đây vì nó thường không có tác động thực sự đến ứng dụng của bạn trừ khi bạn phải tạo ra hàng triệu mã thông báo mỗi giây.

2. Không gian cần thiết của các giá trị có thể

Tiếp theo, bạn phải quyết định "mức độ độc đáo" của mã thông báo của bạn cần phải như thế nào. Điểm toàn diện và duy nhất của việc xem xét entropy là đảm bảo rằng hệ thống có thể chống lại các cuộc tấn công vũ phu: không gian của các giá trị có thể phải lớn đến mức bất kỳ kẻ tấn công nào cũng chỉ có thể thử một tỷ lệ không đáng kể của các giá trị trong thời gian không lố bịch 1 . Các định danh duy nhất như ngẫu nhiên UUIDcó 122 bit entropy (nghĩa là 2 ^ 122 = 5,3x10 ^ 36) - cơ hội va chạm là "* (...) để có một cơ hội nhân đôi trong một tỷ, phiên bản 103 nghìn tỷ 4 UUID phải được tạo 2 ". Chúng tôi sẽ chọn 128 bit vì nó phù hợp chính xác với 16 byte và được xem là đủ caovề cơ bản là duy nhất cho mọi trường hợp, nhưng cực kỳ, các trường hợp sử dụng và bạn không phải suy nghĩ về các bản sao. Dưới đây là bảng so sánh đơn giản của entropy bao gồm phân tích đơn giản về vấn đề sinh nhật .

so sánh kích thước mã thông báo

Đối với các yêu cầu đơn giản, độ dài 8 hoặc 12 byte có thể đủ, nhưng với 16 byte, bạn đang ở "phía an toàn".

Và đó là cơ bản nó. Điều cuối cùng là suy nghĩ về mã hóa để nó có thể được biểu diễn dưới dạng văn bản có thể in được (đọc, a String).

3. Mã hóa nhị phân thành văn bản

Mã hóa điển hình bao gồm:

  • Base64mỗi ký tự mã hóa 6 bit tạo ra 33% chi phí. May mắn thay, có các triển khai tiêu chuẩn trong Java 8+Android . Với Java cũ hơn, bạn có thể sử dụng bất kỳ thư viện bên thứ ba nào . Nếu bạn muốn mã thông báo của bạn là url an toàn, hãy sử dụng phiên bản RFC4648 an toàn url (thường được hỗ trợ bởi hầu hết các triển khai). Ví dụ mã hóa 16 byte với phần đệm:XfJhfv3C0P6ag7y9VQxSbw==

  • Base32mỗi ký tự mã hóa 5 bit tạo ra 40% chi phí. Điều này sẽ sử dụng A-Z2-7làm cho nó có hiệu quả không gian một cách hợp lý trong khi là số alpha không phân biệt chữ hoa chữ thường. Không có triển khai tiêu chuẩn trong JDK . Ví dụ mã hóa 16 byte mà không cần đệm:WUPIL5DQTZGMF4D3NX5L7LNFOY

  • Base16(hex) mỗi ký tự mã hóa 4 bit yêu cầu 2 ký tự cho mỗi byte (tức là 16 byte tạo ra một chuỗi có độ dài 32). Do đó hex là ít không gian hiệu quả hơn Base32nhưng là an toàn để sử dụng trong hầu hết các trường hợp (url) vì nó chỉ sử dụng 0-9Ađể F. Ví dụ mã hóa 16 byte : 4fa3dd0f57cb3bf331441ed285b27735. Xem một cuộc thảo luận về việc chuyển đổi sang hex ở đây.

Mã hóa bổ sung như Base85 và kỳ lạ Base122 tồn tại với hiệu quả không gian tốt hơn / tồi tệ hơn. Bạn có thể tạo mã hóa của riêng bạn (mà về cơ bản hầu hết các câu trả lời trong chuỗi này đều có) nhưng tôi sẽ khuyên bạn nên chống lại nó, nếu bạn không có yêu cầu rất cụ thể. Xem thêm các chương trình mã hóa trong bài viết Wikipedia.

4. Tóm tắt và ví dụ

  • Sử dụng SecureRandom
  • Sử dụng ít nhất 16 byte (2 ^ 128) giá trị có thể
  • Mã hóa theo yêu cầu của bạn (thường hexhoặc base32nếu bạn cần nó là số alpha)

Đừng

  • ... sử dụng mã hóa pha tại nhà của bạn: có thể duy trì tốt hơn và dễ đọc hơn cho người khác nếu họ thấy mã hóa tiêu chuẩn bạn sử dụng thay vì kỳ lạ đối với các vòng lặp tạo ra ký tự một lúc.
  • ... sử dụng UUID: nó không đảm bảo về tính ngẫu nhiên; bạn đang lãng phí 6 bit của entropy và có biểu diễn chuỗi dài

Ví dụ: Trình tạo mã thông báo Hex

public static String generateRandomHexToken(int byteLength) {
    SecureRandom secureRandom = new SecureRandom();
    byte[] token = new byte[byteLength];
    secureRandom.nextBytes(token);
    return new BigInteger(1, token).toString(16); //hex encoding
}

//generateRandomHexToken(16) -> 2189df7475e96aa3982dbeab266497cd

Ví dụ: Trình tạo mã thông báo Base64 (An toàn Url)

public static String generateRandomBase64Token(int byteLength) {
    SecureRandom secureRandom = new SecureRandom();
    byte[] token = new byte[byteLength];
    secureRandom.nextBytes(token);
    return Base64.getUrlEncoder().withoutPadding().encodeToString(token); //base64 encoding
}

//generateRandomBase64Token(16) -> EEcCCAYuUcQk7IuzdaPzrg

Ví dụ: Công cụ Java CLI

Nếu bạn muốn có một công cụ cli sẵn sàng để sử dụng, bạn có thể sử dụng xúc xắc: https://github.com/patrickfav/dice

Ví dụ: Vấn đề liên quan - Bảo vệ Id hiện tại của bạn

Nếu bạn đã có id bạn có thể sử dụng (ví dụ: tổng hợp longtrong thực thể của bạn), nhưng không muốn xuất bản giá trị nội bộ , bạn có thể sử dụng thư viện này để mã hóa nó và làm xáo trộn nó: https://github.com/patrickfav / id-mặt nạ

IdMask<Long> idMask = IdMasks.forLongIds(Config.builder(key).build());
String maskedId = idMask.mask(id);
//example: NPSBolhMyabUBdTyanrbqT8
long originalId = idMask.unmask(maskedId);

3
Câu trả lời này là đầy đủ và hoạt động mà không cần thêm bất kỳ sự phụ thuộc. Nếu bạn muốn tránh các dấu trừ có thể có trong đầu ra, bạn có thể ngăn BigIntegers âm bằng cách sử dụng tham số hàm tạo: BigInteger(1, token)thay vì BigInteger(token).
francoisr

Tanks @francoisr cho gợi ý, tôi đã chỉnh sửa ví dụ mã
Patrick Favre

import java.security.SecureRandom;import java.math.BigInteger;là cần thiết để làm cho ví dụ hoạt động, nhưng nó hoạt động rất tốt!
Anothermh

Câu trả lời tốt nhưng / dev / ngẫu nhiên là một phương thức chặn, đó là lý do nó chậm đến mức chặn nếu entropy quá thấp. Phương pháp tốt hơn và không chặn là / dev / urandom. Điều này có thể được định cấu hình qua <jre> /lib/security/java.security và đặt securerandom.source = file: / dev /./ urandom
Muzammil

@Muzammil Xem tersesystems.com/blog/2015/12/17/iêng (cũng được liên kết trong câu trả lời) - cách new SecureRandom()sử dụng/dev/urandom
Patrick Favre

42

sử dụng Dollar nên đơn giản như:

// "0123456789" + "ABCDE...Z"
String validCharacters = $('0', '9').join() + $('A', 'Z').join();

String randomString(int length) {
    return $(validCharacters).shuffle().slice(length).toString();
}

@Test
public void buildFiveRandomStrings() {
    for (int i : $(5)) {
        System.out.println(randomString(12));
    }
}

nó xuất ra một cái gì đó như thế:

DKL1SBH9UJWC
JH7P0IT21EA5
5DTI72EO6SFU
HQUMJTEBNF7Y
1HCR6SKYWGT7

có thể sử dụng SecureRandom với shuffle không?
iwein

34

Đây là Java:

import static java.lang.Math.round;
import static java.lang.Math.random;
import static java.lang.Math.pow;
import static java.lang.Math.abs;
import static java.lang.Math.min;
import static org.apache.commons.lang.StringUtils.leftPad

public class RandomAlphaNum {
  public static String gen(int length) {
    StringBuffer sb = new StringBuffer();
    for (int i = length; i > 0; i -= 12) {
      int n = min(12, abs(i));
      sb.append(leftPad(Long.toString(round(random() * pow(36, n)), 36), n, '0'));
    }
    return sb.toString();
  }
}

Đây là một mẫu chạy:

scala> RandomAlphaNum.gen(42)
res3: java.lang.String = uja6snx21bswf9t89s00bxssu8g6qlu16ffzqaxxoy

4
Điều này sẽ tạo ra các chuỗi không an toàn tức là các chuỗi có thể dễ dàng đoán ra.
Yuriy Nakonechnyy

8
Tất cả thế hệ int ngẫu nhiên bị nhiễm kép này bị phá vỡ bởi thiết kế, chậm và không thể đọc được. Sử dụng Random#nextInthoặc nextLong. Chuyển sang SecureRandomnếu cần.
maaartinus

31

Đáng ngạc nhiên không có ai ở đây đã đề nghị nó nhưng:

import java.util.UUID

UUID.randomUUID().toString();

Dễ dàng.

Lợi ích của việc này là UUID rất đẹp và lâu dài và được đảm bảo gần như không thể va chạm.

Wikipedia có một lời giải thích tốt về nó:

"... chỉ sau khi tạo 1 tỷ UUID mỗi giây trong 100 năm tiếp theo, xác suất tạo ra chỉ một bản sao sẽ là khoảng 50%."

http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_d repeatates

4 bit đầu tiên là loại phiên bản và 2 cho biến thể để bạn nhận được 122 bit ngẫu nhiên. Vì vậy, nếu bạn muốn, bạn có thể cắt ngắn từ cuối để giảm kích thước của UUID. Không khuyến khích nhưng bạn vẫn có vô số ngẫu nhiên, đủ cho hồ sơ 500k của bạn dễ dàng.


39
Ai đó đã đề nghị nó, khoảng một năm trước bạn.
erickson

31

Một giải pháp ngắn và dễ dàng, nhưng chỉ sử dụng chữ thường và số:

Random r = new java.util.Random ();
String s = Long.toString (r.nextLong () & Long.MAX_VALUE, 36);

Kích thước khoảng 12 chữ số đến cơ sở 36 và không thể được cải thiện hơn nữa, theo cách đó. Tất nhiên bạn có thể nối thêm nhiều trường hợp.


11
Chỉ cần lưu ý rằng có 50% khả năng có một dấu trừ trước kết quả! Vì vậy, gói r.nextLong () trong Math.abs () có thể được sử dụng, nếu bạn không muốn có dấu trừ: Long.toString(Math.abs(r.nextLong()), 36);
Ray Hulha

5
@RayHulha: Nếu bạn không muốn có dấu trừ, bạn nên cắt bỏ nó, bởi vì, đáng ngạc nhiên, Math.abs trả về giá trị âm cho Long.MIN_VALUE.
người dùng không xác định

Thú vị các Math.abs trở lại tiêu cực. Xem thêm tại đây: bmaurer.blogspot.co.nz/2006/10/ trên
Phil

1
Vấn đề với absđược giải quyết bằng cách sử dụng toán tử bitwise để xóa bit quan trọng nhất. Điều này sẽ làm việc cho tất cả các giá trị.
Radiodef

1
@Radiodef Đó chính là những gì @userunkown đã nói. Tôi cho rằng bạn cũng có thể làm << 1 >>> 1.
shmosel

15

Một thay thế trong Java 8 là:

static final Random random = new Random(); // Or SecureRandom
static final int startChar = (int) '!';
static final int endChar = (int) '~';

static String randomString(final int maxLength) {
  final int length = random.nextInt(maxLength + 1);
  return random.ints(length, startChar, endChar + 1)
        .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
        .toString();
}

3
Điều đó thật tuyệt - nhưng nếu bạn muốn giữ nó đúng với cả chữ và số (0-9, az, AZ), hãy xem tại đây rationaljava.com/2015/06/ Kẻ
Dan

12

Sử dụng UUID là không an toàn, bởi vì các phần của UUID hoàn toàn không ngẫu nhiên. Quy trình của @erickson rất gọn gàng, nhưng không tạo ra các chuỗi có cùng độ dài. Đoạn mã sau đây là đủ:

/*
 * The random generator used by this class to create random keys.
 * In a holder class to defer initialization until needed.
 */
private static class RandomHolder {
    static final Random random = new SecureRandom();
    public static String randomKey(int length) {
        return String.format("%"+length+"s", new BigInteger(length*5/*base 32,2^5*/, random)
            .toString(32)).replace('\u0020', '0');
    }
}

Tại sao chọn length*5. Giả sử trường hợp đơn giản của một chuỗi ngẫu nhiên có độ dài 1, do đó, một ký tự ngẫu nhiên. Để có được một ký tự ngẫu nhiên chứa tất cả các chữ số 0-9 và ký tự az, chúng ta sẽ cần một số ngẫu nhiên trong khoảng từ 0 đến 35 để có được một trong mỗi ký tự. BigIntegercung cấp một hàm tạo để tạo một số ngẫu nhiên, phân bố đồng đều trong phạm vi 0 to (2^numBits - 1). Thật không may, 35 không phải là số có thể nhận được bằng 2 ^ numBits - 1. Vì vậy, chúng tôi có hai tùy chọn: Hoặc đi với 2^5-1=31hoặc 2^6-1=63. Nếu chúng ta chọn, 2^6chúng ta sẽ nhận được rất nhiều số "không phân chia" / "dài hơn". Do đó, 2^5là lựa chọn tốt hơn, ngay cả khi chúng tôi mất 4 ký tự (wz). Để tạo một chuỗi có độ dài nhất định, chúng ta chỉ cần sử dụng một2^(length*numBits)-1con số. Vấn đề cuối cùng, nếu chúng ta muốn một chuỗi có độ dài nhất định, ngẫu nhiên có thể tạo ra một số nhỏ, do đó độ dài không được đáp ứng, vì vậy chúng ta phải đệm chuỗi đó vào các số 0 có độ dài cần thiết.


bạn có thể giải thích tốt hơn về 5?
Julian Suarez

11
public static String generateSessionKey(int length){
String alphabet = 
        new String("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); //9
int n = alphabet.length(); //10

String result = new String(); 
Random r = new Random(); //11

for (int i=0; i<length; i++) //12
    result = result + alphabet.charAt(r.nextInt(n)); //13

return result;
}

10
import java.util.Random;

public class passGen{
    //Verison 1.0
    private static final String dCase = "abcdefghijklmnopqrstuvwxyz";
    private static final String uCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final String sChar = "!@#$%^&*";
    private static final String intChar = "0123456789";
    private static Random r = new Random();
    private static String pass = "";

    public static void main (String[] args) {
        System.out.println ("Generating pass...");
        while (pass.length () != 16){
            int rPick = r.nextInt(4);
            if (rPick == 0){
                int spot = r.nextInt(25);
                pass += dCase.charAt(spot);
            } else if (rPick == 1) {
                int spot = r.nextInt (25);
                pass += uCase.charAt(spot);
            } else if (rPick == 2) {
                int spot = r.nextInt (7);
                pass += sChar.charAt(spot);
            } else if (rPick == 3){
                int spot = r.nextInt (9);
                pass += intChar.charAt (spot);
            }
        }
        System.out.println ("Generated Pass: " + pass);
    }
}

Vì vậy, những gì nó làm chỉ là thêm mật khẩu vào chuỗi và ... yeah hoạt động tốt kiểm tra nó ... rất đơn giản. tôi đã viết nó


Tôi cho phép mình thực hiện một số sửa đổi nhỏ. Tại sao bạn thêm + 0nó thường xuyên? Tại sao bạn chia khai báo tại chỗ và khởi tạo? Lợi thế của chỉ số 1,2,3,4 thay vì 0,1,2,3 là gì? Quan trọng nhất: bạn đã lấy một giá trị ngẫu nhiên và so sánh với if-other 4 lần một giá trị mới, luôn có thể không khớp, mà không đạt được nhiều tính ngẫu nhiên hơn. Nhưng hãy thoải mái để rollback.
người dùng không xác định

8

Tôi tìm thấy giải pháp này tạo ra một chuỗi mã hóa hex ngẫu nhiên. Kiểm tra đơn vị được cung cấp dường như giữ đúng trường hợp sử dụng chính của tôi. Mặc dù, nó hơi phức tạp hơn một số câu trả lời khác được cung cấp.

/**
 * Generate a random hex encoded string token of the specified length
 *  
 * @param length
 * @return random hex string
 */
public static synchronized String generateUniqueToken(Integer length){ 
    byte random[] = new byte[length];
    Random randomGenerator = new Random();
    StringBuffer buffer = new StringBuffer();

    randomGenerator.nextBytes(random);

    for (int j = 0; j < random.length; j++) {
        byte b1 = (byte) ((random[j] & 0xf0) >> 4);
        byte b2 = (byte) (random[j] & 0x0f);
        if (b1 < 10)
            buffer.append((char) ('0' + b1));
        else
            buffer.append((char) ('A' + (b1 - 10)));
        if (b2 < 10)
            buffer.append((char) ('0' + b2));
        else
            buffer.append((char) ('A' + (b2 - 10)));
    }
    return (buffer.toString());
}

@Test
public void testGenerateUniqueToken(){
    Set set = new HashSet();
    String token = null;
    int size = 16;

    /* Seems like we should be able to generate 500K tokens 
     * without a duplicate 
     */
    for (int i=0; i<500000; i++){
        token = Utility.generateUniqueToken(size);

        if (token.length() != size * 2){
            fail("Incorrect length");
        } else if (set.contains(token)) {
            fail("Duplicate token generated");
        } else{
            set.add(token);
        }
    }
}

Tôi không nghĩ là công bằng khi thất bại đối với các mã thông báo trùng lặp hoàn toàn dựa trên xác suất.
Thom Wiggers

8
  1. Thay đổi các ký tự Chuỗi theo yêu cầu của bạn.

  2. Chuỗi là bất biến. Ở đây StringBuilder.appendhiệu quả hơn so với nối chuỗi.


public static String getRandomString(int length) {
       final String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+";
       StringBuilder result = new StringBuilder();
       while(length > 0) {
           Random rand = new Random();
           result.append(characters.charAt(rand.nextInt(characters.length())));
           length--;
       }
       return result.toString();
    }

3
Điều này không thêm gì vào hàng tá câu trả lời được đưa ra trước đây không bao gồm. Và việc tạo một thể hiện mới Randomtrong mỗi lần lặp của vòng lặp là không hiệu quả.
erickson

7
import java.util.Date;
import java.util.Random;

public class RandomGenerator {

  private static Random random = new Random((new Date()).getTime());

    public static String generateRandomString(int length) {
      char[] values = {'a','b','c','d','e','f','g','h','i','j',
               'k','l','m','n','o','p','q','r','s','t',
               'u','v','w','x','y','z','0','1','2','3',
               '4','5','6','7','8','9'};

      String out = "";

      for (int i=0;i<length;i++) {
          int idx=random.nextInt(values.length);
          out += values[idx];
      }
      return out;
    }
}

7
import java.util.*;
import javax.swing.*;
public class alphanumeric{
    public static void main(String args[]){
        String nval,lenval;
        int n,len;

        nval=JOptionPane.showInputDialog("Enter number of codes you require : ");
        n=Integer.parseInt(nval);

        lenval=JOptionPane.showInputDialog("Enter code length you require : ");
        len=Integer.parseInt(lenval);

        find(n,len);

    }
    public static void find(int n,int length) {
        String str1="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuilder sb=new StringBuilder(length);
        Random r = new Random();

        System.out.println("\n\t Unique codes are \n\n");
        for(int i=0;i<n;i++){
            for(int j=0;j<length;j++){
                sb.append(str1.charAt(r.nextInt(str1.length())));
            }
            System.out.println("  "+sb.toString());
            sb.delete(0,length);
        }
    }
}

7

Không thực sự thích bất kỳ câu trả lời nào về giải pháp "đơn giản" này: S

Tôi sẽ đi đơn giản;), java thuần túy, một lớp lót (entropy dựa trên độ dài chuỗi ngẫu nhiên và bộ ký tự đã cho):

public String randomString(int length, String characterSet) {
    return IntStream.range(0, length).map(i -> new SecureRandom().nextInt(characterSet.length())).mapToObj(randomInt -> characterSet.substring(randomInt, randomInt + 1)).collect(Collectors.joining());
}

@Test
public void buildFiveRandomStrings() {
    for (int q = 0; q < 5; q++) {
        System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));//charachterSet can basically be anything
    }
}

hoặc (cách cũ dễ đọc hơn một chút)

public String randomString(int length, String characterSet) {
    StringBuilder sb = new StringBuilder(); //consider using StringBuffer if needed
    for (int i = 0; i < length; i++) {
        int randomInt = new SecureRandom().nextInt(characterSet.length());
        sb.append(characterSet.substring(randomInt, randomInt + 1));
    }
    return sb.toString();
}

@Test
public void buildFiveRandomStrings() {
    for (int q = 0; q < 5; q++) {
        System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")); //charachterSet can basically be anything
    }
}

Nhưng mặt khác, bạn cũng có thể sử dụng UUID có entropy khá tốt ( https://en.wikipedia.org/wiki/Universally_unique_identifier#Collutions ):

UUID.randomUUID().toString().replace("-", "")

Mong rằng sẽ giúp.


6

Bạn đề cập đến "đơn giản", nhưng chỉ trong trường hợp bất kỳ ai khác đang tìm kiếm thứ gì đó đáp ứng các yêu cầu bảo mật nghiêm ngặt hơn, bạn có thể muốn xem qua jpwgen . jpwgen được mô phỏng theo pwgen trong Unix và rất có thể cấu hình.


Cảm ơn, đã sửa nó. Vì vậy, ít nhất là có nguồn và liên kết là hợp lệ. Mặt khác, có vẻ như nó đã không được cập nhật trong một thời gian, mặc dù tôi thấy pwgen đã được cập nhật gần đây.
michaelok

4

Bạn có thể sử dụng lớp UUID với thông báo getLeastSignificantBits () của nó để nhận 64 bit dữ liệu Ngẫu nhiên, sau đó chuyển đổi nó thành một số 36 cơ số (tức là một chuỗi bao gồm 0-9, AZ):

Long.toString(Math.abs( UUID.randomUUID().getLeastSignificantBits(), 36));

Điều này mang lại một chuỗi dài tối đa 13 ký tự. Chúng tôi sử dụng Math.abs () để đảm bảo không có dấu trừ lén lút.


2
Tại sao trên thế giới bạn sẽ sử dụng UUID để nhận các bit ngẫu nhiên? Tại sao không chỉ sử dụng random.nextLong()? Hay thậm chí Double.doubleToLongBits(Math.random())?
erickson

4

Bạn có thể sử dụng mã sau đây, nếu mật khẩu của bạn bắt buộc có chứa các ký tự đặc biệt chữ cái:

private static final String NUMBERS = "0123456789";
private static final String UPPER_ALPHABETS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String LOWER_ALPHABETS = "abcdefghijklmnopqrstuvwxyz";
private static final String SPECIALCHARACTERS = "@#$%&*";
private static final int MINLENGTHOFPASSWORD = 8;

public static String getRandomPassword() {
    StringBuilder password = new StringBuilder();
    int j = 0;
    for (int i = 0; i < MINLENGTHOFPASSWORD; i++) {
        password.append(getRandomPasswordCharacters(j));
        j++;
        if (j == 3) {
            j = 0;
        }
    }
    return password.toString();
}

private static String getRandomPasswordCharacters(int pos) {
    Random randomNum = new Random();
    StringBuilder randomChar = new StringBuilder();
    switch (pos) {
        case 0:
            randomChar.append(NUMBERS.charAt(randomNum.nextInt(NUMBERS.length() - 1)));
            break;
        case 1:
            randomChar.append(UPPER_ALPHABETS.charAt(randomNum.nextInt(UPPER_ALPHABETS.length() - 1)));
            break;
        case 2:
            randomChar.append(SPECIALCHARACTERS.charAt(randomNum.nextInt(SPECIALCHARACTERS.length() - 1)));
            break;
        case 3:
            randomChar.append(LOWER_ALPHABETS.charAt(randomNum.nextInt(LOWER_ALPHABETS.length() - 1)));
            break;
    }
    return randomChar.toString();

}

4

Đây là mã một dòng của AbacusUtil

String.valueOf(CharStream.random('0', 'z').filter(c -> N.isLetterOrDigit(c)).limit(12).toArray())

Ngẫu nhiên không có nghĩa là nó phải là duy nhất. để có được chuỗi duy nhất, sử dụng:

N.uuid() // e.g.: "e812e749-cf4c-4959-8ee1-57829a69a80f". length is 36.
N.guid() // e.g.: "0678ce04e18945559ba82ddeccaabfcd". length is 32 without '-'

3

Đây là một giải pháp Scala:

(for (i <- 0 until rnd.nextInt(64)) yield { 
  ('0' + rnd.nextInt(64)).asInstanceOf[Char] 
}) mkString("")


3
public static String randomSeriesForThreeCharacter() {
    Random r = new Random();
    String value="";
    char random_Char ;
    for(int i=0; i<10;i++)
    { 
        random_Char = (char) (48 + r.nextInt(74));
        value=value+random_char;
    }
    return value;
}

2
Việc nối chuỗi đó là không hiệu quả. Và sự thụt lề điên rồ làm cho mã của bạn gần như không thể đọc được. Điều này giống như ý tưởng của Jamie, nhưng được thực hiện kém.
erickson

3

Tôi nghĩ rằng đây là giải pháp nhỏ nhất ở đây, hoặc gần một trong những giải pháp nhỏ nhất:

 public String generateRandomString(int length) {
    String randomString = "";

    final char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890".toCharArray();
    final SecureRandom random = new SecureRandom();
    for (int i = 0; i < length; i++) {
        randomString = randomString + chars[random.nextInt(chars.length)];
    }

    return randomString;
}

Mã này hoạt động tốt. Nếu bạn đang sử dụng phương pháp này, tôi khuyên bạn nên sử dụng hơn 10 ký tự. Va chạm xảy ra ở 5 ký tự / 30362 lần lặp. Điều này mất 9 giây.


3
public static String getRandomString(int length) {
        char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST".toCharArray();

        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            char c = chars[random.nextInt(chars.length)];
            sb.append(c);
        }
        String randomStr = sb.toString();

        return randomStr;
    }

1
Thực sự tốt đẹp! Nhưng nó nên lengththay vì chars.lengthtrong vòng lặp for: for (int i = 0; i < length; i++)
Lò đốt

2
public static String getRandomString(int length) 
{
   String randomStr = UUID.randomUUID().toString();
   while(randomStr.length() < length) {
       randomStr += UUID.randomUUID().toString();
   }
   return randomStr.substring(0, length);
}

3
Điều này khá giống với câu trả lời của Steve McLeod được đưa ra hai năm trước.
erickson
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.