如何安全的存储用户密码?(下)代码实现pbkdf2算法加密

本文以java为例,进行实际加解密操作:

1       密码加盐hash

使用salt+password进行哈希算法加密!哈希算法选择:PBKDF2!

1.1    生成salt

使用随机函数java.security.SecureRandom生成24位随机数作为salt:

本文参考的依据是:JDK API 1.6.0 中文版,下载地址:http://down.51cto.com/data/2300228

 

1.2    PBKDF2代码详解

可以直接copy到代码中

package com.demo.encrypt;

 

/*

 *Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).

 *Copyright (c) 2013, Taylor Hornby

 *All rights reserved.

 *

 *Redistribution and use in source and binary forms, with or without

 *modification, are permitted provided that the following conditions are met:

 *

 * 1.Redistributions of source code must retain the above copyright notice,

 *this list of conditions and the following disclaimer.

 *

 * 2.Redistributions in binary form must reproduce the above copyright notice,

 *this list of conditions and the following disclaimer in the documentation

 *and/or other materials provided with the distribution.

 *

 *THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "ASIS"

 *AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE

 *IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE

 *ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE

 *LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR

 *CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF

 *SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS

 *INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN

 *CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)

 *ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE

 *POSSIBILITY OF SUCH DAMAGE.

 */

 

import java.math.BigInteger;

importjava.security.NoSuchAlgorithmException;

import java.security.SecureRandom;

import java.security.spec.InvalidKeySpecException;

 

import javax.crypto.SecretKeyFactory;

import javax.crypto.spec.PBEKeySpec;

 

/*

 *PBKDF2 salted password hashing.

 *Author: havoc AT defuse.ca

 *www: http://crackstation.net/hashing-security.htm

 */

public class HashEncrypt {

         //PBKDF2.PBKDF2WithHmacSHA1, PBKDF2.PBKDF2WithHmacSHA224,PBKDF2.PBKDF2WithHmacSHA256,

         //PBKDF2.PBKDF2WithHmacSHA384,//PBKDF2.PBKDF2WithHmacSHA512

         //pbkdf2算法API链接:http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/pkcs/pkcs5/PBKDF2.html

         publicstatic final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";

         //publicstatic final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";

         //注意jdk1.8以下版本不支持SHA224以及SHA224以上算法,会报错:(项目环境为JDK1.7)

         //ERROR:java.security.NoSuchAlgorithmException: PBKDF2WithHmacSHA224 SecretKeyFactorynot available

         //The following constants may be changed without breaking existing hashes.

         publicstatic final int SALT_BYTE_SIZE = 16;//=32/2,生成字符长度为32的salt值

         publicstatic final int HASH_BYTE_SIZE = 24;

         publicstatic final int PBKDF2_ITERATIONS = 1000;

 

         publicstatic final int ITERATION_INDEX = 0;

         publicstatic final int SALT_INDEX = 1;

         publicstatic final int PBKDF2_INDEX = 2;

 

         /**

          * Returns a salted PBKDF2 hash of thepassword.(返回使用PBKDF2对password进行加盐hash生成的字符串)

          *

          * @param  password    the password to hash

          * @return              a salted PBKDF2 hash of thepassword

          */

         publicstatic String createHash(String password)

                            throwsNoSuchAlgorithmException, InvalidKeySpecException {

                   returncreateHash(password.toCharArray());

         }

 

         /**

          * Returns a salted PBKDF2 hash of thepassword.(返回使用PBKDF2对password字节数组进行加盐hash生成的字符串)

          *

          * @param  password    the password to hash

          * @return              a salted PBKDF2 hash of thepassword

          */

         publicstatic String createHash(char[] password)

                            throwsNoSuchAlgorithmException, InvalidKeySpecException {

                   LongstartTime = System.currentTimeMillis();

                   //Generate a random salt:创建一个随机16位盐

                   SecureRandomrandom = new SecureRandom();

                   byte[]salt = new byte[SALT_BYTE_SIZE];

                   random.nextBytes(salt);

                   //Hash the password :生成可以编码的密钥

                   byte[]hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);

                   LongendTime = System.currentTimeMillis();

                   System.out.println("生成加盐hash密码时间是:"+ String.valueOf(endTime - startTime));

                   //format iterations:salt:hash:对字节数组进行hash返回字符串,字符串结构:(迭代次数:salt盐:加盐hash密码)

                   returnPBKDF2_ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash);

         }

 

         /**

          * Validates a password using a hash.(校验密码是否正确)

          *

          * @param  password        the password tocheck

          * @param  correctHash     the hash of thevalid password

          * @return                  true if the password iscorrect, false if not

          */

         publicstatic boolean validatePassword(String password, String correctHash)

                            throwsNoSuchAlgorithmException, InvalidKeySpecException {

                   returnvalidatePassword(password.toCharArray(), correctHash);

         }

 

         /**

          * Validates a password using a hash.(校验密码是否正确实现)

          *

          * @param  password        the password tocheck

          * @param  correctHash     the hash of thevalid password

          * @return                  true if the password iscorrect, false if not:校验密码是否正确

          */

         publicstatic boolean validatePassword(char[] password, String correctHash)

                            throwsNoSuchAlgorithmException, InvalidKeySpecException {

                   //correctHash字符串的结构为(迭代次数:salt盐:加盐hash密码)

                   //Decode the hash into its parameters:冒号分隔correctHash

                   String[]params = correctHash.split(":");

                   //params[0]:迭代次数

                   intiterations = Integer.parseInt(params[ITERATION_INDEX]);

                   //params[1]:salt盐 ,再转换为字节数组

                   byte[]salt = fromHex(params[SALT_INDEX]);

                   ////params[2]:加盐hash密码字符串,再转换为字节数组

                   byte[]hash = fromHex(params[PBKDF2_INDEX]);

                   //Compute the hash of the provided password, using the same salt,

                   //iteration count, and hash length :使用password生成密钥

                   byte[]testHash = pbkdf2(password, salt, iterations, hash.length);

                   //Compare the hashes in constant time. The password is correct if

                   //both hashes match.:比较密钥是否相等

                   returnslowEquals(hash, testHash);

         }

 

         /**

          * Compares two byte arrays in length-constanttime. This comparison method

          * is used so that password hashes cannot beextracted from an on-line

          * system using a timing attack and thenattacked off-line.

          * 这段代码使用了异或(XOR)操作符”^”来比较整数是否相等,而没有使用”==”操作符。原因在于如果两个数完全一致,异或之后的值为零。

          * 
因为 0 XOR 0 = 0, 1 XOR1 = 0, 0 XOR 1 = 1, 1 XOR 0 = 1。

          * 
所以,第一行代码如果a.length等于b.length,变量diff等于0,否则的话diff就是一个非零的值。

          * 
然后,让a,b的每一个字节XOR之后再跟diff OR。这样,只有diff一开始是0,并且,a,b的每一个字节XOR的结果也是零,

          * 
最后循环完成后diff的值才是0,这种情况是a,b完全一样。否则最后diff是一个非零的值。

          *

          * @param  a       the first byte array

          * @param  b       the second byte array

          * @return          true if both byte arrays are thesame, false if not:比较两个字节数组是否相等。0相等;1不等

          */

         privatestatic boolean slowEquals(byte[] a, byte[] b) {

                   intdiff = a.length ^ b.length;

                   for(int i = 0; i < a.length && i < b.length; i++)

                            diff|= a[i] ^ b[i];

                   returndiff == 0;

         }

 

         /**

          * Computes the PBKDF2 hash of a password.(使用pbkdf2算法生成加盐hash字节数组)

          *

          * @param  password    the password to hash.

          * @param  salt        the salt

          * @param  iterations  the iteration count(slowness factor)

          * @param  bytes       the length of the hashto compute in bytes

          * @return              the PBDKF2 hash of the password:使用pbkdf2进行加盐hash生成密钥

          */

         privatestatic byte[] pbkdf2(char[] password, byte[] salt, int iterations,

                            intbytes) throws NoSuchAlgorithmException, InvalidKeySpecException {

                   //PBEKeySpec:可随同基于密码的加密法 (PBE)

                   //使用的供用户选择的密码。可以将密码视为某种原始密钥内容,由此加密机制使用其导出一个密钥。

                   //带有生成可变密钥大小的 PBE 密码的 PBEKey 时使用的一个密码、salt、迭代计数以及导出密钥长度的构造方法。如果指定

                   //password 为 null,则使用一个空 char[]。

                   //注:在将 password 和 salt 存储进新的 PBEKeySpec 对象前将其复制。

                   //参数:

                   //password - 密码。

                   //salt - salt。

                   //iterationCount - 迭代计数。

                   //keyLength - 要导出的密钥长度。

                   PBEKeySpecspec = new PBEKeySpec(password, salt, iterations, bytes * 8);

                   //SecretKeyFactory此类表示秘密密钥的工厂。

                   //getInstance(String PBKDF2_ALGORITHM)返回转换指定算法的秘密密钥的SecretKeyFactory

                   //对象。

                   //PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";

                   SecretKeyFactoryskf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);

                   //skf.generateSecret(spec)根据提供的密钥规范(密钥材料)生成 SecretKey 对象。

                   //SecretKey.getEncoded()返回基本编码格式的密钥,如果此密钥不支持编码,则返回 null。

                   returnskf.generateSecret(spec).getEncoded();

         }

 

         /**

          * Converts a string of hexadecimal charactersinto a byte array.(将十六进制字符串转换为字节数组)

          *

          * @param  hex         the hex string

          * @return              the hex string decoded into abyte array:字符串转换为字节数组

          */

         privatestatic byte[] fromHex(String hex) {

                   byte[]binary = new byte[hex.length() / 2];

                   for(int i = 0; i < binary.length; i++) {

                            binary[i]= (byte) Integer.parseInt(

                                               hex.substring(2* i, 2 * i + 2), 16);

                   }

                   returnbinary;

         }

 

         /**

          * Converts a byte array into a hexadecimalstring.(将字节数组转换为十六进制字符串)

          *

          * @param  array       the byte array toconvert

          * @return              a length*2 character stringencoding the byte array::字节数组转换为字符串

          */

         privatestatic String toHex(byte[] array) {

                   BigIntegerbi = new BigInteger(1, array);

                   Stringhex = bi.toString(16);

                   intpaddingLength = (array.length * 2) - hex.length();

                   if(paddingLength > 0)

                            returnString.format("%0" + paddingLength + "d", 0) + hex;

                   else

                            returnhex;

         }

 

         /**

          * Tests the basic functionality of thePasswordHash class

          *

          * @param  args        ignored

          */

         publicstatic void main(String[] args) {

                   try{

                            //Print out 10 hashes

                            for(int i = 0; i < 10; i++) {

                                     //System.out.println(HashEncrypt.createHash("p\\r\\nassw0Rd!"));

                                     System.out.println("生成加盐hash后密钥:第" +i + "次:hashPWd:"

                                                        +HashEncrypt.createHash("p\\r\\nassw0Rd!"));

                            }

 

                            //Test password validation

                            booleanfailure = false;

                            System.out.println("Runningtests...");

                            for(int i = 0; i < 2; i++) {

                                     Stringpassword = "" + i;

                                     Stringhash = createHash(password);

                                     System.out.println("password第一次加盐hash:"+ hash);

                                     StringsecondHash = createHash(password);

                                     System.out.println("password第二次加盐hash:"+ secondHash);

                                     if(hash.equals(secondHash)) {

                                               //System.out.println("FAILURE: TWO HASHES ARE EQUAL!");

                                               System.out.println("password="+ i

                                                                 +",加盐hash后生成的两个密钥相等!就不可用!");

                                               failure= true;

                                     }

                                     StringwrongPassword = "" + (i + 1);

                                     if(validatePassword(wrongPassword, hash)) {

                                               //System.out.println("FAILURE: WRONG PASSWORD ACCEPTED!");

                                               System.out.println("hash="+ hash + ",wrongPassword="

                                                                 +wrongPassword + ",校验密钥匹配!就不可用!");

                                               failure= true;

                                     }

                                     if(!validatePassword(password, hash)) {

                                               System.out.println("FAILURE:GOOD PASSWORD NOT ACCEPTED!");

                                              System.out.println("hash=" +hash + ",password=" + password

                                                                 +",校验密钥不匹配,hash就是password生成的!就不可用!");

                                               failure= true;

                                     }

                            }

                            if(failure)

                                     System.out.println("TESTSFAILED!");

                            else

                                     System.out.println("TESTSPASSED!");

                   }catch (Exception ex) {

                            System.out.println("ERROR:" + ex);

                   }

         }

 

}

1.3    PBKDF2项目实践

1,  用户注册,使用PBKDF2对密码进行加盐hash加密;

2,  将salt盐和生成的hash密码存入数据库中;

3,  用户登录对密码进行加盐hash;

4,  校验密码;

5,  用户每次登录,重复3/4步骤。

如果想保存32位固定长度密码,可以在后面再进行一次MD5加密

 

工具类:

package com. common.utils;

 

import java.math.BigInteger;

import java.security.NoSuchAlgorithmException;

import java.security.SecureRandom;

importjava.security.spec.InvalidKeySpecException;

 

import javax.crypto.SecretKeyFactory;

import javax.crypto.spec.PBEKeySpec;

 

/**

 *

 * @项目名称:common

 * @类名称:HashEncrypt

 * @类描述:使用PBKDF2对密码进行加密处理

 * @创建人:wyait

 * @创建时间:2017年4月24日 上午9:53:15

 *@version:

 */

public class HashEncrypt {

         //pbkdf2 SHA1算法(由于JDK1.8以下只能使用SHA1,so....这将会是个历史遗留问题)

         publicstatic final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";

         //=32/2,生成字符长度为32的salt值

         publicstatic final int SALT_BYTE_SIZE = 16;

         //48/2,生成字符长度为48的hash密码

         publicstatic final int HASH_BYTE_SIZE = 24;

         //加密迭代次数

         publicstatic final int PBKDF2_ITERATIONS = 1000;

 

         /**

          *

          * @描述:返回使用SecureRandom生成salt盐

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午10:40:14

          * @return

          */

         publicstatic String createSalt() {

                   //Generate a random salt:创建一个随机16位盐

                   SecureRandomrandom = new SecureRandom();

                   byte[]salt = new byte[SALT_BYTE_SIZE];

                   random.nextBytes(salt);

                   //转换为十六进制字符串,字符长度=32

                   returntoHex(salt);

         }

 

         /**

          *

          * @描述:返回使用PBKDF2对password进行加盐hash生成的字符串(字符长度48)

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午9:55:37

          * @param salt 盐

          * @param password  原始密码

          * @return

          * @throws NoSuchAlgorithmException

          * @throws InvalidKeySpecException

          */

         publicstatic String createHash(String salt, String password)

                            throwsNoSuchAlgorithmException, InvalidKeySpecException {

                   returncreateHash(fromHex(salt), password.toCharArray());

         }

 

         /**

          *

          * @描述:返回使用PBKDF2对password字节数组进行加盐hash生成的字符串(字符长度48)

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午9:57:35

          * @param salt 盐转换为字节数组

          * @param password 密码转换为字符数组

          * @return

          * @throws NoSuchAlgorithmException

          * @throws InvalidKeySpecException

          */

         publicstatic String createHash(byte[] salt, char[] password)

                            throwsNoSuchAlgorithmException, InvalidKeySpecException {

                   //Hash the password :hash生成字节数组

                   byte[]hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);

                   //对字节数组进行hash返回字符串(加盐hash密码)

                   System.out.println("hash密钥字符串长度:"+ toHex(hash).length());

                   returntoHex(hash);

         }

 

         /**

          *

          * @描述:校验密码是否正确

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午10:02:49

          * @param salt 盐

          * @param password  密码

          * @param correctHash  加盐hash密码

          * @return

          * @throws NoSuchAlgorithmException

          * @throws InvalidKeySpecException

          */

         publicstatic boolean validatePassword(String salt, String password,

                            StringcorrectHash) throws NoSuchAlgorithmException,

                            InvalidKeySpecException{

                   returnvalidatePassword(fromHex(salt), password.toCharArray(),

                                     correctHash);

         }

 

         /**

          *

          * @描述:校验密码是否正确实现

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午10:07:14

          * @param salt  盐

          * @param password   密码

          * @param correctHash   加盐hash密码

          * @return

          * @throws NoSuchAlgorithmException

          * @throws InvalidKeySpecException

          */

         publicstatic boolean validatePassword(byte[] salt, char[] password,

                            StringcorrectHash) throws NoSuchAlgorithmException,

                            InvalidKeySpecException{

                   //将correctHash转换为字节数组

                   byte[]hash = fromHex(correctHash);

                   //对passwrod进行:加盐hash生成密码字符串,再比对correctHash

                   byte[]testHash = pbkdf2(password, salt, PBKDF2_ITERATIONS, hash.length);

                   //both hashes match.:比较密钥是否相等

                   returnslowEquals(hash, testHash);

         }

 

         /**

          *

          * @描述:比较两个字节数组的值是否相等

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午10:07:46

          * @param a

          * @param b

          * @return

          */

         privatestatic boolean slowEquals(byte[] a, byte[] b) {

                   intdiff = a.length ^ b.length;

                   for(int i = 0; i < a.length && i < b.length; i++)

                            diff|= a[i] ^ b[i];

                   returndiff == 0;

         }

 

         /**

          *

          * @描述:使用pbkdf2算法生成加盐hash字节数组

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午10:11:02

          * @param password

          * @param salt

          * @param iterations

          * @param bytes

          * @return

          * @throws NoSuchAlgorithmException

          * @throws InvalidKeySpecException

          */

         privatestatic byte[] pbkdf2(char[] password, byte[] salt, int iterations,

                            intbytes) throws NoSuchAlgorithmException, InvalidKeySpecException {

                   //PBEKeySpec:

                   //带有生成可变密钥大小的 PBE 密码的 PBEKey 时使用的一个密码、salt、迭代计数以及导出密钥长度的构造方法。如果指定

                   //password 为 null,则使用一个空 char[]。

                   //注:在将 password 和 salt 存储进新的 PBEKeySpec 对象前将其复制。

                   //参数:

                   //password - 密码。

                   //salt - salt。

                   //iterationCount - 迭代计数。

                   //keyLength - 要导出的密钥长度。

                   PBEKeySpecspec = new PBEKeySpec(password, salt, iterations, bytes * 8);

                   //SecretKeyFactory此类表示秘密密钥的工厂。

                   SecretKeyFactoryskf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);

                   //skf.generateSecret(spec)根据提供的密钥规范(密钥材料)生成 SecretKey 对象。

                   //SecretKey.getEncoded()返回基本编码格式的密钥,如果此密钥不支持编码,则返回 null。返回字节数组

                   returnskf.generateSecret(spec).getEncoded();

         }

 

         /**

          *

          * @描述:将十六进制字符串转换为字节数组

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午10:11:15

          * @param hex

          * @return

          */

         privatestatic byte[] fromHex(String hex) {

                   byte[]binary = new byte[hex.length() / 2];

                   for(int i = 0; i < binary.length; i++) {

                            binary[i]= (byte) Integer.parseInt(

                                               hex.substring(2* i, 2 * i + 2), 16);

                   }

                   returnbinary;

         }

 

         /**

          *

          * @描述:将字节数组转换为十六进制字符串

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午10:11:22

          * @param array

          * @return

          */

         privatestatic String toHex(byte[] array) {

                   BigIntegerbi = new BigInteger(1, array);

                   Stringhex = bi.toString(16);

                   intpaddingLength = (array.length * 2) - hex.length();

                   if(paddingLength > 0)

                            returnString.format("%0" + paddingLength + "d", 0) + hex;

                   else

                            returnhex;

         }

 

         /**

          *

          * @描述:main方法测试

          * @创建人:wyait

          * @创建时间:2017年4月24日 上午10:23:53

          * @param args

          */

         publicstatic void main(String[] args) {

                   try{

                            //Print out 10 hashes

                            for(int i = 0; i < 10; i++) {

                                     Longs = System.currentTimeMillis();

                                     String salt = HashEncrypt.createSalt();

                                     //System.out.println(HashEncrypt.createHash("p\\r\\nassw0Rd!"));

                                     Longe = System.currentTimeMillis();

                                     System.out.println("生成加盐hash后密钥:第" +i + "次:hashPWd:"

                                                        +HashEncrypt.createHash(salt, "12003p\\r\\nassw0Rd!")

                                                        +",生成密码所用毫秒值:" + (String.valueOf(e - s)));

                            }

                            //Test password validation

                            booleanfailure = false;

                            System.out.println("Runningtests...");

                            for(int i = 0; i < 200; i++) {

                                     Longs = System.currentTimeMillis();

                                     Stringsalt = HashEncrypt.createSalt();

                                     Stringpassword = "" + i;

                                     Stringhash = createHash(salt, password);

                                     Stringsalt1 = HashEncrypt.createSalt();

                                     Longe = System.currentTimeMillis();

                                     System.out.println("password第一次加盐hash:"+ hash + ",生成密码所用毫秒值:"

                                                        +(String.valueOf(e - s)));

                                     StringsecondHash = createHash(salt1, password);

                                     System.out.println("password第二次加盐hash:"+ secondHash);

                                     //两个不同盐的hash密码比较

                                     if(hash.equals(secondHash)) {

                                               //System.out.println("FAILURE: TWO HASHES ARE EQUAL!");

                                               System.out.println("password="+ i

                                                                 +",加盐hash后生成的两个密钥相等!就不可用!");

                                               failure= true;

                                     }

                                     StringwrongPassword = "" + (i + 1);

                                     //使用其他密码是hash比对

                                     if(validatePassword(salt1, wrongPassword, hash)) {

                                               //System.out.println("FAILURE: WRONG PASSWORD ACCEPTED!");

                                               System.out.println("hash="+ hash + ",wrongPassword="

                                                                 +wrongPassword + ",校验密钥匹配!就不可用!");

                                               failure= true;

                                     }

                                     //用salt+password和使用它们生成的hash比对是否相等

                                     if(!validatePassword(salt, password, hash)) {

                                               System.out.println("FAILURE:GOOD PASSWORD NOT ACCEPTED!");

                                               System.out.println("hash="+ hash + ",password=" + password

                                                                 +",校验密钥不匹配,hash就是password生成的!就不可用!");

                                               failure= true;

                                     }

                            }

                            if(failure)

                                     System.out.println("TESTSFAILED!");

                            else

                                     System.out.println("TESTSPASSED!");

                   }catch (Exception ex) {

                            System.out.println("ERROR:" + ex);

                   }

         }

 

}

 


本文转自 wyait 51CTO博客,原文链接:http://blog.51cto.com/wyait/1920128,如需转载请自行联系原作者

上一篇:Android 拦截WebView加载URL,控制其加载CSS、JS资源


下一篇:canvas学习之制作动画