Java随机数⽣成器RandomSecureRandom原理分析
⽂章⽬录
Java⾥提供了⼀些⽤于⽣成随机数的⼯具类,这⾥分析⼀下其实现原理,以及他们之间的区别、使⽤场景。
Random是⽐较常⽤的随机数⽣成类,它的基本信息在类的注释⾥都写到了,下⾯是JDK8⾥该类的注释:
/**
*Aninstanceofthisclassisudtogenerateastreamof
*ssusa48-bited,whichis
*modifiedusingalinearcongruentialformula.(SeeDonaldKnuth,
*TheArtofComputerProgramming,Volume2,Section3.2.1.)
*
*Iftwoinstancesof{@codeRandom}arecreatedwiththesame
*ed,andthesamequenceofmethodcallsismadeforeach,they
*rto
*guaranteethisproperty,particularalgorithmsarespecifiedforthe
*class{@codeRandom}.Javaimplementationsmustuallthealgorithms
*shownherefortheclass{@codeRandom},forthesakeofabsolute
*r,subclassofclass{@codeRandom}
*arepermittedtouotheralgorithms,solongastheyadheretothe
*generalcontractsforallthemethods.
*
*Thealgorithmsimplementedbyclass{@codeRandom}ua
*{@codeprotected}utilitymethodthatoneachinvocationcansupply
*upto32pudorandomlygeneratedbits.
*
*Manyapplicationswillfindthemethod{@linkMath#random}simplertou.
*
*
Instancesof{@}arethreadsafe.
*However,theconcurrentuofthesame{@}
*instanceacrossthreadsmayencountercontentionandconquent
*erinsteadusing
*{@LocalRandom}inmultithreaded
*designs.
*
*
Instancesof{@}arenotcryptographically
*erinsteadusing{@Random}to
*getacryptographicallycurepudo-randomnumbergeneratorforu
*bycurity-nsitiveapplications.
*
*@authorFrankYellin
*@since1.0
*/
翻译⼀下,主要有以下⼏点:
类使⽤线性同余法linearcongruentialformula来⽣成伪随机数。
2.两个Random实例,如果使⽤相同的种⼦ed,那他们产⽣的随机数序列也是⼀样的。
是线程安全的,你的程序如果对性能要求⽐较⾼的话,推荐使⽤ThreadLocalRandom。
不是密码学安全的,加密相关的推荐使⽤SecureRandom。
Random的基本⽤法如下所⽰:
Randomrandom=newRandom();
intr=t();//⽣成⼀个随机数
从下⾯的源码中可以看到,Random的默认使⽤当前系统时钟来⽣成种⼦ed。
privatestaticfinalAtomicLongedUniquifier=newAtomicLong(8682522807148012L);
publicRandom(){
this(edUniquifier()^me());
}
publicRandom(longed){
if(getClass()==)
=newAtomicLong(initialScramble(ed));
el{
//subclassmighthaveoverridentSeed
=newAtomicLong();
tSeed(ed);
}
}
privatestaticlongedUniquifier(){
for(;;){
longcurrent=();
longnext=current*6652981L;
if(eAndSet(current,next))
returnnext;
}
}
Random
介绍Random类时提到过,要⽣成加密基本的随机数应该使⽤SecureRandom类,该类信息如下所⽰:
/**
*Thisclassprovidesacryptographicallystrongrandomnumber
*generator(RNG).
*
*
Acryptographicallystrongrandomnumber
*minimallycomplieswiththestatisticalrandomnumbergeneratortests
*specifiedin
*FIPS140-2,SecurityRequirementsforCryptographicModules,
*ction4.9.1.
*Additionally,SecureRandommustproducenon-deterministicoutput.
*ThereforeanyedmaterialpasdtoaSecureRandomobjectmustbe
*unpredictable,andallSecureRandomoutputquencesmustbe
*cryptographicallystrong,asdescribedin
*
*RFC1750:RandomnessRecommendationsforSecurity.
*
*
AcallerobtainsaSecureRandominstanceviathe
*no-argumentconstructororoneofthe{@codegetInstance}methods:
*
*
*SecureRandomrandom=newSecureRandom();
*
*
*
ManySecureRandomimplementationsareintheformofapudo-random
*numbergenerator(PRNG),whichmeanstheyuadeterministicalgorithm
*toproduceapudo-randomquencefromatruerandomed.
*Otherimplementationsmayproducetruerandomnumbers,
*andyetothersmayuacombinationofbothtechniques.
*
*
TypicalcallersofSecureRandominvokethefollowingmethods
*toretrieverandombytes:
*
*
*SecureRandomrandom=newSecureRandom();
*bytebytes[]=newbyte[20];
*tes(bytes);
*
*
*
Callersmayalsoinvokethe{@codegenerateSeed}method
*togenerateagivennumberofedbytes(toedotherrandomnumber
*generators,forexample):
*
*byteed[]=teSeed(20);
*
*
*Note:Dependingontheimplementation,the{@codegenerateSeed}and
*{@codenextBytes}methodsmayblockantropyisbeinggathered,
*forexample,iftheyneedtoreadfrom/dev/randomonvariousUnix-like
*operatingsystems.
*/
主要有以下⼏点:
1.该类提供了能满⾜加密要求的强随机数⽣成器。
2.传递给SecureRandom种⼦必须是不可预测的,ed使⽤不当引发的安全漏洞可以看看。
1.⼀般使⽤默认的种⼦⽣成策略就⾏,对应Linux⾥⾯就是/dev/random和/dev/urandom。其实现原理是:操作系统收集了
⼀些随机事件,⽐如⿏标点击,键盘点击等等,SecureRandom使⽤这些随机事件作为种⼦。
2.使⽤/dev/random来⽣成种⼦时,可能会因为熵不够⽽阻塞,性能⽐较差。
SecureRandom⽤法如下所⽰:
SecureRandomrandom=newSecureRandom();
byte[]data=tes(16);
下⾯我们看看其内部实现:
synchronizedpublicvoidnextBytes(byte[]bytes){
NextBytes(bytes);
}
publicSecureRandom(){
super(0);
getDefaultPRNG(fal,null);
}
privatevoidgetDefaultPRNG(booleantSeed,byte[]ed){
Stringprng=getPrngAlgorithm();
if(prng==null){
//bummer,gettheSUNimplementation
prng="SHA1PRNG";
RandomSpi=Random();
er=Provider();
if(tSeed){
SetSeed(ed);
}
}el{
try{
SecureRandomrandom=tance(prng);
RandomSpi=ureRandomSpi();
er=vider();
if(tSeed){
SetSeed(ed);
}
}catch(NoSuchAlgorithmExceptionnsae){
//neverhappens,becauwemadesurethealgorithmexists
thrownewRuntimeException(nsae);
}
}
if(getClass()==){
thm=prng;
}
}
在mac环境下使⽤JDK8测试时发现,默认使⽤了NativePRNG⽽⾮SHA1PRNG,但是NativePRNG其实还是在
Random的基础上做了⼀些封装。
在nerator类⾥,可以看到ed是利⽤/dev/random或/dev/urandom来⽣成的,启动应⽤程序时
可以通过参数-=file:/dev/urandom来指定ed源。
static{
Stringvar0=dSource();
if(!("file:/dev/random")&&!("file:/dev/urandom")){
if(()!=0){
try{
instance=dGenerator(var0);
if(debug!=null){
n("UsingURLedgeneratorreadingfrom"+var0);
}
}catch(IOExceptionvar2){
if(debug!=null){
n("Failedtocreateedgeneratorwith"+var0+":"+ng());
}
}
}
}el{
try{
instance=newNativeSeedGenerator(var0);
if(debug!=null){
n("Usingoperatingsystemedgenerator"+var0);
}
}catch(IOExceptionvar3){
if(debug!=null){
n("Failedtouoperatingsystemedgenerator:"+ng());
}
}
}
if(instance==null){
if(debug!=null){
n("Usingdefaultthreadededgenerator");
}
instance=edSeedGenerator();
}
}
在Random类⾥,多个实例设置相同的ed,产⽣的随机数序列也是⼀样的。⽽SecureRandom则不同,运⾏下⾯的代码:
publicclassRandomTest{
publicstaticvoidmain(String[]args){
byte[]ed="hello".getBytes();
for(inti=0;i<10;++i){
SecureRandomcureRandom=newSecureRandom(ed);
n(t());
}
}
}
输出如下所⽰,每次运⾏产⽣的随机数都不⼀样。
-2105877601
1151182748
1329080810
-617594950
2094315881
-1649759687
-1360561033
-653424535
-927058354
-1577199965
为什么呢?因为engineSetSeed⽅法设置ed时调⽤的是静态实例INSTANCE的implSetSeed⽅法,该⽅法通过
getMixedRandom得到的SecureRandom来设置ed,⽽这个SecureRandom初始化种⼦是系统的。
IOINSTANCE;
//inNativePRNG
protectedvoidengineSetSeed(byte[]var1){
tSeed(var1);
}
privatevoidimplSetSeed(byte[]var1){
Objectvar2=_SET_SEED;
synchronized(_SET_SEED){
if(!tInitialized){
tInitialized=true;
t=(OutputStream)ileged(newPrivilegedAction
publicOutputStreamrun(){
try{
returnnewFileOutputStream(le,true);
}catch(Exceptionvar2){
returnnull;
}
}
});
}
if(t!=null){
try{
(var1);
}catch(IOExceptionvar5){
thrownewProviderException("tSeed()failed",var5);
}
}
Random().engineSetSeed(var1);
}
}
privateSecureRandomgetMixRandom(){
SecureRandomvar1=dom;
if(var1==null){
Objectvar2=_GET_BYTES;
synchronized(_GET_BYTES){
var1=dom;
if(var1==null){
var1=newSecureRandom();
try{
byte[]var3=newbyte[20];
readFully(,var3);
SetSeed(var3);
}catch(IOExceptionvar5){
thrownewProviderException("initfailed",var5);
}
dom=var1;
}
}
}
returnvar1;
}
在SetSeed⽅法⾥,新种⼦的⽣成不仅和刚设置的ed有关,也和原来的种⼦(系统
产⽣的ed)有关。
//Random
publicsynchronizedvoidengineSetSeed(byte[]var1){
if(!=null){
();
for(intvar2=0;var2<;++var2){
[var2]=0;
}
}
=(var1);
}
/dev/random与/dev/urandom
在Linux操作系统中,有⼀个特殊的设备⽂件/dev/random,可以⽤作随机数发⽣器或伪随机数发⽣器。
在读取时,/dev/random设备会返回⼩于熵池噪声总数的随机字节。/dev/random可⽣成⾼随机性的公钥或⼀次性密码本。若熵池空
了,对/dev/random的读操作将会被阻塞,直到从别的设备中收集到了⾜够的环境噪声为⽌。
当然你也可以设置成不堵塞,当你在open的时候设置参数O_NONBLOCK,但是当你read的时候,如果熵池空了,会返回-1。
/dev/random的⼀个副本是/dev/urandom(“unlocked”,⾮阻塞的随机数发⽣器),它会重复使⽤熵池中的数据以产⽣伪随机数
据。这表⽰对/dev/urandom的读取操作不会产⽣阻塞,但其输出的熵可能⼩于/dev/random的。它可以作为⽣成较低强度密码的伪随
机数⽣成器,不建议⽤于⽣成⾼强度长期密码。
资料
1.
本文发布于:2023-01-04 17:57:25,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/92057.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |