Bagaimana cara membuat string alpha-numeric acak?

1741

Saya telah mencari algoritma Java sederhana untuk menghasilkan string alfa-numerik pseudo-acak. Dalam situasi saya ini akan digunakan sebagai pengidentifikasi sesi / kunci unik yang "cenderung" unik dari 500K+generasi ke generasi (kebutuhan saya tidak benar-benar membutuhkan sesuatu yang jauh lebih canggih).

Idealnya, saya dapat menentukan panjang tergantung pada kebutuhan keunikan saya. Misalnya, string yang dihasilkan dengan panjang 12 mungkin terlihat seperti "AEYGF7K0DM1X".

Todd
sumber
150
Waspadai paradoks ulang tahun .
pablosaraiva
58
Bahkan dengan mempertimbangkan paradoks ulang tahun, jika Anda menggunakan 12 karakter alfanumerik (total 62), Anda masih membutuhkan lebih dari 34 miliar string untuk mencapai paradoks tersebut. Dan paradoks ulang tahun tidak menjamin tabrakan, hanya mengatakan itu lebih dari 50% peluang.
NullUserException
4
@NullUserException Kesempatan sukses 50% (per coba) sangat tinggi: bahkan dengan 10 upaya, tingkat keberhasilan adalah 0,999. Dengan itu dan fakta bahwa Anda dapat mencoba BANYAK dalam waktu 24 jam dalam pikiran, Anda tidak perlu 34 miliar string untuk cukup yakin untuk menebak setidaknya satu dari mereka. Itulah alasan mengapa beberapa token sesi harus sangat, sangat lama.
Pijusn
16
3 kode baris tunggal ini sangat berguna, saya kira ..Long.toHexString(Double.doubleToLongBits(Math.random())); UUID.randomUUID().toString(); RandomStringUtils.randomAlphanumeric(12);
Manindar
18
@Pijusn Saya tahu ini sudah tua, tapi ... "50% peluang" dalam paradoks ulang tahun BUKAN "per coba", itu "50% kemungkinan bahwa, dari (dalam hal ini) 34 miliar string, ada di setidaknya satu duplikat ". Anda membutuhkan 1,6 sept illion - 1,6e21 - entri dalam database Anda agar ada peluang 50% per coba.
Tin Wizard

Jawaban:

1541

Algoritma

Untuk menghasilkan string acak, karakter gabungan digambar secara acak dari himpunan simbol yang dapat diterima sampai string mencapai panjang yang diinginkan.

Penerapan

Berikut adalah beberapa kode yang cukup sederhana dan sangat fleksibel untuk menghasilkan pengidentifikasi acak. Baca informasi berikut untuk catatan aplikasi penting.

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);
    }

}

Contoh penggunaan

Buat generator tidak aman untuk pengidentifikasi 8 karakter:

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

Buat generator aman untuk pengidentifikasi sesi:

RandomString session = new RandomString();

Buat generator dengan kode yang mudah dibaca untuk dicetak. String lebih panjang dari string alfanumerik penuh untuk mengimbangi penggunaan simbol lebih sedikit:

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

Gunakan sebagai pengidentifikasi sesi

Membuat pengidentifikasi sesi yang cenderung unik tidak cukup baik, atau Anda bisa menggunakan penghitung sederhana. Penyerang membajak sesi ketika pengidentifikasi yang dapat diprediksi digunakan.

Ada ketegangan antara panjang dan keamanan. Pengidentifikasi yang lebih pendek lebih mudah ditebak, karena ada lebih sedikit kemungkinan. Tetapi pengidentifikasi yang lebih lama mengkonsumsi lebih banyak penyimpanan dan bandwidth. Serangkaian simbol yang lebih besar membantu, tetapi dapat menyebabkan masalah penyandian jika pengidentifikasi dimasukkan dalam URL atau dimasukkan kembali dengan tangan.

Sumber yang mendasari keacakan, atau entropi, untuk pengidentifikasi sesi harus berasal dari generator nomor acak yang dirancang untuk kriptografi. Namun, menginisialisasi generator ini terkadang mahal atau lambat secara komputasi, sehingga harus dilakukan upaya untuk menggunakannya kembali jika memungkinkan.

Gunakan sebagai pengidentifikasi objek

Tidak semua aplikasi membutuhkan keamanan. Penugasan acak dapat menjadi cara yang efisien bagi banyak entitas untuk menghasilkan pengidentifikasi di ruang bersama tanpa koordinasi atau partisi. Koordinasi bisa lambat, terutama di lingkungan yang berkerumun atau terdistribusi, dan pemisahan ruang menyebabkan masalah ketika entitas berakhir dengan saham yang terlalu kecil atau terlalu besar.

Pengidentifikasi yang dihasilkan tanpa mengambil tindakan untuk membuatnya tidak dapat diprediksi harus dilindungi dengan cara lain jika penyerang mungkin dapat melihat dan memanipulasinya, seperti yang terjadi pada sebagian besar aplikasi web. Harus ada sistem otorisasi terpisah yang melindungi objek yang pengenalnya dapat ditebak oleh penyerang tanpa izin akses.

Kehati-hatian juga harus diambil untuk menggunakan pengidentifikasi yang cukup lama untuk membuat tabrakan tidak mungkin mengingat jumlah total pengidentifikasi yang diantisipasi. Ini disebut sebagai "paradoks ulang tahun." Probabilitas tabrakan, p , kira-kira n 2 / (2q x ), di mana n adalah jumlah pengidentifikasi yang sebenarnya dihasilkan, q adalah jumlah simbol yang berbeda dalam alfabet, dan x adalah panjang pengidentifikasi. Ini harus menjadi jumlah yang sangat kecil, seperti 2-50 atau kurang.

Mengatasi hal ini menunjukkan bahwa peluang tabrakan di antara 500k 15-karakter pengidentifikasi adalah sekitar 2-52 , yang kemungkinan lebih kecil dari kesalahan yang tidak terdeteksi dari sinar kosmik, dll.

Perbandingan dengan UUID

Menurut spesifikasinya, UUID tidak dirancang untuk tidak dapat diprediksi, dan tidak boleh digunakan sebagai pengidentifikasi sesi.

UUID dalam format standar membutuhkan banyak ruang: 36 karakter hanya untuk 122 bit entropi. (Tidak semua bit UUID "acak" dipilih secara acak.) String alfanumerik yang dipilih secara acak mengemas lebih banyak entropi hanya dalam 21 karakter.

UUID tidak fleksibel; mereka memiliki struktur dan tata letak standar. Ini adalah kebajikan utama mereka serta kelemahan utama mereka. Ketika berkolaborasi dengan pihak luar, standardisasi yang ditawarkan oleh UUID mungkin membantu. Untuk penggunaan internal murni, mereka bisa tidak efisien.

erickson
sumber
6
Jika Anda membutuhkan ruang di ruang Anda, Anda dapat .replaceAll("\\d", " ");menempel ke ujung return new BigInteger(130, random).toString(32);garis untuk melakukan pertukaran regex. Ini menggantikan semua digit dengan spasi. Bekerja sangat baik untuk saya: Saya menggunakan ini sebagai pengganti front-end Lorem Ipsum
weisjohn
4
@weisjohn Itu ide yang bagus. Anda dapat melakukan sesuatu yang mirip dengan metode kedua, dengan menghapus digit dari symbolsdan menggunakan spasi sebagai gantinya; Anda dapat mengontrol panjang "kata" rata-rata dengan mengubah jumlah spasi dalam simbol (lebih banyak kejadian untuk kata yang lebih pendek). Untuk solusi teks palsu yang benar-benar berlebihan, Anda dapat menggunakan rantai Markov!
erickson
4
Pengidentifikasi ini dipilih secara acak dari ruang ukuran tertentu. Panjangnya bisa 1 karakter. Jika Anda menginginkan panjang tetap, Anda bisa menggunakan solusi kedua, dengan SecureRandominstance yang ditetapkan ke randomvariabel.
erickson
15
Mengapa .toString (32) daripada .toString (36)?
ejain
17
@ lagi karena 32 = 2 ^ 5; setiap karakter akan mewakili tepat 5 bit, dan 130 bit dapat dibagi secara merata menjadi karakter.
erickson
817

Java memasok cara untuk melakukan ini secara langsung. Jika Anda tidak ingin tanda hubung, strip mudah dihapus. Gunakan sajauuid.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;
    }
}

Keluaran:

uuid = 2d7428a6-b58c-4008-8575-f05549f16316
Steve McLeod
sumber
33
Hati-hati bahwa solusi ini hanya menghasilkan string acak dengan karakter heksadesimal. Yang bisa baik dalam beberapa kasus.
Dave
5
Kelas UUID bermanfaat. Namun, mereka tidak sepadat pengidentifikasi yang dihasilkan oleh jawaban saya. Ini bisa menjadi masalah, misalnya, dalam URL. Tergantung kebutuhan Anda.
erickson
6
@Ruggs - Tujuannya adalah string alpha-numeric. Bagaimana memperluas output ke byte yang mungkin cocok dengan itu?
erickson
72
Menurut RFC4122 menggunakan UUID sebagai token adalah ide yang buruk: Jangan berasumsi bahwa UUID sulit ditebak; mereka tidak boleh digunakan sebagai kemampuan keamanan (pengidentifikasi yang kepemilikannya hanya memberikan akses), misalnya. Sumber angka acak yang dapat diprediksi akan memperburuk situasi. ietf.org/rfc/rfc4122.txt
Somatik
34
UUID.randomUUID().toString().replaceAll("-", "");membuat string alpha-numeric, seperti yang diminta.
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();
}
72%
sumber
61
+1, solusi paling sederhana di sini untuk menghasilkan string acak dengan panjang yang ditentukan (selain menggunakan RandomStringUtils dari Commons Lang).
Jonik
12
Pertimbangkan untuk menggunakan SecureRandomalih-alih Randomkelas. Jika kata sandi dibuat di server, ini mungkin rentan terhadap serangan waktu.
foens
8
Saya akan menambahkan huruf kecil juga: AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";dan beberapa karakter yang diizinkan lainnya.
ACV
1
Kenapa tidak dimasukkan static Random rnd = new Random();ke dalam metode?
Micro
4
@ MicroR Apakah ada alasan yang baik untuk membuat Randomobjek di setiap pemanggilan metode? Saya kira tidak.
cassiomolin
484

Jika Anda senang menggunakan kelas Apache, Anda bisa menggunakan org.apache.commons.text.RandomStringGenerator(commons-text).

Contoh:

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

Sejak commons-lang 3.6, RandomStringUtilstidak digunakan lagi.

numéro6
sumber
22
Baru saja melihat melalui kelas disebutkan dari Apache Commons Lang 3.3.1perpustakaan - dan itu hanya menggunakan java.util.Randomuntuk memberikan urutan acak, sehingga menghasilkan urutan tidak aman .
Yuriy Nakonechnyy
16
Pastikan Anda menggunakan SecureRandom saat menggunakan 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
JANGAN GUNAKAN. Ini menciptakan urutan yang tidak aman !
Patrick Favre
110

Anda dapat menggunakan perpustakaan Apache untuk ini: RandomStringUtils

RandomStringUtils.randomAlphanumeric(20).toUpperCase();
manish_s
sumber
18
@ kamil, saya melihat kode sumber untuk RandomStringUtils, dan menggunakan instance java.util.Random instantiated tanpa argumen. Dokumentasi untuk java.util.Random mengatakan menggunakan waktu sistem saat ini jika tidak ada seed yang disediakan. Ini berarti bahwa itu tidak dapat digunakan untuk pengidentifikasi / kunci sesi karena penyerang dapat dengan mudah memprediksi apa pengidentifikasi sesi yang dihasilkan pada waktu tertentu.
Insya Allah
36
@ Insya Allah: Anda (tidak perlu) overengineering sistem. Sementara saya setuju bahwa itu menggunakan waktu sebagai seed, penyerang harus memiliki akses ke data berikut untuk benar-benar mendapatkan apa yang dia inginkan 1. Waktu ke milidetik yang tepat, ketika kode diunggulkan 2. Jumlah panggilan yang telah terjadi sejauh ini 3. Atomisitas untuk panggilannya sendiri (sehingga jumlah panggilan sejauh ini sama saja) Jika penyerang Anda memiliki ketiga hal ini, maka Anda memiliki masalah yang lebih besar di tangan ...
Ajeet Ganga
3
ketergantungan gradle: compile 'commons-lang:commons-lang:2.6'
younes0
4
@ Bertemu ini tidak benar. Anda dapat memperoleh status generator angka acak dari outputnya. Jika penyerang dapat menghasilkan beberapa ribu panggilan untuk menghasilkan token API acak, penyerang akan dapat memprediksi semua token API yang akan datang.
Thomas Grainger
3
@AjeetGanga Tidak ada hubungannya dengan lebih dari rekayasa. Jika Anda ingin membuat id sesi, Anda memerlukan generator acak pseudo kriptografis. Setiap kali menggunakan waktu sebagai seed dapat diprediksi dan sangat tidak aman untuk data yang seharusnya tidak dapat diprediksi. Cukup gunakan SecureRandomdan Anda baik-baik saja.
Patrick Favre
105

Dalam satu baris:

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

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

anonim
sumber
9
Tapi hanya 6 huruf :(
Moshe Revah
2
Itu membantu saya juga tetapi hanya digit heksadesimal :(
noquery
@ Zippoxer, Anda bisa menyuarakan itu beberapa kali =)
daniel.bavrin
7
Contoh OP menunjukkan String berikut sebagai contoh AEYGF7K0DM1Xyang tidak heksadesimal. Itu membuatku khawatir betapa sering orang mengira alfanumerik dengan heksadesimal. Mereka bukan hal yang sama.
hfontanez
6
Ini jauh lebih acak daripada seharusnya diberikan panjang string karena Math.random()menghasilkan doubleantara 0 dan 1, sehingga bagian eksponen sebagian besar tidak digunakan. Gunakan random.nextLonguntuk acak longbukan hack jelek ini.
maaartinus
80

Ini mudah dicapai tanpa perpustakaan eksternal.

1. Pembuatan Data Acak Kriptografis Pseudo

Pertama, Anda membutuhkan PRNG kriptografi. Java memiliki SecureRandomuntuk itu dan biasanya menggunakan sumber entropi terbaik pada mesin (misalnya /dev/random). Baca lebih lanjut di sini.

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

Catatan: SecureRandom adalah cara paling lambat, tetapi paling aman di Jawa untuk menghasilkan byte acak. Namun saya sarankan TIDAK mempertimbangkan kinerja di sini karena biasanya tidak memiliki dampak nyata pada aplikasi Anda kecuali jika Anda harus menghasilkan jutaan token per detik.

2. Ruang yang Diperlukan dari Nilai yang Mungkin

Selanjutnya Anda harus memutuskan "seberapa unik" token Anda. Keseluruhan dan satu-satunya titik pertimbangan entropi adalah untuk memastikan bahwa sistem dapat menahan serangan brute force: ruang nilai yang mungkin harus sangat besar sehingga penyerang hanya bisa mencoba proporsi nilai yang diabaikan dalam waktu non-menggelikan 1 . Pengidentifikasi unik seperti acak UUIDmemiliki entbit 122bit (mis. 2 ^ 122 = 5.3x10 ^ 36) - peluang tabrakan adalah "* (...) karena ada peluang duplikasi satu dalam miliar, versi 103 triliun 4 UUID harus dibuat 2 ". Kami akan memilih 128 bit karena cocok persis menjadi 16 byte dan dipandang sangat memadaiuntuk menjadi unik pada dasarnya setiap, tetapi yang paling ekstrim, gunakan kasing dan Anda tidak perlu memikirkan duplikat. Berikut ini adalah tabel perbandingan entropi sederhana termasuk analisis sederhana masalah ulang tahun .

perbandingan ukuran token

Untuk persyaratan sederhana, panjang 8 atau 12 byte mungkin cukup, tetapi dengan 16 byte Anda berada di "sisi aman".

Dan pada dasarnya itu. Hal terakhir adalah memikirkan encoding sehingga dapat direpresentasikan sebagai teks yang dapat dicetak (baca, a String).

3. Binary to Text Encoding

Pengkodean umum meliputi:

  • Base64setiap karakter mengkodekan 6bit menciptakan overhead 33%. Untungnya ada implementasi standar di Java 8+ dan Android . Dengan Java yang lebih lama Anda dapat menggunakan salah satu dari banyak perpustakaan pihak ketiga . Jika Anda ingin token Anda menjadi url aman, gunakan versi RFC4648 yang aman-url (yang biasanya didukung oleh sebagian besar implementasi). Contoh pengkodean 16 byte dengan bantalan:XfJhfv3C0P6ag7y9VQxSbw==

  • Base32setiap karakter mengkodekan 5bit menciptakan overhead 40%. Ini akan menggunakan A-Zdan 2-7menjadikannya ruang yang efisien, sementara alfa-numerik tidak peka terhadap huruf besar-kecil. Tidak ada implementasi standar di JDK . Contoh penyandian 16 byte tanpa bantalan:WUPIL5DQTZGMF4D3NX5L7LNFOY

  • Base16(hex) setiap karakter mengkode 4bit yang membutuhkan 2 karakter per byte (mis. 16 byte membuat string dengan panjang 32). Oleh karena itu hex kurang efisien daripada ruang Base32tetapi aman untuk digunakan dalam kebanyakan kasus (url) karena hanya menggunakan 0-9dan Auntuk F. Misalnya encoding 16 bytes: 4fa3dd0f57cb3bf331441ed285b27735. Lihat diskusi SO tentang konversi ke hex di sini.

Pengkodean tambahan seperti Base85 dan eksotis Base122 ada dengan efisiensi ruang yang lebih baik / buruk. Anda dapat membuat encoding sendiri (yang pada dasarnya sebagian besar jawaban di utas ini lakukan) tetapi saya akan menyarankan untuk tidak melakukannya, jika Anda tidak memiliki persyaratan yang sangat spesifik. Lihat lebih banyak skema penyandian di artikel Wikipedia.

4. Ringkasan dan Contoh

  • Menggunakan SecureRandom
  • Gunakan setidaknya 16 byte (2 ^ 128) dari nilai yang mungkin
  • Enkode sesuai dengan kebutuhan Anda (biasanya hexatau base32jika Anda membutuhkannya menjadi alpha-numeric)

Jangan

  • ... gunakan pengodean brew home Anda: lebih mudah dirawat dan dibaca oleh orang lain jika mereka melihat pengodean standar apa yang Anda gunakan alih-alih aneh untuk loop yang membuat karakter sekaligus.
  • ... gunakan UUID: tidak memiliki jaminan tentang keacakan; Anda membuang 6 bit entropi dan memiliki representasi string verbose

Contoh: Hex Token Generator

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

Contoh: Generator Token Base64 (Aman 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

Contoh: Alat Java CLI

Jika Anda ingin alat cli yang siap digunakan, Anda dapat menggunakan dadu: https://github.com/patrickfav/dice

Contoh: Masalah terkait - Lindungi Id Anda Saat Ini

Jika Anda sudah memiliki id yang dapat Anda gunakan (misalnya sintetis longdi entitas Anda), tetapi tidak ingin mempublikasikan nilai internal , Anda dapat menggunakan perpustakaan ini untuk mengenkripsi dan mengaburkannya: https://github.com/patrickfav / id-mask

IdMask<Long> idMask = IdMasks.forLongIds(Config.builder(key).build());
String maskedId = idMask.mask(id);
//example: NPSBolhMyabUBdTyanrbqT8
long originalId = idMask.unmask(maskedId);
patrickf
sumber
3
Jawaban ini selesai dan berfungsi tanpa menambahkan ketergantungan. Jika Anda ingin menghindari kemungkinan tanda minus dalam output, Anda dapat mencegah negatif BigIntegermenggunakan parameter konstruktor: BigInteger(1, token)alih-alih BigInteger(token).
francoisr
Tanks @francoisr untuk petunjuk itu, saya mengedit contoh kode
Patrick Favre
import java.security.SecureRandom;dan import java.math.BigInteger;diperlukan untuk menjadikan contoh itu berhasil, tetapi itu berhasil hebat!
anothermh
Jawaban yang bagus tetapi / dev / acak adalah metode pemblokiran yang merupakan alasan mengapa ia lambat hingga tidak bisa diblokir jika entropi terlalu rendah. Metode yang lebih baik dan non-blocking adalah / dev / urandom. Ini dapat dikonfigurasi melalui <jre> /lib/security/java.security dan mengatur securerandom.source = file: / dev /./ urandom
Muzammil
@Muzammil Lihat tersesystems.com/blog/2015/12/17/… (juga ditautkan dalam jawabannya) - new SecureRandom()menggunakan/dev/urandom
Patrick Favre
42

menggunakan Dollar harus sederhana seperti:

// "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));
    }
}

ini menghasilkan sesuatu seperti itu:

DKL1SBH9UJWC
JH7P0IT21EA5
5DTI72EO6SFU
HQUMJTEBNF7Y
1HCR6SKYWGT7
dfa
sumber
apakah mungkin menggunakan SecureRandom dengan shuffle?
iwein
34

Ini dia di Jawa:

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();
  }
}

Inilah contoh menjalankan:

scala> RandomAlphaNum.gen(42)
res3: java.lang.String = uja6snx21bswf9t89s00bxssu8g6qlu16ffzqaxxoy
Apocalisp
sumber
4
Ini akan menghasilkan urutan tidak aman yaitu urutan yang dapat dengan mudah ditebak.
Yuriy Nakonechnyy
8
Semua generasi int acak ganda dipenuhi oleh desain, lambat dan tidak dapat dibaca. Gunakan Random#nextIntatau nextLong. Beralih ke SecureRandomjika perlu.
maaartinus
31

Tidak ada yang mengejutkan di sini yang menyarankannya tetapi:

import java.util.UUID

UUID.randomUUID().toString();

Mudah.

Manfaat dari hal ini adalah UUID bagus dan panjang dan dijamin hampir tidak mungkin bertabrakan.

Wikipedia memiliki penjelasan yang bagus tentang hal itu:

"... hanya setelah menghasilkan 1 miliar UUID setiap detik selama 100 tahun ke depan, kemungkinan membuat hanya satu duplikat akan menjadi sekitar 50%."

http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_duplicates

4 bit pertama adalah tipe versi dan 2 untuk varian sehingga Anda mendapatkan 122 bit secara acak. Jadi jika Anda mau, Anda bisa memotong dari ujung untuk mengurangi ukuran UUID. Ini tidak disarankan tetapi Anda masih memiliki banyak keacakan, cukup untuk catatan 500k Anda mudah.

Michael Allen
sumber
39
Seseorang memang menyarankannya, sekitar setahun sebelum Anda.
erickson
31

Solusi singkat dan mudah, tetapi hanya menggunakan huruf kecil dan angka:

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

Ukurannya sekitar 12 digit hingga basis 36 dan tidak dapat ditingkatkan lebih lanjut, dengan cara itu. Tentu saja Anda dapat menambahkan beberapa instance.

pengguna tidak diketahui
sumber
11
Hanya perlu diingat, bahwa ada kemungkinan 50% dari tanda minus di depan hasil! Jadi, membungkus r.nextLong () dalam sebuah Math.abs () dapat digunakan, jika Anda tidak ingin tanda minus: Long.toString(Math.abs(r.nextLong()), 36);
Ray Hulha
5
@ RayHulha: Jika Anda tidak ingin tanda minus, Anda harus memotongnya, karena, anehnya, Math.abs mengembalikan nilai negatif untuk Long.MIN_VALUE.
pengguna tidak diketahui
Menarik Math.ab kembali negatif. Lebih lanjut di sini: bmaurer.blogspot.co.nz/2006/10/...
Phil
1
Masalah dengan absdiselesaikan dengan menggunakan operator bitwise untuk menghapus bit yang paling signifikan. Ini akan bekerja untuk semua nilai.
Radiodef
1
@Radiodef Itulah dasarnya yang dikatakan @userunkown. Saya kira Anda juga bisa melakukannya << 1 >>> 1.
shmosel
15

Alternatif di Java 8 adalah:

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();
}
Howard Lovatt
sumber
3
Itu hebat - tetapi jika Anda ingin tetap menggunakan alfanumerik (0-9, az, AZ) lihat di sini rationaljava.com/2015/06/...
Dan
12

Menggunakan UUID tidak aman, karena sebagian UUID tidak acak sama sekali. Prosedur @erickson sangat rapi, tetapi tidak membuat string dengan panjang yang sama. Cuplikan berikut harus cukup:

/*
 * 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');
    }
}

Mengapa memilih length*5. Mari kita asumsikan kasus sederhana dari string acak dengan panjang 1, jadi satu karakter acak. Untuk mendapatkan karakter acak yang berisi semua angka 0-9 dan karakter az, kita membutuhkan angka acak antara 0 dan 35 untuk mendapatkan satu dari setiap karakter. BigIntegermenyediakan konstruktor untuk menghasilkan angka acak, didistribusikan secara merata pada rentang 0 to (2^numBits - 1). Sayangnya 35 adalah tidak ada nomor yang dapat diterima oleh 2 ^ numBits - 1. Jadi kita memiliki dua opsi: Baik dengan 2^5-1=31atau 2^6-1=63. Jika kita memilih, 2^6kita akan mendapatkan banyak angka "tidak perlu" / "lebih lama". Karena itu 2^5adalah pilihan yang lebih baik, bahkan jika kita kehilangan 4 karakter (wz). Untuk sekarang menghasilkan string dengan panjang tertentu, kita cukup menggunakan a2^(length*numBits)-1jumlah. Masalah terakhir, jika kita menginginkan string dengan panjang tertentu, acak dapat menghasilkan sejumlah kecil, sehingga panjangnya tidak terpenuhi, jadi kita harus mengisi string dengan panjang nol yang diperlukan.

Kristian Kraljic
sumber
bisa Anda jelaskan 5 yang lebih baik?
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;
}
rina
sumber
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);
    }
}

Jadi apa yang dilakukan adalah menambahkan kata sandi ke dalam string dan ... ya berfungsi dengan baik memeriksanya ... sangat sederhana. Saya menulisnya

cmpbah
sumber
Saya membiarkan diri saya melakukan beberapa modifikasi kecil. Mengapa Anda + 0sering menambahkannya ? Mengapa Anda membagi deklarasi tempat dan inisialisasi? Apa keuntungan dari indeks 1,2,3,4 daripada 0,1,2,3? Yang paling penting: Anda mengambil nilai acak, dan membandingkan dengan if-else 4 kali nilai baru, yang selalu bisa tidak cocok, tanpa mendapatkan lebih banyak keacakan. Tapi jangan ragu untuk mengembalikan.
pengguna tidak diketahui
8

Saya menemukan solusi ini yang menghasilkan string hex disandikan acak. Tes unit yang disediakan tampaknya sesuai dengan kasus penggunaan utama saya. Meskipun, ini sedikit lebih kompleks daripada beberapa jawaban lain yang diberikan.

/**
 * 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);
        }
    }
}
Todd
sumber
Saya tidak berpikir itu adil untuk gagal untuk duplikat token yang murni berdasarkan pada probabilitas.
Thom Wiggers
8
  1. Ubah karakter String sesuai kebutuhan Anda.

  2. String tidak dapat diubah. Di sini StringBuilder.appendlebih efisien daripada penggabungan string.


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();
    }
lincah
sumber
3
Ini tidak menambah apa pun yang diberikan oleh lusinan jawaban yang sebelumnya tidak mencakup. Dan membuat Randominstance baru di setiap iterasi dari loop tidak efisien.
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;
    }
}
Jameskittu
sumber
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);
        }
    }
}
Suganya
sumber
7

Tidak terlalu menyukai jawaban ini mengenai solusi "sederhana": S

Saya akan pergi untuk sederhana;), java murni, satu liner (entropi didasarkan pada panjang string acak dan set karakter yang diberikan):

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
    }
}

atau (cara lama sedikit lebih mudah dibaca)

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
    }
}

Tetapi di sisi lain Anda juga bisa menggunakan UUID yang memiliki entropi yang cukup bagus ( https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions ):

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

Semoga itu bisa membantu.

Patrik Bego
sumber
6

Anda menyebutkan "sederhana", tetapi kalau-kalau ada orang lain mencari sesuatu yang memenuhi persyaratan keamanan yang lebih ketat, Anda mungkin ingin melihat jpwgen . jpwgen dimodelkan setelah pwgen di Unix, dan sangat dapat dikonfigurasi.

michaelok
sumber
Terima kasih, perbaiki. Jadi setidaknya ada sumber dan tautannya valid. Pada sisi negatifnya, sepertinya tidak diperbarui dalam beberapa saat, meskipun saya melihat pwgen telah diperbarui cukup baru.
michaelok
4

Anda dapat menggunakan kelas UUID dengan pesan getLeastSignificantBits () untuk mendapatkan 64bit data Acak, kemudian mengonversinya menjadi nomor radix 36 (yaitu string yang terdiri dari 0-9, AZ):

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

Ini menghasilkan sebuah String hingga 13 karakter. Kami menggunakan Math.abs () untuk memastikan tidak ada tanda minus yang menyelinap masuk.

neuhaus
sumber
2
Mengapa Anda menggunakan UUID untuk mendapatkan bit acak? Kenapa tidak pakai saja random.nextLong()? Atau bahkan Double.doubleToLongBits(Math.random())?
erickson
4

Anda dapat menggunakan kode berikut, jika kata sandi Anda wajib berisi angka karakter khusus alfabet:

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();

}
Prasobh.K
sumber
4

Ini adalah kode satu baris oleh AbacusUtil

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

Acak tidak berarti itu harus unik. untuk mendapatkan string unik, menggunakan:

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

Ini dia solusi Scala:

(for (i <- 0 until rnd.nextInt(64)) yield { 
  ('0' + rnd.nextInt(64)).asInstanceOf[Char] 
}) mkString("")
Ugo Matrangolo
sumber
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;
}
duggu
sumber
2
Rangkaian string itu tidak perlu tidak efisien. Dan lekukan gila membuat kode Anda hampir tidak terbaca. Ini sama dengan ide Jamie, tetapi dieksekusi dengan buruk.
erickson
3

Saya pikir ini adalah solusi terkecil di sini, atau hampir salah satu yang terkecil:

 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;
}

Kode berfungsi dengan baik. Jika Anda menggunakan metode ini, saya sarankan Anda menggunakan lebih dari 10 karakter. Tabrakan terjadi pada 5 karakter / 30362 iterasi. Ini membutuhkan waktu 9 detik.

FileInputStream
sumber
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;
    }
Prasad Parab
sumber
1
Benar-benar bagus! Tapi itu seharusnya lengthbukan chars.lengthdi loop for: for (int i = 0; i < length; i++)
Incinerator
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);
}
Vin Ferothas
sumber
3
Ini hampir sama dengan jawaban Steve McLeod yang diberikan dua tahun sebelumnya.
erickson