Pertanyaan Bagaimana cara menghasilkan string alfa-numerik acak?


Saya sudah mencari a sederhana Algoritma Java untuk menghasilkan string alfa-numerik pseudo-random. Dalam situasi saya, ini akan digunakan sebagai pengidentifikasi sesi / kunci unik yang "kemungkinan" akan menjadi unik di atas 500K + generasi (kebutuhan saya tidak benar-benar membutuhkan sesuatu yang jauh lebih canggih). Idealnya, saya akan bisa menentukan panjangnya tergantung pada kebutuhan keunikan saya. Misalnya, string yang dihasilkan dengan panjang 12 mungkin terlihat seperti itu "AEYGF7K0DM1X".


1458


asal


Jawaban:


Algoritma

Untuk menghasilkan string acak, gabungkan karakter yang diambil secara acak dari himpunan simbol yang dapat diterima hingga string mencapai panjang yang diinginkan.

Pelaksanaan

Berikut ini beberapa kode yang cukup sederhana dan sangat fleksibel untuk menghasilkan pengenal acak. Baca informasi selanjutnya untuk catatan aplikasi penting.

import java.security.SecureRandom;
import java.util.Locale;
import java.util.Objects;
import java.util.Random;

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 pengenal 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 lengkap untuk mengkompensasi menggunakan lebih sedikit simbol:

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

Gunakan sebagai pengenal sesi

Menghasilkan pengidentifikasi sesi yang cenderung unik tidak cukup baik, atau Anda bisa menggunakan penghitung sederhana. Para penyerang membajak sesi ketika identifier yang dapat diprediksi digunakan.

Ada ketegangan antara panjang dan keamanan. Pengidentifikasi yang lebih pendek lebih mudah ditebak, karena kemungkinannya lebih sedikit. Tetapi pengenal yang lebih lama mengkonsumsi lebih banyak penyimpanan dan bandwidth. Kumpulan simbol yang lebih besar membantu, tetapi mungkin menyebabkan masalah encoding jika pengidentifikasi dimasukkan dalam URL atau dimasukkan ulang dengan tangan.

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

Gunakan sebagai pengidentifikasi objek

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

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

Perawatan juga harus diambil untuk menggunakan pengidentifikasi yang cukup panjang untuk membuat tabrakan tidak mungkin mengingat jumlah pengidentifikasi yang diantisipasi. Ini disebut sebagai "paradoks ulang tahun." Probabilitas tabrakan,  p, kira-kira n2/ (2qx), di mana n adalah jumlah pengenal yang benar-benar dihasilkan, q adalah jumlah simbol yang berbeda dalam alfabet, dan x adalah panjang pengidentifikasi. Ini harus berupa angka yang sangat kecil, seperti 2‑50 atau kurang.

Bekerja ini menunjukkan bahwa kemungkinan tabrakan antara 500k 15-karakter pengidentifikasi adalah sekitar 2‑52, yang mungkin kurang dari kesalahan yang tidak terdeteksi dari sinar kosmik, dll.

Perbandingan dengan UUID

Menurut spesifikasi mereka, UUID tidak dirancang untuk tidak dapat diprediksi, dan jangan digunakan sebagai pengidentifikasi sesi.

UUID dalam format standar mereka membutuhkan banyak ruang: 36 karakter hanya untuk 122 bit entropi. (Tidak semua bit dari UUID "acak" dipilih secara acak.) Sebuah 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.


1396



Java menyediakan cara untuk melakukan ini secara langsung. Jika Anda tidak menginginkan setripnya, mereka mudah dilepas. Cukup gunakan uuid.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

732



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

468



Jika Anda senang menggunakan kelas Apache, Anda bisa menggunakannya 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, RandomStringUtils sudah ditinggalkan.


453



Dalam satu baris:

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

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


90



Anda dapat menggunakan pustaka Apache untuk ini: RandomStringUtils

RandomStringUtils.randomAlphanumeric(20).toUpperCase();

87



menggunakan Dolar harus sesederhana:

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

itu menghasilkan sesuatu seperti itu:

DKL1SBH9UJWC
JH7P0IT21EA5
5DTI72EO6SFU
HQUMJTEBNF7Y
1HCR6SKYWGT7

36



Ini mudah dicapai tanpa pustaka eksternal.

1. Cryptographic Pseudo Random Data Generation

Pertama Anda membutuhkan PRNG kriptografi. Java punya SecureRandom untuk itu biasanya menggunakan sumber entropi terbaik pada mesin (mis. /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 menghasilkan byte acak. Namun saya merekomendasikan TIDAK mempertimbangkan kinerja di sini karena biasanya tidak memiliki dampak nyata pada aplikasi Anda kecuali Anda harus menghasilkan jutaan token per detik.

2. Diperlukan Ruang Kemungkinan Nilai

Selanjutnya Anda harus memutuskan "seberapa unik" token Anda. Seluruh dan satu-satunya titik mempertimbangkan entropi adalah untuk memastikan bahwa sistem dapat menahan serangan brute force: ruang dari nilai-nilai yang mungkin harus begitu besar sehingga penyerang hanya bisa mencoba proporsi yang dapat diabaikan dari nilai-nilai dalam waktu yang tidak menggelikan1. Pengenal unik seperti acak UUID memiliki 122bit entropi (mis. 2 ^ 122 = 5,3x10 ^ 36) - kemungkinan tabrakan adalah "* (...) karena akan ada satu dalam satu miliar kesempatan duplikasi, 103 triliun versi 4 UUIDs harus dihasilkan2". Kami akan memilih 128 bit karena itu tepat sesuai dengan 16 byte dan dilihat sebagai sangat memadai untuk menjadi unik pada dasarnya setiap kasus, tetapi yang paling ekstrim, gunakan dan Anda tidak perlu memikirkan duplikat. Berikut ini adalah tabel perbandingan sederhana dari entropi termasuk analisis sederhana dari masalah ulang tahun.

comparison of token sizes

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

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

3. Binary to Text Encoding

Encoding yang khas termasuk:

  • Base64 setiap karakter mengkodekan 6 bit menciptakan overhead 33%. Sayangnya tidak ada implementasi standar di JDK (7 dan di bawah - ada di Android dan Java 8+). Tapi banyak perpustakaan ada yang menambah ini. Kekurangannya adalah standar itu Base64 tidak aman untuk misalnya. url dan sebagai nama file di sebagian besar sistem file yang membutuhkan enkode tambahan (mis. pengkodean url) atau Versi URL aman dari Base64 digunakan. Contoh encoding 16 byte dengan padding: XfJhfv3C0P6ag7y9VQxSbw==

  • Base32 setiap karakter mengkodekan 5bit menciptakan overhead 40%. Ini akan digunakan A-Z dan 2-7 membuatnya cukup ruang efisien sementara alpha-numeric case-insensitive. Tidak ada implementasi standar di JDK. Contoh encoding 16 byte tanpa padding: WUPIL5DQTZGMF4D3NX5L7LNFOY

  • Base16 (hex) setiap karakter mengkodekan 4bit membutuhkan 2 karakter per byte (mis. 16 byte membuat string dengan panjang 32). Oleh karena itu hex kurang efisien dari ruang Base32 tetapi aman digunakan dalam banyak kasus (url) karena hanya digunakan 0-9 dan A untuk F. Contoh encoding 16 byte: 4fa3dd0f57cb3bf331441ed285b27735. Lihat diskusi SO tentang konversi ke hex disini.

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

4. Ringkasan dan Contoh

  • Menggunakan SecureRandom
  • Gunakan setidaknya 16 byte (2 ^ 128) dari nilai yang mungkin
  • Encode sesuai dengan kebutuhan Anda (biasanya hex atau base32 jika Anda perlu menjadi alfa-numerik)

Jangan

  • ... gunakan encoding buatan rumah Anda: lebih mudah dipelihara dan dapat dibaca untuk orang lain jika mereka melihat encoding standar apa yang Anda gunakan alih-alih aneh untuk loop menciptakan karakter pada suatu waktu.
  • ... gunakan UUID: Anda membuang 6bits 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: Alat

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


32



Mengejutkan tidak ada orang di sini yang menyarankannya tetapi:

import java.util.UUID

UUID.randomUUID().toString();

Mudah.

Manfaat dari ini adalah UUID yang bagus dan panjang dan dijamin hampir mustahil untuk bertabrakan.

Wikipedia memiliki penjelasan yang bagus tentangnya:

"... hanya setelah menghasilkan 1 miliar UUID setiap detik selama 100 tahun ke depan, kemungkinan untuk membuat hanya satu duplikat adalah 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 acak. Jadi jika kamu ingin untuk Anda dapat memotong dari ujung untuk mengurangi ukuran UUID. Ini tidak direkomendasikan tetapi Anda masih memiliki banyak keacakan, cukup untuk merekam 500k Anda dengan mudah.


29