commonsのRandomStringUtilsを使わず、JavaのUUIDを使わず、Javaでランダムな文字列作成してみるの巻

眠いので、数年前に作ってお蔵入りしていたソースをとりあえず貼ってみた。
最近はこんな風にランダムな値を作ることはしないのかしらね。
自分でもかなり忘れている。

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

/**
 * ランダムな文字列を作成する。
 * ランダムに生成したx個のバイト列を基にbase64の方法でバイトから下の値で変換していく。
 * 
 * ランダムに生成したbyte配列から3byteずつ取り出す。
 * base64形式でbyteをascii文字に変換していくため。
 * 3byte=24bitだからそれを演算で結合していく。
 * 00000000 11111111 00010111こんな感じのを
 * 000000 001111 111100 010111このように6bitずつ使いたい。
 * そのため、byte配列をビット演算する。
 * 1byte目を左に16bitシフト、2byte目を左に8bitシフト、3byte目をor演算。
 * これをint型に入れる。残りの8bitは0が入る。
 * 00000000 00000000 11111111 00010111こんな感じ。
 * 今度はこれから6bitずつ値を取り出す。
 * 右に18bit, 12bit, 6bit, 0bitシフトし、111111(10進数なら63)でand演算をそれぞれ実行。
 * 
 * 00000000 00000000 11111111 00010111
 *          111111
 * 00000000 00000000 11111111 00010111
 *                11 1111
 * 00000000 00000000 11111111 00010111
 *                       1111 11
 * 00000000 00000000 11111111 00010111
 *                              111111
 * 
 * 32bitで見てみると先ほどの24bitを4等分にしたint型の値を取得できる。
 * 
 * 実際のand演算は以下。
 * 
 * 00000000 00000000 00000000 00000000
 * 00000000 00000000 00000000 00111111
 * 
 * 00000000 00000000 00000000 00001111
 * 00000000 00000000 00000000 00111111
 * 
 * 00000000 00000000 00000011 11111100
 * 00000000 00000000 00000000 00111111
 * 
 * 00000000 00000000 11111111 00010111
 * 00000000 00000000 00000000 00111111
 * 
 * 得られた4つのint値でA〜/までの値を含む文字列から該当順序にある文字を取り出す。
 * 
 * これを3byteずつ取り出して繰り返す。
 * 
 * 端数となる場合(残り2byte, 1byteの時)、bitが6の倍数となるようパディングを行う。
 * 残り2byteの場合、2bit足りないので左に2bitシフト演算し、0パディングする。
 * 残り1byteの場合、4bit足りないので左に4bitシフト演算し、0パディングする。
 * その後は上記と同様に処理をして文字を取り出す。
 * base64では文字は4つずつなので足りない場合「=」を付け足して増やす。
 * 
 * 
 * ランダムな英数字のみが必要な場合はその後、+/や=の値を変換する処理を入れる。
 * ちなみに今の処理では、指定する値はbyte数であるため、実際に作成される文字数はそれより多くなる。
 * 
 * @author お屠蘇
 *
 */
public class Samplepleple {

	private static final String des = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	
	private static final char equal = '=';
	
	private static final Random num_randam = new Random();
	//とりあえず、デフォ使用。
	private static final SecureRandom random = new SecureRandom();
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		int len = 10;
		for(int i=0;i<len;i++) {
			int xd = 24;
			getSecureRandumString(xd);
		}
	}
	
	public static String getSecureRandumString(int len) {
		if(len <= 0) {
			return null;
		}
		byte[] bytes = new byte[len];
		random.nextBytes(bytes);
		
		byte[] bytecp = bytes.clone();
		
		StringBuilder buf = new StringBuilder();
		int encount = 0;
		int total = bytes.length;
		int lastcount = 0;
		if (total - 3 < 0) {
			//どうしようかね。
		}
		for (; encount < bytes.length; encount=encount+2) {
			if (total - 3 < 0) {
				lastcount = total;
				break;
			}
			total = total - 3;//3byte取り出す=24bit
			//int 32bit
			byte n1 = bytes[encount];
			byte n2 = bytes[encount+1];
			byte n3 = bytes[encount+2];
			
			//24bit
			//000000 001111 111100 010111
			int n123 = n1 << 16 | n2 << 8 | n3 ;
			//010100 001111 111100 010111
			//111111 111111 111111 111111
			
			//6bitであることに注意。0xfは4bit
			int b1 = (n123 >>> 18) & 63;
			int b2 = (n123 >>> 12) & 63;
			int b3 = (n123 >>> 6) & 63;
			int b4 = n123 & 63;
			
			buf.append(des.charAt(b1))
			   .append(des.charAt(b2))
			   .append(des.charAt(b3))
			   .append(des.charAt(b4));
		}
		//残り2byte 16bit/6=2 rm4bit
		if (total == 2) {
			byte n1 = bytes[encount];
			byte n2 = bytes[encount+1];
			int n123 = (n1 << 8 | n2) << 2;
			//18bit
			//11111111 11111111 0000
			//111111 111111 111100
			int b1 = (n123 >>> 12) & 63;
			int b2 = (n123 >>> 6) & 63;
			int b3 = n123 & 63;
			
			buf.append(des.charAt(b1))
			   .append(des.charAt(b2))
			   .append(des.charAt(b3))
   			   .append(equal);
			encount++;
			encount++;
		} else if (total == 1) {
			//残り1byte 8bit/6=1 rm2bit
			byte n1 = bytes[encount];
			int n123 = n1 << 4;
			int b1 = (n123 >>> 6) & 63;
			int b2 = n123 & 63;
			buf.append(des.charAt(b1))
			   .append(des.charAt(b2))
			   .append(equal)
			   .append(equal);
		}

		String reVal = buf.toString();

		char[] chval = reVal.toCharArray();
		for (int i = 0; i < chval.length; i++) {
			char x= chval[i];
			if (des.charAt(63) == x || des.charAt(62) == x || equal == x) {
				int xd = num_randam.nextInt(61);
				chval[i] = des.charAt(xd);
			}
		}
		String newval = new String(chval);
		int randumlen = len;
		if (newval.length() - len >= 0) {
			randumlen = len;
		} else {
			randumlen = 0;
		}
		String val = newval.substring(0, randumlen);
		
		System.out.println("引数: " + len);
		System.out.println("整形: " + reVal);
		System.out.println("最後: " + newval);
		System.out.println("カウント: " + newval.length());
		System.out.println("求められる文字数: " + len);
		System.out.println("出力する文字数: " + val.length());
		System.out.println("結果: " + val);
		System.out.println();
		
		return val;
	}
}

実行したらこんな感じになりました。
動いたから良かった。(^O^)

引数: 24
整形: zgUc///X///L///s///Z///3/6Bw//+a
最後: zgUcq3GXmPNLNlkskArZI4t3E6BwZYIa
カウント: 32
求められる文字数: 24
出力する文字数: 24
結果: zgUcq3GXmPNLNlkskArZI4t3