密码学支持

在日常工作中经常需要涉及到对数据的加密、解密以及数据验证等操作,Java提供了一些类来完成加密解密功能。

哈希函数

哈希函数通常用于验证数据指纹,同一个文件或数据,使用同一种哈希函数得到的哈希值一定相同,如果发现对某文件两次哈希得到的结果不一样,就证明文件数据有被篡改过。

MessageDigest

MessageDigest类提供了常用的哈希函数包括SHA-1,SHA-224,SHA-256,SHA-384,SHA-512,MD5算法等。

MD5

import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 加密解密。
 */
public class MessageDigestDemo {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        byte[] digest = messageDigest.digest("hello world".getBytes());
        String encode = HexBin.encode(digest);
        System.out.println(encode);
    }
}

运行结果为
5EB63BBBE01EEED093CB22BB8F5ACDC3

SHA-256

import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
 * 加密解密。
 */

public class MessageDigestDemo {
     public static void main(String[] args) throws NoSuchAlgorithmException {
         MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
         byte[] digest = messageDigest.digest("hello world".getBytes());
         String encode = HexBin.encode(digest);
         System.out.println(encode);
     }
}

运行结果为
B94D27B9934D3E08A52E52D7DA7DABFAC484EFE37A5380EE9088F7ACE2EFCDE9

加密与解密

数据加密是最常见的数据安全保护手段,加密函数和哈希函数不一样,哈希函数不可逆,我们无法通过哈希值还原被哈希的数据。但是被加密的数据需要能解密还原内容,否则加密也就失去了意义。

加密前的数据称为“明文”,被加密后的数据称为“密文”。

在实际进行加密操作过程中,我们需要使用经验证安全可靠的加密算法,并且还需要生成加密所需的密钥。明文数据通过加密算法使用对应的密钥来对数据进行加密,从而得到密文。

即便知道了密文加密使用的加密算法,没有密钥的情况下,也无法在有限的时间内破解加密数据获取原文。

AES加密、解密数据

AES 加密的参数及其条件

密钥:加密的时候用秘钥,解密的时候需要同样的密钥才能解出来,密钥必须是16位字节或者24位字节或者32位字节(因为python3的字符串是unicode编码,需要encode才可以转换成字节型数据)

明文:需要加密的内容,字节长度需要是16位的倍数

模式:AES加密常用的有ECB和CBC模式

iv偏移量:这个参数在ECB模式下不需要,在CBC模式下需要

AES ECB模式

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

/**
 * AES ECB模式例子。
 */
public class AESECBDemo {
     private static final int KEY_LENGTH_16 = 16;
     private static final int KEY_LENGTH_32 = 32;
     private static final String AES_ECB_PKCS_5_PADDING = "AES/ECB/PKCS5Padding";
     private static final String AES = "AES";
     public static void main(String[] args) throws Exception {
         //加密key长度需要为16位或32位
         String key = "11112222333344441111222233334444";
         //待加密内容
         String content = "hello world";
         System.out.println("加密前的内容:" + content);
         AESECBDemo aesEncodeDemo = new AESECBDemo();
         String encrypted = aesEncodeDemo.encryptByECB(key, content);
         System.out.println("加密后的内容:" + encrypted);
         String decrypt = aesEncodeDemo.decryptByECB(key, encrypted);
         System.out.println("解密后的内容:" + decrypt);
     }
     /**
      * AES ECB模式加密
      * @param key     加密的秘钥
      * @param content 待加密内容
      * @return 加密后的内容
      * @throws Exception 异常信息
      */
     public String encryptByECB(String key, String content) throws Exception {
         checkKey(key);
         SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
         //AES/ECB/PKCS5Padding 格式为 "算法/模式/补码方式"
         Cipher cipher = Cipher.getInstance(AES_ECB_PKCS_5_PADDING);
         //设置加密模式,加密的key
         cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
         //加密
         byte[] bytes = cipher.doFinal(content.getBytes());
         //base64编码进行二次加密
         return new BASE64Encoder().encode(bytes);
     }
     
     /**
      * 检查key是否合法
      *
      * @param key {@link String}秘钥信息
      * @throws Exception 异常信息
      */
     private void checkKey(String key) throws Exception {
         if (key == null || key.length() != KEY_LENGTH_16 && key.length() != KEY_LENGTH_32) {
             throw new Exception("加密秘钥不正确");
         }
     }

     /**
      \* AES ECB模式解密
      \* @param key     加密的秘钥
      \* @param encrypt 加密后的内容
      \* @return 解密后的内容
      \* @throws Exception 异常信息
      */
     public String decryptByECB(String key, String encrypt) throws Exception {
         checkKey(key);
         SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
         //AES/ECB/PKCS5Padding 格式为 "算法/模式/补码方式"
         Cipher cipher = Cipher.getInstance(AES_ECB_PKCS_5_PADDING);
         //设置为解密模式,解密的key
         cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
         //base64解密
         byte[] decodeBuffer = new BASE64Decoder().decodeBuffer(encrypt);
         //aes解密
         byte[] bytes = cipher.doFinal(decodeBuffer);
         return new String(bytes);
     }
}

输出内容如下

加密前的内容:hello world

加密后的内容:E8QK4xNrtTlkU5Fh2rMNqg==

解密后的内容:hello world

AES CBC模式

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

/**
 * AES CBC模式例子。
 */
public class AESCBCDemo {
     private static final int KEY_LENGTH_16 = 16;
     private static final int KEY_LENGTH_32 = 32;
     private static final String AES = "AES";
     private static final String AES_CBC_NO_PADDING = "AES/CBC/NoPadding";
     public static void main(String[] args) throws Exception {
         //加密key长度需要为16位或32位
         String key = "1111222233334444";
         //偏移矢量,必须为16位
         String iv = "5555666677778888";
         //待加密内容
         String content = "hello world";
         System.out.println("加密前的内容:" + content);
         AESCBCDemo aescbcDemo = new AESCBCDemo();
         String encrypted = aescbcDemo.encryptByCBC(key, content, iv);
         System.out.println("加密后的内容:" + encrypted);
         String decrypt = aescbcDemo.decryptByCBC(key, encrypted, iv);
         System.out.println("解密后的内容:" + decrypt);
     }
     
     /**
      * AES ECB模式加密
      * @param key     加密的秘钥
      * @param content 待加密内容
      * @param iv      偏移矢量
      * @return 加密后的内容
      * @throws Exception 异常信息
      */
     public String encryptByCBC(String key, String content, String iv) throws Exception {
         checkKey(key);
         checkIV(iv);
         SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
         //AES/ECB/PKCS5Padding 格式为 "算法/模式/补码方式"
         Cipher cipher = Cipher.getInstance(AES_CBC_NO_PADDING);
         //加密内容长度必须要为blockSize的整数倍
         int blockSize = cipher.getBlockSize();
         int contentLength = content.getBytes().length;
         if (contentLength % blockSize != 0) {
             contentLength = contentLength + (blockSize - (contentLength % blockSize));
         }
         byte[] newBytes = new byte[contentLength];
         System.arraycopy(content.getBytes(), 0, newBytes, 0, content.getBytes().length);
         //偏移矢量
         IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
         //设置加密模式,加密的key,偏移矢量
         cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
         //加密
         byte[] bytes = cipher.doFinal(newBytes);
         //base64编码进行二次加密
         return new BASE64Encoder().encode(bytes);
     }
     
     /**
      * 检查key是否合法
      * @param key {@link String}秘钥信息
      * @throws Exception 异常信息
      */
     private void checkKey(String key) throws Exception {
         if (key == null || key.length() != KEY_LENGTH_16 && key.length() != KEY_LENGTH_32) {
             throw new Exception("加密秘钥不正确");
         }
     }
     
     /**
      * 检查偏移矢量是否合法
      * @param iv {@link String} 偏移矢量
      * @throws Exception 异常信息
      */
     private void checkIV(String iv) throws Exception {
         if (iv == null || iv.length() != KEY_LENGTH_16) {
             throw new Exception("偏移矢量不正确,必须为16位");
         }
     }
     
     /**
      * AES ECB模式解密
      * @param key     加密的秘钥
      * @param encrypt 加密后的内容
      * @param iv      偏移矢量
      * @return 解密后的内容
      * @throws Exception 异常信息
      */
     public String decryptByCBC(String key, String encrypt, String iv) throws Exception {
         checkKey(key);
         checkIV(iv);
         SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
         //AES/ECB/PKCS5Padding 格式为 "算法/模式/补码方式"
         Cipher cipher = Cipher.getInstance(AES_CBC_NO_PADDING);
         //偏移矢量
         IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
         //设置为解密模式,解密的key,偏移矢量
         cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
         //base64解密
         byte[] decodeBuffer = new BASE64Decoder().decodeBuffer(encrypt);
         //aes解密
        byte[] bytes = cipher.doFinal(decodeBuffer);
        return new String(bytes);
    }
}