欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

有关加密的那些事儿

程序员文章站 2022-07-12 19:53:24
...

    在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和服务器进行请求数据或者返回数据的认证。