一. 隨機(jī)數(shù)
隨機(jī)算法的起源數(shù)字稱(chēng)為種子數(shù)(seed),在種子數(shù)的基礎(chǔ)上進(jìn)行一定的變換,從而產(chǎn)生需要的隨機(jī)數(shù)字朝聋。
Random類(lèi)中實(shí)現(xiàn)的隨機(jī)是偽隨機(jī)激蹲,也就是有規(guī)則的隨機(jī),因?yàn)樗姆N子是System.currentTimeMillis()谒臼,所以它的隨機(jī)數(shù)都是可預(yù)測(cè)的。相同種子數(shù)的Random對(duì)象,相同次數(shù)生成的隨機(jī)數(shù)字是完全相同的茴晋。所以在需要頻繁生成隨機(jī)數(shù),或者安全要求較高的時(shí)候回窘,不要使用Random诺擅。
SecureRandom類(lèi)提供加密的強(qiáng)隨機(jī)數(shù)生成器 (RNG)。當(dāng)然啡直,它的許多實(shí)現(xiàn)都是偽隨機(jī)數(shù)生成器 (PRNG) 形式烁涌,這意味著它們將使用確定的算法根據(jù)實(shí)際的隨機(jī)種子生成偽隨機(jī)序列,也有其他實(shí)現(xiàn)可以生成實(shí)際的隨機(jī)數(shù)酒觅,還有另一些實(shí)現(xiàn)則可能結(jié)合使用這兩項(xiàng)技術(shù)撮执。
SecureRandom和Random如果種子一樣,產(chǎn)生的隨機(jī)數(shù)也一樣: 因?yàn)榉N子確定舷丹,隨機(jī)數(shù)算法也確定二打,因此輸出是確定的。只是說(shuō)掂榔,SecureRandom類(lèi)收集了一些隨機(jī)事件继效,比如鼠標(biāo)點(diǎn)擊,鍵盤(pán)點(diǎn)擊等等装获,SecureRandom 使用這些隨機(jī)事件作為種子瑞信,因此種子是不可預(yù)測(cè)的,而不像Random默認(rèn)使用系統(tǒng)當(dāng)前時(shí)間的毫秒數(shù)作為種子穴豫,有規(guī)律可尋凡简。
1. 創(chuàng)建SecureRandom
內(nèi)置兩種隨機(jī)數(shù)算法逼友,NativePRNG和SHA1PRNG。
(1) new
通過(guò)new來(lái)初始化秤涩,默認(rèn)會(huì)使用NativePRNG算法生成隨機(jī)數(shù)帜乞。
SecureRandom secureRandom = new SecureRandom()
(2) getInstance
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG")
//傳算法名,如果不存在算法會(huì)拋出異常
2. SecureRandom的使用
SecureRandom繼承自Random筐眷,所以也有nextInt之類(lèi)的方法黎烈。
-
nextBytes(byte[] bytes) void
//獲取隨機(jī)的一個(gè)byte數(shù)組,傳入瓶子(產(chǎn)生的隨機(jī)數(shù)放入了入?yún)ytes) -
generateSeed(int numBytes) byte[]
//獲取一個(gè)隨機(jī)的byte數(shù)組匀谣,這個(gè)數(shù)組中的數(shù)通痴掌澹可以用來(lái)做其他隨機(jī)生成器的種子 -
SecureRandom.getSeed(int numBytes) byte[]
//直接獲得隨機(jī)數(shù),本質(zhì)是包含了創(chuàng)建和使用兩步 -
setSeed(byte[] seed) void
//設(shè)置種子數(shù) -
nextInt(int bound) int
//生成[0,bound)之間的隨機(jī)數(shù)
3. 實(shí)例
/**
* 生成一個(gè)隨機(jī)數(shù)矩陣
*
* @param num 行數(shù)
* @param rowLen 列數(shù)
* @param seedLength 種子數(shù)長(zhǎng)度
* @author yaohuix
* @time 2018/3/30 16:02
*/
private List<List<Integer>> generateCakes(int num, int seedLength, int rowLen) {
SecureRandom random = new SecureRandom();
byte[] seeds = SecureRandom.getSeed(seedLength); //獲取隨機(jī)的byte數(shù)組武翎,用來(lái)后續(xù)作為種子
int counter = 0;
int tmprows = 0;
List<List<Integer>> CakesList = new ArrayList<List<Integer>>();
while (num > tmprows) {
List<Integer> list = new ArrayList<Integer>();
while (counter < rowLen) {
random.setSeed(seeds); //設(shè)置種子
int cake = random.nextInt(38); //隨機(jī)生成0-37的數(shù)字
if (!list.contains(cake) && 0 != cake) {
list.add(cake);
counter++;
}
random.nextBytes(seeds); //隨機(jī)獲取新的byte數(shù)組用以作為下次的種子烈炭,不斷循環(huán)
}
Collections.sort(list);
tmprows++;
counter = 0;
CakesList.add(list);
}
return CakesList;
}
4. 關(guān)于種子seed獲取思路
產(chǎn)生高強(qiáng)度的隨機(jī)數(shù),有兩個(gè)重要的因素:種子和算法宝恶。當(dāng)然算法是可以有很多的符隙,但是如何選擇種子是非常關(guān)鍵的因素。那么如何得到一個(gè)近似隨機(jī)的種子垫毙?這里有一個(gè)思路:收集計(jì)算機(jī)的各種信息膏执,如鍵盤(pán)輸入時(shí)間,CPU時(shí)鐘露久,內(nèi)存使用狀態(tài),硬盤(pán)空閑空間欺栗,IO延時(shí)毫痕,進(jìn)程數(shù)量,線(xiàn)程數(shù)量等信息迟几,來(lái)得到一個(gè)近似隨機(jī)的種子消请。這樣的話(huà),除了理論上有破解的可能类腮,實(shí)際上基本沒(méi)有被破解的可能臊泰。而事實(shí)上,現(xiàn)在的高強(qiáng)度的隨機(jī)數(shù)生成器都是這樣實(shí)現(xiàn)的
二. 散列算法
散列是信息的提煉蚜枢,通常其長(zhǎng)度要比信息小得多缸逃,且為一個(gè)固定長(zhǎng)度。加密性強(qiáng)的散列一定是不可逆的厂抽,這就意味著通過(guò)散列結(jié)果需频,無(wú)法推出任何部分的原始信息。任何輸入信息的變化筷凤,哪怕僅一位昭殉,都將導(dǎo)致散列結(jié)果的明顯變化苞七,這稱(chēng)之為雪崩效應(yīng)。散列還應(yīng)該是防沖突的挪丢,即找不出具有相同散列結(jié)果的兩條信息蹂风。具有這些特性的散列結(jié)果就可以用于驗(yàn)證信息是否被修改。
散列算法可以用來(lái)加密token生成簽名乾蓬, 以便token信息不暴露在網(wǎng)絡(luò)同時(shí)還能驗(yàn)證登錄的有效性惠啄。
1. MD5
全寫(xiě): Message Digest Algorithm MD5(消息摘要算法第五版)
輸出: 128bit
特點(diǎn):
a) 壓縮性:任意長(zhǎng)度的數(shù)據(jù),算出的MD5值長(zhǎng)度都是固定的巢块。
b) 容易計(jì)算:從原數(shù)據(jù)計(jì)算出MD5值很容易礁阁。
c) 抗修改性:對(duì)原數(shù)據(jù)進(jìn)行任何改動(dòng),哪怕只修改1個(gè)字節(jié)族奢,所得到的MD5值都有很大區(qū)別姥闭。
d) 弱抗碰撞:已知原數(shù)據(jù)和其MD5值,想找到一個(gè)具有相同MD5值的數(shù)據(jù)(即偽造數(shù)據(jù))是非常困難的越走。
e) 強(qiáng)抗碰撞:想找到兩個(gè)不同的數(shù)據(jù)棚品,使它們具有相同的MD5值,是非常困難的廊敌。
缺陷:
Md5一度被認(rèn)為十分靠譜铜跑。2009年,馮登國(guó)骡澈、謝濤二人利用差分攻擊锅纺,將MD5的碰撞算法復(fù)雜度降低到221,極端情況下甚至可以降低至210肋殴。僅僅2^21的復(fù)雜度意味著即便是在2008年的計(jì)算機(jī)上囤锉,也只要幾秒便可以找到一對(duì)碰撞。MD5已老护锤, 在安全性要求較高的場(chǎng)合官地,不建議使用。
應(yīng)用場(chǎng)景:
一致性驗(yàn)證
數(shù)字簽名
安全訪(fǎng)問(wèn)認(rèn)證
(1) 算法實(shí)現(xiàn)
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bytes = md5.digest(string.getBytes());
或者
InputStream in = new FileInputStream(file);
while ((len = in.read(buffer)) != -1) {
md5.update(buffer, 0, len);
}
byte[] bytes = md5.digest();
(2) MD5加密安全性探討
雖然說(shuō)MD5加密本身是不可逆的烙懦,但并不是不可破譯的驱入,網(wǎng)上有關(guān)MD5解密的網(wǎng)站數(shù)不勝數(shù),破解機(jī)制采用窮舉法氯析,就是我們平時(shí)說(shuō)的跑字典亏较。所以如何才能加大MD5破解的難度呢?
i) 對(duì)字符串多次MD5加密
public static String md5(String string, int times) {
if (TextUtils.isEmpty(string)) {
return "";
}
String md5 = md5(string);
for (int i = 0; i < times - 1; i++) {
md5 = md5(md5);
}
return md5(md5);
}
ii) MD5加鹽
所謂加鹽掩缓, 就是在原本需要加密的信息基礎(chǔ)上宴杀,糅入其它內(nèi)容salt:string+key(鹽值key)然后進(jìn)行MD5加密,加鹽的方式也是多種多樣拾因。
- 用string明文的hashcode作為鹽旺罢,然后進(jìn)行MD5加密
- 隨機(jī)生成一串字符串作為鹽旷余,然后進(jìn)行MD5加密
public static String md5(String string, String slat) {
if (TextUtils.isEmpty(string)) {
return "";
}
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
byte[] bytes = md5.digest((string + slat).getBytes());
String result = "";
for (byte b : bytes) {
String temp = Integer.toHexString(b & 0xff);
if (temp.length() == 1) {
temp = "0" + temp;
}
result += temp;
}
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
2. SHA1
全名:安全哈希算法(Secure Hash Algorithm)
輸出:160bit
(1) 與MD5比較
相同點(diǎn):
因?yàn)槎呔蒑D4導(dǎo)出,SHA-1和MD5彼此很相似扁达。相應(yīng)的正卧,他們的強(qiáng)度和其他特性也是相似。
不同點(diǎn):
a) 對(duì)強(qiáng)行攻擊的安全性:最顯著和最重要的區(qū)別是SHA-1摘要比MD5摘要長(zhǎng)32 位跪解。使用強(qiáng)行技術(shù)炉旷,產(chǎn)生任何一個(gè)報(bào)文使其摘要等于給定報(bào)摘要的難度對(duì)MD5是2128數(shù)量級(jí)的操作,而對(duì)SHA-1則是2160數(shù)量級(jí)的操作叉讥。這樣窘行,SHA-1對(duì)強(qiáng)行攻擊有更大的強(qiáng)度。
b) 對(duì)密碼分析的安全性:由于MD5的設(shè)計(jì)图仓,易受密碼分析的攻擊罐盔,SHA-1顯得不易受這樣的攻擊。
c) 速度:在相同的硬件上救崔,SHA-1的運(yùn)行速度比MD5慢惶看。
三. 對(duì)稱(chēng)加密
1. AES加密
高級(jí)加密標(biāo)準(zhǔn)(Advanced Encryption Standard,縮寫(xiě):AES)六孵,在密碼學(xué)中又稱(chēng)Rijndael加密法纬黎,是美國(guó)聯(lián)邦政府采用的一種區(qū)塊加密標(biāo)準(zhǔn)。這個(gè)標(biāo)準(zhǔn)用來(lái)替代原先的DES劫窒,已經(jīng)被多方分析且廣為全世界所使用本今。
接下來(lái)我們來(lái)實(shí)際看下具體怎么實(shí)現(xiàn):
(1) 獲取密鑰生成器
KeyGenerator kg = KeyGenerator.getInstance("DESede")
分析:KeyGenerator類(lèi)中提供了創(chuàng)建對(duì)稱(chēng)密鑰的方法。getInstance(String algorithm)
的參數(shù)指定加密算法的名稱(chēng)主巍,可以是 “AES”冠息、“DES”、“DESede”等煤禽。這些算法都可以實(shí)現(xiàn)加密。其中“DES”是目前最常用的對(duì)稱(chēng)加密算法岖赋,但安全性較差檬果。“AES”是一種替代DES算法的新算法唐断,可提供很好的安全性选脊。
(2) 初始化密鑰生成器
kg.init(168)
或init(int keysize, SecureRandom random)
分析:該步驟一般指定密鑰的長(zhǎng)度。如果該步驟省略的話(huà)脸甘,會(huì)根據(jù)算法自動(dòng)使用默認(rèn)的密鑰長(zhǎng)度恳啥。指定長(zhǎng)度時(shí),若第一步密鑰生成器使用的是“DES”算法丹诀,則密鑰長(zhǎng)度必須是56位钝的;若是“DESede”翁垂,則可以是112或168位,其中112位有效硝桩;若是“AES”沿猜,可以是128, 192或256位。
(3) 生成密鑰
SecretKey k = kg.generateKey( )
分析:使用第一步獲得的KeyGenerator類(lèi)型的對(duì)象中g(shù)enerateKey()方法可以獲得密鑰碗脊。其類(lèi)型為SecretKey類(lèi)型啼肩,可用于以后的加密和解密。
i) 通過(guò)對(duì)象序列化方式將密鑰保存在文件中
FileOutputStream f = new FileOutputStream("key1.dat");
ObjectOutputStream b = new ObjectOutputStream(f);
b.writeObject(k);
分析:ObjectOutputStream類(lèi)中提供的writeObject方法可以將對(duì)象序列化衙伶,以流的方式進(jìn)行處理祈坠。這里將文件輸出流作為參數(shù)傳遞給ObjectOutputStream類(lèi)的構(gòu)造器,這樣創(chuàng)建好的密鑰將保存在文件key1.dat中矢劲。
代碼與分析:
public static void main(String args[])
throws Exception {
KeyGenerator kg = KeyGenerator.getInstance("DESede");
kg.init(168);
SecretKey k = kg.generateKey();
FileOutputStream f = new FileOutputStream("key1.dat");
ObjectOutputStream b = new ObjectOutputStream(f);
b.writeObject(k);
}
ii) 以字節(jié)保存對(duì)稱(chēng)密鑰
★ 編程思路:
Java中所有的密鑰類(lèi)都有一個(gè)getEncoded( )方法赦拘,通過(guò)它可以從密鑰對(duì)象中獲取主要編碼格式,其返回值是字節(jié)數(shù)組卧须。其主要步驟為:
- 獲取主要編碼格式
byte[] kb=k.getEncoded()
分析:執(zhí)行SecretKey類(lèi)型的對(duì)象k的getEncoded( )方法另绩,返回的編碼放在byte類(lèi)型的數(shù)組中。 - 保存密鑰編碼格式
FileOutputStream f2=new FileOutputStream("keykb1.dat");
f2.write(kb);
(4) 轉(zhuǎn)換為AES專(zhuān)用密鑰
SecretKeySpec secretKeyspec = new SecretKeySpec(secretKey.getEncoded(), "AES")
(5) 創(chuàng)建密碼器
//實(shí)例化
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
//使用密鑰初始化花嘶,設(shè)置為加密模式/解密模式(Cipher.DECRYPT_MODE)
cipher.init(Cipher.ENCRYPT_MODE, secretKeyspec);
(6) 加密/解密
byte[] doFinal(byte[] input)
實(shí)例:
//Cipher密碼 encrypt加密 decrypt解密 crypto秘密
private final static String HEX = "0123456789ABCDEF";
//AES是加密方式 CBC是工作模式 PKCS5Padding是填充模式
private static final String CBC_PKCS5_PADDING = "AES/CBC/PKCS5Padding";
private static final String AES = "AES";//AES 加密
private static final String SHA1PRNG = "SHA1PRNG";//// SHA1PRNG 強(qiáng)隨機(jī)種子算法, 要區(qū)別4.2以上版本的調(diào)用方法
/*
* 生成隨機(jī)數(shù)笋籽,可以當(dāng)做動(dòng)態(tài)的密鑰 加密和解密的密鑰必須一致,不然將不能解密
*/
public static String generateKey() {
try {
SecureRandom localSecureRandom = SecureRandom.getInstance(SHA1PRNG);
byte[] bytes_key = new byte[20];
localSecureRandom.nextBytes(bytes_key);
String str_key = toHex(bytes_key);
return str_key;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 對(duì)密鑰進(jìn)行處理
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance(AES);
//for android
SecureRandom sr = null;
// 在4.2以上版本中椭员,SecureRandom獲取方式發(fā)生了改變
if (android.os.Build.VERSION.SDK_INT >= 17) {
sr = SecureRandom.getInstance(SHA1PRNG, "Crypto");
} else {
sr = SecureRandom.getInstance(SHA1PRNG);
}
// for Java
// secureRandom = SecureRandom.getInstance(SHA1PRNG);
sr.setSeed(seed);
kgen.init(128, sr); //256 bits or 128 bits,192bits
//AES中128位密鑰版本有10個(gè)加密循環(huán)车海,192比特密鑰版本有12個(gè)加密循環(huán),256比特密鑰版本則有14個(gè)加密循環(huán)隘击。
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
/*
* 加密
*/
public static String encrypt(String key, String cleartext) {
if (TextUtils.isEmpty(cleartext)) {
return cleartext;
}
try {
byte[] result = encrypt(key, cleartext.getBytes());
return Base64.encode(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/*
* 加密
*/
private static byte[] encrypt(String key, byte[] clear) throws Exception {
byte[] raw = getRawKey(key.getBytes());
SecretKeySpec skeySpec = new SecretKeySpec(raw, AES);
Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
/*
* 解密
*/
public static String decrypt(String key, String encrypted) {
if (TextUtils.isEmpty(encrypted)) {
return encrypted;
}
try {
byte[] enc = Base64.decodeToBytes(encrypted);
byte[] result = decrypt(key, enc);
return new String(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/*
* 解密
*/
private static byte[] decrypt(String key, byte[] encrypted) throws Exception {
byte[] raw = getRawKey(key.getBytes());
SecretKeySpec skeySpec = new SecretKeySpec(raw, AES);
Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
//二進(jìn)制轉(zhuǎn)字符
public static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2 * buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}