有关加密的那些事儿
在android开发中,与后台进行网络请求,APP端发送有关用户的数据(像密码)或者接口返回有关的用户的数据(sessionKey、passWord)等,会进行处理;以及从文件服务器下载文件,进行文件使用之前,会进行本地的文件“单向散列函数”校验或者“消息认证码校验”,这其中都涉及到加密,接下来一一展开。
一:对称加密
对称加密有很多种,但是AES基本已取代DES和3DES,成为对称加密中最流行的算法。对称加密的定义可以理解为:加密和解密都使用同一把**,如下图所示:
其中普通文本,可以指普通意义上的字符串,也可以指电脑中的任何一个文件。
接下来通过代码的形式,来看下如何用AES进行加密和解密,如下:
AES加密:
1/**
2 * AES进行加密
3 * @param content
4 * @param key 128 256位
5 * @param iv 128位
6 * @param transformation AES/CBC/PKCS5Padding
7 * AES/GCM/NoPadding(该padding模式下AlgorithmParameterSpec不一样)
8 * @return 返回加密的byte数组
9 */
10
11public static byte[] encrypt(byte[] content, byte[] key, byte[] iv, String transformation){
12 try {
13 Cipher cipher = Cipher.getInstance(transformation);
14 SecretKeySpec secretKeySpec = new SecretKeySpec(key,"AES");
15 IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
16 cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec, ivParameterSpec);
17 byte[] encryptBytes = cipher.doFinal(content);
18 return encryptBytes;
19 } catch (Exception e) {
20 Log.e(TAG,"Exception is caught in encrypt, " + e.getMessage());
21 }
22 return new byte[0];
23}
AES解密:
1/**
2 * AES进行解密
3 * @param encrypt
4 * @param key 128 256位
5 * @param iv 128位
6 * @param transformation AES/CBC/PKCS5Padding
7 * AES/GCM/NoPadding(该padding模式下AlgorithmParameterSpec不一样)
8 * @return 返回解密的byte数组
9 */
10public static byte[] decrypt(byte[] encrypt, byte[] key, byte[] iv, String transformation){
11 try {
12 Cipher cipher = Cipher.getInstance(transformation);
13 SecretKeySpec secretKeySpec = new SecretKeySpec(key,"AES");
14 IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
15 cipher.init(Cipher.DECRYPT_MODE,secretKeySpec, ivParameterSpec);
16 byte[] encryptBytes = cipher.doFinal(encrypt);
17 return encryptBytes;
18 } catch (Exception e) {
19 Log.e(TAG,"Exception is caught in encrypt, " + e.getMessage());
20 }
21 return new byte[0];
22}
上图的AES加密和解密,除去加密和解密的数据,有三个参数,key、iv以及transformation(变形或者填充模式),iv俗称盐值,加上填充模式是为了更加安全,在AES中不添加iv,认为是不安全的。
1private static final String TAG = "AESUtilsTest";
2private String content = "wangkaibing";
3private String encrypt;
4private static byte[] iv = new byte[16];
5private static byte[] key = new byte[16];
6
7static {
8 SecureRandom secureRandom = new SecureRandom();
9 secureRandom.nextBytes(iv);
10 secureRandom.nextBytes(key);
11}
12
13/**
14 * AES加解密单元测试
15 */
aaa@qq.com
17public void encryptByAES(){
18 byte[] encryptBytes = AESUtils.encrypt(content.getBytes(), key, iv, AESUtils.CBC);
19 encrypt = HexUtils.byte2hex(encryptBytes);
20 Log.i(TAG,"encyrpt content = " + encrypt);
21 decryptByAES();
22}
23
24public void decryptByAES(){
25 byte[] encryptBytes = HexUtils.hex2byte(encrypt);
26 byte[] decryptBytes = AESUtils.decrypt(encryptBytes, key, iv, AESUtils.CBC);
27 String decryptContent = new String(decryptBytes);
28 Log.i(TAG,"decrypt content = " + decryptContent);
29 assertEquals(content,decryptContent);
30}
下面为测试用例的结果:
6 4377-4393/com.example.wangkaibing.encryptdemo I/AESUtilsTest: encyrpt content = 0D653F50021363EF06A61B2912A733A1
2019-04-06 21:10:35.108 4377-4393/com.example.wangkaibing.encryptdemo I/AESUtilsTest: decrypt content = wangkaibing
对称加密的场景:
1.本地数据加密(加密android中SharedPreferences里面的某些敏感字段)
2.网络传输:登录接口post请求参数加密{userName=lishi,pwd=oJYa4i9VASRoxVLh75wPCg==}
3.加密用户登陆结果信息并反序列化到本地磁盘(将user对象序列化到本地磁盘,下次登录时反序列化到内存里)
4.网页交互数据加密(https)
二:非对称加密
与对称加密只是用一个key不同,非对称加密有公钥(publicKey)和私钥(privateKey),它们是一对,如果用公钥对数据进行加密,那么需要用对应的私钥进行解密;如果用私钥对数据进行加密,那么需要用对应的公钥进行解密。因为私钥和公钥是不同的两个**,所以称为非对称加密。
同样通过代码的形式来进行演示:
传输RSA**对:
1/**
2 * 产生RSA公私钥对,**长度512~2048,默认1024
3 */
4public static KeyPair generateRSAKeyPair(){
5 try {
6 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
7 keyPairGenerator.initialize(1024);
8 keyPair = keyPairGenerator.generateKeyPair();
9 } catch (NoSuchAlgorithmException e) {
10 Log.e(TAG,"NoSuchAlgorithmException is caught in KeyPair, " + e.getMessage());
11 }
12 return keyPair;
13}
RSA加密:
1/**
2 * 用公钥进行加密
3 * @param content
4 * @param publicKey
5 * @return 返回加密后的byte数组
6 */
7public static byte[] encrypt(byte[] content, PublicKey publicKey){
8 try {
9 Cipher cipher = Cipher.getInstance(RSA);
10 cipher.init(Cipher.ENCRYPT_MODE,publicKey);
11 return cipher.doFinal(content);
12 } catch (NoSuchAlgorithmException e) {
13 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
14 } catch (NoSuchPaddingException e) {
15 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
16 } catch (InvalidKeyException e) {
17 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
18 } catch (IllegalBlockSizeException e) {
19 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
20 } catch (BadPaddingException e) {
21 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
22 }
23 return new byte[0];
24}
RSA解密:
1/**
2 * 用私钥进行解密
3 * @param content
4 * @param privateKey
5 * @return 返回解密后的byte数组
6 */
7public static byte[] decrypt(byte[] content, PrivateKey privateKey){
8 try {
9 Cipher cipher = Cipher.getInstance(RSA);
10 cipher.init(Cipher.DECRYPT_MODE,privateKey);
11 return cipher.doFinal(content);
12 } catch (NoSuchAlgorithmException e) {
13 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
14 } catch (NoSuchPaddingException e) {
15 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
16 } catch (InvalidKeyException e) {
17 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
18 } catch (IllegalBlockSizeException e) {
19 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
20 } catch (BadPaddingException e) {
21 Log.e(TAG,"NoSuchAlgorithmException is caught in encrypt, " + e.getMessage());
22 }
23 return new byte[0];
24}
下面通过测试用例来进行验证
1private String content = "wangkaibing";
2
3/**
4 * 单元测试
5 */
aaa@qq.com
7public void encryptTest(){
8 KeyPair keyPair = RSAUtils.generateRSAKeyPair();
9 byte[] encrypt = RSAUtils.encrypt(content.getBytes(), keyPair.getPublic());
10
11 byte[] decrypt = RSAUtils.decrypt(encrypt, keyPair.getPrivate());
12 String decryptContent = new String(decrypt);
13
14 assertEquals(true,content.equals(decryptContent));
15}
非对称加密使用场景:
非对称加密一般不会单独使用,它并不是为了取代对称加密而出现的,非对称加密的速度比对称加密的速度慢很多,极端情况下会慢1000倍,所以一般不会用来加密大量数据,通常会将非对称加密和对称加密结合使用,用非对称加密来给对称加密的**进行加密(即**交换)。
三:单向散列函数(消息摘要)
单向散列函数有一个输入和输出,其中输入称为消息,输出称为散列值。单向散列函数可以根据消息的内容计算出散列值,而散列值就可以被用来检查消息的完整性,这里的消息不一定是文字,也可以是图像文件或者声音文件,例如常见的有MD5 sha1 sha-256等。
相关代码如下:
1/**
2 *
3 * @param content
4 * @param algorithm md5 sha1 sha-256
5 * @return 返回对应数字摘要的字符串(byte数组进行16进制字符串转换)
6 */
7public static String sha(String content, String algorithm){
8 try {
9 MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
10 messageDigest.update(content.getBytes("UTF-8"));
11 byte[] digest = messageDigest.digest();
12 return HexUtils.byte2hex(digest);
13 } catch (NoSuchAlgorithmException e) {
14 Log.e(TAG,"NoSuchAlgorithmException is caught in SHA, " + e.getMessage());
15 }catch (UnsupportedEncodingException e) {
16 Log.e(TAG,"NoSuchAlgorithmException is caught in SHA, " + e.getMessage());
17 }
18 return null;
19}
单元测试代码如下:
1/**
2 * 单元测试
3 */
aaa@qq.com
5public void shaTest(){
6 String md5 = HashUtils.sha(content, "MD5");
7 Log.i(TAG,"md5 = " + md5);
8 String sha1 = HashUtils.sha(content,"sha1");
9 Log.i(TAG,"sha1 = " + sha1);
10 String sha256 = HashUtils.sha(content,"sha-256");
11 Log.i(TAG,"sha-256 = " + sha256);
12}
0-8956/com.example.wangkaibing.encryptdemo I/HashUtilsTest: md5 = A4EA0A4A11A0C488C40220E8C89F208B
2019-04-06 22:21:33.353 8940-8956/com.example.wangkaibing.encryptdemo I/HashUtilsTest: sha1 = 32C54519450685DCE8E33F142C0BC9F175736FBC
2019-04-06 22:21:33.354 8940-8956/com.example.wangkaibing.encryptdemo I/HashUtilsTest: sha-256 = CD74D9D390303003CA8FDBC83D16DF680861F2ABF18FA0B22088E6F7EC2B301A
使用场景:
1.对用户密码进行md5加密后保存到数据库中。
2.软件下载网站 使用消息摘要计算文件指纹,防止篡改。
3.数字签名
四:消息认证码
消息认证码是对消息摘要的一种改良,上面的消息摘要只是确认这个文件的完整性,无法对文件进行认证,即这个消息确实来自张三发出来,不是第三方人冒充伪装发送过来的。
消息认证码(Message Authentication Code)是一种确认完整性并进行认证的技术,包含任意一个长度的消息和一个发送者与接收者之间的共享的**,要计算出MAC值必须持有共享**,它是一种与**相关联的单向单列函数。
相关代码如下:
1/**
2 * 获取文本或文件的消息认证码,用于验证身份或防篡改
3 * @param data 文本或文件的字节数组
4 * @param key 加密key数组
5 * @param algorithm Mac.getInstance支持的算法有:HmacMD5(不推荐)、HmacSHA1、HmacSHA256等等
6 * @return
7 */
8public static byte[] mac(byte[] data, byte[] key, String algorithm){
9 try {
10 Mac mac = Mac.getInstance(algorithm);
11 SecretKeySpec secretKeySpec = new SecretKeySpec(key,algorithm);
12 mac.init(secretKeySpec);
13 byte[] bytes = mac.doFinal(data);
14 return bytes;
15 } catch (NoSuchAlgorithmException e) {
16 Log.e(TAG,"NoSuchAlgorithmException is caught in mac, " + e.getMessage());
17 } catch (InvalidKeyException e) {
18 Log.e(TAG,"NoSuchAlgorithmException is caught in mac, " + e.getMessage());
19 }
20 return new byte[0];
21}
单元测试:
1private static final String TAG = "HMacShaUtilsTest";
2private String content = "wangkaibing";
3private static byte[] key = new byte[16];
4
5static {
6 SecureRandom secureRandom = new SecureRandom();
7 secureRandom.nextBytes(key);
8}
9
10/**
11 * 单元测试
12 */
aaa@qq.com
14public void macTest(){
15 byte[] hmacMD5s = HMacShaUtils.mac(content.getBytes(), key, "HmacMD5");
16 String hmacMD5ToHex = HexUtils.byte2hex(hmacMD5s);
17 Log.i(TAG,"HmacMD5 = " + hmacMD5ToHex);
18
19 byte[] hmacSHA1 = HMacShaUtils.mac(content.getBytes(), key, "HmacSHA1");
20 String hmacSHA1ToHex = HexUtils.byte2hex(hmacSHA1);
21 Log.i(TAG,"HmacSHA1 = " + hmacSHA1ToHex);
22
23 byte[] hmacSHA256 = HMacShaUtils.mac(content.getBytes(), key, "HmacSHA256");
24 String hmacSHA256ToHex = HexUtils.byte2hex(hmacSHA256);
25 Log.i(TAG,"HmacSHA256 = " + hmacSHA256ToHex);
26}
结果如下:
/com.example.wangkaibing.encryptdemo I/HMacShaUtilsTest: HmacMD5 = 7E3BA50F10EC7E06B00031FD3635C26D
2019-04-06 22:36:15.907 9882-9898/com.example.wangkaibing.encryptdemo I/HMacShaUtilsTest: HmacSHA1 = 02BBF3ED7E7D0F2C00153C36A953E1D652B7F777
2019-04-06 22:36:15.908 9882-9898/com.example.wangkaibing.encryptdemo I/HMacShaUtilsTest: HmacSHA256 = B9493CA04F2DFAC71428D357F3F828122C058D5F1E4CA5BFE50EEA36A88DFF12
使用场景:
android中从服务器下载文件就会使用到消息认证码,使用约定好的**,android端对从服务器下载的文件进行消息认证码比对,一致则会使用下载下来的文件,否则删除文件。
五:签名验证
签名验证也是基于RSA非对称加密来实现的,A发送一段消息给B,A发送的消息中会附加上RSA签名,B接收到消息会使用响应的**通过RSA进行验证签名,比对一致才会对消息做进一步的处理。
相关代码如下:
1private static final String RSA = "RSA";
2private static final String TAG = "SignatureUtils";
3private static KeyPair keyPair;
4
5/**
6 * 产生RSA公私钥对,**长度512~2048,默认1024
7 */
8public static KeyPair generateRSAKeyPair(){
9 try {
10 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
11 keyPairGenerator.initialize(1024);
12 keyPair = keyPairGenerator.generateKeyPair();
13 } catch (NoSuchAlgorithmException e) {
14 Log.e(TAG,"NoSuchAlgorithmException is caught in KeyPair, " + e.getMessage());
15 }
16 return keyPair;
17}
18
19/**
20 * 用私钥进行签名
21 * @param content 待签名的byte数组
22 * @param privateKey privateKey 私钥
23 * @param algorithm 签名算法
24 * @return 返回签名后的byte数组
25 */
26public static byte[] signatureByPrivate(byte[] content, PrivateKey privateKey, String algorithm){
27 try {
28 Signature signature = Signature.getInstance(algorithm);
29 signature.initSign(privateKey);
30 signature.update(content);
31 byte[] sign = signature.sign();
32 return sign;
33 } catch (NoSuchAlgorithmException e) {
34 Log.e(TAG,"NoSuchAlgorithmException is caught in KeyPair, " + e.getMessage());
35 } catch (InvalidKeyException e) {
36 Log.e(TAG,"InvalidKeyException is caught in KeyPair, " + e.getMessage());
37 }catch (SignatureException e) {
38 Log.e(TAG,"SignatureException is caught in KeyPair, " + e.getMessage());
39 }
40 return new byte[0];
41}
42
43/**
44 * 返回验证签名的结果
45 * @param content 待签名的源byte数组
46 * @param signatureData 已用私钥进行签名后的byte数组
47 * @param publicKey 公钥
48 * @param algorithm 签名算法
49 * @return true or false
50 */
51public static boolean verifySignature(byte[] content, byte[] signatureData, PublicKey publicKey, String algorithm){
52 try {
53 Signature signature = Signature.getInstance(algorithm);
54 signature.initVerify(publicKey);
55 signature.update(content);
56 boolean result = signature.verify(signatureData);
57 return result;
58 } catch (NoSuchAlgorithmException e) {
59 Log.e(TAG,"NoSuchAlgorithmException is caught in verifySignature, " + e.getMessage());
60 }catch (InvalidKeyException e) {
61 Log.e(TAG,"InvalidKeyException is caught in verifySignature, " + e.getMessage());
62 } catch (SignatureException e) {
63 Log.e(TAG,"SignatureException is caught in verifySignature, " + e.getMessage());
64 }
65 return false;
66}
单元测试:
1private static final String content = "wangkaibing";
2private static final String TAG = "SignatureUtilsTest";
3
4/**
5 * 单元测试
6 */
aaa@qq.com
8public void verifySignatureTest(){
9 KeyPair keyPair = SignatureUtils.generateRSAKeyPair();
10 byte[] md5withRSAS = SignatureUtils.signatureByPrivate(content.getBytes(), keyPair.getPrivate(), "MD5withRSA");
11 String md5Byte2hex = HexUtils.byte2hex(md5withRSAS);
12 boolean result = SignatureUtils.verifySignature(content.getBytes(), md5withRSAS, keyPair.getPublic(), "MD5withRSA");
13 Log.i(TAG,"MD5withRSA result = " + result + ", md5Byte2hex = " + md5Byte2hex);
14
15
16 byte[] SHA256withRSAS = SignatureUtils.signatureByPrivate(content.getBytes(), keyPair.getPrivate(), "SHA256withRSA");
17 String SHA256Byte2hex = HexUtils.byte2hex(md5withRSAS);
18 boolean SHA256Result = SignatureUtils.verifySignature(content.getBytes(), SHA256withRSAS, keyPair.getPublic(), "SHA256withRSA");
19 Log.i(TAG,"SHA256withRSA result = " + SHA256Result + ", SHA256Byte2hex = " + SHA256Byte2hex);
20
21}
结果如下:
2019-04-06 22:50:04.940 10792-10808/com.example.wangkaibing.encryptdemo I/SignatureUtilsTest: MD5withRSA result = true, md5Byte2hex = 331F313F7C4F410DD3048F4763D580B72B0F57866888189DE5254F551B130B269EF06C3B6E6C14639C9DD741118E88D13889DFCFC0CAC30E5CD435BB2DA8793197B9044AB88A4DB628A8F119EF1230BDFABF67006E1482FC726B44D47BB85D6D613C9B0EDCEB9A1A6D417FDC2D8E06B6F558B64D49D836329F293A8322C8E510
2019-04-06 22:50:04.944 10792-10808/com.example.wangkaibing.encryptdemo I/SignatureUtilsTest: SHA256withRSA result = true, SHA256Byte2hex = 331F313F7C4F410DD3048F4763D580B72B0F57866888189DE5254F551B130B269EF06C3B6E6C14639C9DD741118E88D13889DFCFC0CAC30E5CD435BB2DA8793197B9044AB88A4DB628A8F119EF1230BDFABF67006E1482FC726B44D47BB85D6D613C9B0EDCEB9A1A6D417FDC2D8E06B6F558B64D49D836329F293A8322C8E510
使用场景:经常用来android和服务器进行请求数据或者返回数据的认证。