Java中的散列算法、消息摘要算法、加密算法总结


Bouncy Castle 官网

BASE64

  • 在JDK1.6之前,JDK核心一直没有Base64的实现类,在JDK1.6中添加了另一个Base64的实现:javax.xml.bind.DataTypeConverter两个静态方法printBase64Binary和parseBase64Binary,由于是隐藏在javax.xml.bind包下面,不被很多开发者知道。在JDK8的java.util包下面实现了新的BASE64编解码API,而且性能比JDK1.6中的性能更高。

  • 用途:BASE64的用处主要不是加密,而是为了传输,如图片、声音等的网络传输。

  • JDK8中的BASE64实现了三种:

    • 基本的BASE64:将数据编码成只含有{‘A’-‘Z’,’a’-‘z’,’0’-‘9’,’+’,’/‘}这64个字符,’=’用于填充,编码的方法是,讲输入数据流每次取6bit,用次6bit的值(0-63)作为索引去在64个字符表中查找,输出相应的字符,结果就是把3个字节编码成了4个字节(3x8=4x6),不满4个字符的以’=’填充。
    • URL的BASE64:基于基本的BASE64,为了URL的安全,把’/‘以‘_’替代。
    • MIME的BASE64:基于基本的BASE64,对MIME的格式友好,每行输出不超过76个字符且以’\r\n’结束,超出76则换行。

示例,更详细代码可查看这里

// JDK6的 base64
String jdk6EncodeRes = javax.xml.bind.DatatypeConverter.printBase64Binary(src.getBytes());
System.out.println("jdk6 base64 encode:" + jdk6EncodeRes);
String jdk6DecodeRes = new String(javax.xml.bind.DatatypeConverter.parseBase64Binary(jdk6EncodeRes));
System.out.println("jdk6 base64 decode:" + jdk6DecodeRes);

// JDK8基本的 base64
// jdk 基本的 base64 编码
String encodeRes = Base64.getEncoder().encodeToString(src.getBytes());
System.out.println("\r\njdk base64 encode:" + encodeRes);
// jdk 基本的 base64 解码
String decodeRes = new String(Base64.getDecoder().decode(encodeRes));
System.out.println("jdk base64 decode:" + decodeRes);

// JDK URL base64编码:使用下划线代替基本的64个字符的/
// jdk URL的 base64 编码
encodeRes = Base64.getUrlEncoder().encodeToString(src.getBytes());
System.out.println("\r\njdk url base64 encode:" + encodeRes);
// jdk URL的 base64 解码
decodeRes = new String(Base64.getUrlDecoder().decode(encodeRes));
System.out.println("jdk url base64 decode:" + decodeRes);

// JDK MIME
// base64编码:使用基本BASE64输出,而且对MIME格式友好:每一行输出不超过76个字符,而且每行以“\r\n”符结束
// jdk MIME的 base64 编码
encodeRes = Base64.getMimeEncoder().encodeToString(src.getBytes());
System.out.println("\r\njdk MIME base64 encode:" + encodeRes);
// jdk MIME的 base64 解码
decodeRes = new String(Base64.getMimeDecoder().decode(encodeRes.getBytes()));
System.out.println("jdk MIME base64 decode:" + decodeRes);

摘要算法(散列值)

  • 摘要算法主要用于验证数据的完整性,有MD系列(MD2、MD4、MD5)、SHA、MAC(HMAC)等

  • 概要

    MD(Message Digest Algorithm)系列长度都是128,由于MD已经不安全了,就开始升级到SHA(Secure Hash Algorithm),SHA大体分两种SHA1和SHA2(SHA-224,SHA-256,SHA-384,SHA-512),长度如下图。

MD系列

在这里插入图片描述

SHA系列

在这里插入图片描述

  • MAC(Message Authentication Code)是结合MD和SHA结合的带有KEY的算法,因此又称为HMAC(keyed-Hash Message Authentication Code),因此分为MD系列(HmacMD2,HmacMD4,HmacMD5)和SHA系列(HmacSHA1,HmacSHA224,HmacSHA256,HmacSHA384,HmacSHA512).

    HMAC系列

    摘要算法SHA系列

代码示例,具体可查看这里

private static String src = "com.zhaiqianfeng";

public static void main(String[] args) {
	jdkmd5();
	jdkSHA1();
	jdkSHA2();
	jdkMAC();
}

// jdk md5
public static void jdkmd5() {
	try {
		MessageDigest md = MessageDigest.getInstance("MD5");
		byte[] mdRes = md.digest(src.getBytes());
		String md5Base64 = Base64.getEncoder().encodeToString(mdRes);
		// 使用jdk8自带的base64编码显示
		System.out.println("jdk md5 base64编码显示::" + md5Base64);

		String md5BCHex = Hex.toHexString(mdRes);
		// 使用BC来转换十六进制来显示
		System.out.println("jdk md5 Hex显示:" + md5BCHex);
	} catch (NoSuchAlgorithmException e) {
		e.printStackTrace();
	}
}

// jdk sha1
public static void jdkSHA1() {
	try {
		MessageDigest md = MessageDigest.getInstance("SHA");
		byte[] mdRes = md.digest(src.getBytes());
		String shaBase64 = Base64.getEncoder().encodeToString(mdRes);
		System.out.println("\r\njdk sha base64编码显示:" + shaBase64);
		String shaBCHex = Hex.toHexString(mdRes);

		System.out.println("jdk sha Hex显示:" + shaBCHex);
	} catch (NoSuchAlgorithmException e) {
		e.printStackTrace();
	}
}

// jdk sha2
public static void jdkSHA2() {
	try {
		MessageDigest md = MessageDigest.getInstance("SHA-512");
		byte[] mdRes = md.digest(src.getBytes());
		String shaBase64 = Base64.getEncoder().encodeToString(mdRes);
		System.out.println("\r\njdk sha-512 base64编码显示:" + shaBase64);
		String shaBCHex = Hex.toHexString(mdRes);

		System.out.println("jdk sha-512 Hex显示:" + shaBCHex);
	} catch (NoSuchAlgorithmException e) {
		e.printStackTrace();
	}
}

// jdk mac
public static void jdkMAC() {
	try {
		//产生秘钥
		KeyGenerator keyGenerator=KeyGenerator.getInstance("HmacMD5");
		SecretKey secretKey=keyGenerator.generateKey();
		byte[] key=secretKey.getEncoded();
	
		//对原始秘钥进行特性处理
		SecretKey restoreSecretKey=new SecretKeySpec(key,"HmacMD5");
		Mac mac=Mac.getInstance(restoreSecretKey.getAlgorithm());
		//设置MAC秘钥
		mac.init(restoreSecretKey);
		//编码
		byte[] macRes=mac.doFinal(src.getBytes());
	
		System.out.println("\r\njdk HmacMD5 key:"+Hex.toHexString(key)+"\r\njdk HmacMD5 result:"+Hex.toHexString(macRes));
	} catch (NoSuchAlgorithmException | InvalidKeyException e) {
		e.printStackTrace();
	}

}

对称加密

DES

DES算法中只用到64位密钥中的其中56位,而第8、16、24、…64位8个位并未参与DES运算,这一点,向我们提出了一个应用上的要求,即DES的安全性是基於除了8,16,24,…64位外的其餘56位的组合变化256才得以保证的。因此,在实际应用中,我们应避开使用第8,16,24,…64位作為有效数据位,而使用其它的56位作為有效数据位,才能保证DES算法安全可靠地发挥作用。如果不瞭解这一点,把密钥Key的8,16,24,… .64位作為有效数据使用,将不能保证DES加密数据的安全性,对运用DES来达到保密作用的系统產生数据被破译的危险,这正是DES算法在应用上的误区。

  • 分组加密(DES 只能一次性加密 64 位明文,如果明文超过了 64 位,就要进行分组加密。反复迭代)
  • 密钥长度:64位(有效长度56位)
  • 分组加密快大小:64位(8字节),如果填充模式为NoPadding加密的data字节数必须是8的倍数,否则会报错。。
  • 最大加密明文长度:无限制
  • 密文长度:DES 算法把 64 位的明文输入块变为 64 位的密文输出块,它所使用的密钥也是 64 位。
    记住:如果是有padding,如果是明文如果是8字节的整数倍的话,加密的密文长度肯定要增加8个字节,因为要有标识。

å¯¹ç§°åŠ å¯†ä¹‹DES

DESede

DESed是DES向AES过渡的加密算法,它使用3条56位的密钥对数据进行三次加密。
该方法使用两个密钥对明文进行加解密:

  • 加密过程:加密-解密-加密
  • 解密过程:解密-加密-解密

采用两个密钥进行三重加密的好处有:

  • 两个密钥合起来有效密钥长度有112bit,可以满足商业应用的需要,若采用总长为168bit的三个密钥,会产生不必要的开销。
    å¯¹ç§°åŠ å¯†ä¹‹DESede
    注意:
    java中只提供了3倍长3des的算法,也就是key的长度必须是24字节,如果想要使用2倍长3des,需要自己将后8个字节补全(就是将16个字节的前8个字节补到最后,变成24字节)。如果提供的key不足24字节,将会报错,如果超过24字节,将会截取前24字节作为key。
AES
  • 对称分组加密。

  • 密钥长度:128、192、256位。

  • 分组加密快大小:128位(16字节),如果填充模式为NoPadding加密的data的byte字节数必须为16的倍数,否则会报错。

  • 最大加密明文长度:无限制。

  • 密文长度:

    • 1、在原始数据长度为 16 的整数倍时
      • 假如原始数据长度等于 16xN,则使用 NoPadding 时加密后数据长度等于 16xN,其它情况下加密数据长度等于 16*(N+1)。其中1是一个数据块的大小用来标志填充。
    • 2、在不足 16 的整数倍的情况下
      • 假如原始数据长度等于 16N+M [其中 M 小于16],除了 NoPadding 填充之外的任何方式,加密数据长度都等于 16(N+1);
      • 其中 NoPadding 填充情况下,CBC、ECB 和 PCBC 三种模式是不支持的,CFB、OFB 两种模式下则加密数据长度等于原始数据长度。

    在这里插入图片描述å¯¹ç§°åŠ å¯†ä¹‹AES

PBE
  • PBE(Password Based Encryption,基于口令加密)是一种基于口令的加密算法,其特点是使用口令代替了密钥,而口令由用户自己掌管,采用随机数(这里称之为 盐 或者 扰码)杂凑多重加密等方法保证数据的安全性。PBE 算法并没有 真正构建新的加密/解密算法,而是对已知的对称加密算法(eg:DES算法)做了包装;使用PBE算法对数据做加密/解密操作时,其实是使用了 DES 或者 AES 等其他对称加密算法做了相应的操作。
    在这里插入图片描述
代码示例
  1. 创建规范密钥(两种方式通过已有密码转换创建新的)
    ➤ 通过指定密码转换成规范密钥SecretKey(有两种实现方式SecretKeySpeSecretKeyFactory区别见文末扩展《加密算法中SecretKeyFactory、KeyGenerator、KeyPairGenerator区别 》)
    注意
    指定的密码长度需要满足当前算法支持的密钥长度,比如DESed:24字节;AES:16、24、32字节
    //24字节字符串形式密码
    String password = "aac9^*sds(12z88vsd+d03f.";  
    //24字节十六进制形式密码
    //String password = "616163395E2A7364732831327A38387673642B643033662E" 
    
    //方式一:使用 SecretKeySpe 转换(独立于Provider)支持AES
    private SecretKey convertSecretKeyUseSecretKeySpe(String password) {
      try {
          //1.将24字节的字符串密码转换成字节数组
          byte[] passwordByte = password.getBytes("UTF-8");
          //1.将24字节的十六进进制密码转换成字节数组
          //byte[] passwordByte = hexString2byte(password);
          //2.将密码字节数组转换成指定算法专用的规范密钥
          SecretKey secretKey = new SecretKeySpec(passwordByte, "DESede");  //AES
          return secretKey;
      } catch (Exception e) {
          return null;
      }
    }
    
    //方式二:使用 SecretKeyFactory 转换(依赖算法提供商Provider,例如:SunJCE不支持AES算法)
    private SecretKey convertSercetKeyUseSecretKeyFactory(String password) {
      try {
          //1.将24字节的字符串密码转换成字节数组
          byte[] passwordByte = keyStr.getBytes("UTF-8");
          //1.将24字节的十六进进制密码转换成字节数组
          //byte[] passwordByte = hexString2byte(keyStr);
          //2.创建DESedeKeySpec对象(通过已有的密码字节数组)
          DESedeKeySpec deSedeKeySpec = new DESedeKeySpec(passwordByte);
          //3.创建密钥工厂SecretKeyFactory实例对象(指定算法名称)
          SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DESede");
          //4.转换成规范密钥
          SecretKey secretKey = secretKeyFactory.generateSecret(deSedeKeySpec);
          //5.将规范密钥转换成DES算法专用的规范密钥
          SecretKeySpec secretKeySpec = new SecretKeySpec (secretKey.getEncoded(), "DESede");
          return secretKeySpec;
      } catch (Exception e) {
          return null;
      }
    }
    
    ➤ 通过随机源(SecureRandom)创建新的规范密钥SecretKey(使用KeyGenerator可指定随机源种子生成)
    //方式一:不指定随机源种子创建规范密钥(每次随机数都不同,生成的规范密钥都也不同,需要保存加密使用的密钥,否会解密失败)
    public SecretKey createSecretKeyFromRandom() {
        try {
            //1.1 创建密钥管理器(指定算法名称)
            KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");//AES
            //1.2 初始化密钥管理器(指定密钥长度,需要根据当前算法支持密钥长度选择,不可随意)
            keyGenerator.init(112);    //这里默认使用算法提供商(或系统默认)随机数。
            //1.3 生成规范密钥SecrectKey对象
            SecretKey secretKey = keyGenerator.generateKey();
            //1.4 生成指定算法的专用密钥
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "DESede");//AES
            return secretKeySpec;
        } catch (Exception e) {
            return null;
        }
    }
    //方式二 指定随机源种子创建新的规范密钥(只要种子不变,生成的随机数就不变,因此规范密钥也不变)
    String randomSeed = "faishdfcas";
    public SecretKey createSecretKeyFromSeed(String randomSeed) {
        try {
            //1. 创建密钥管理器(指定算法名称)
            KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede"); //AES
            //2. 创建随源并将我们指定的字符串作为种子
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(randomStr.getBytes("UTF-8"));
            //3. 初始化密钥管理器(指定密钥大小 、随机源),注意密钥大小需要根据当前算法支持的长度选择。
            keyGenerator.init(112, secureRandom);
            //4. 生成SecretKey密钥对象
            SecretKey secretKey = keyGenerator.generateKey();
            //5. 通过SecretKey生成DESede专用密钥对象
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "DESede");//AES
            return secretKeySpec;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
  2. 加解密(如果使用JCR之外的算法模式参见文末BouncyCastle使用)
    ➤ 使用已有的密码转换成规范密钥SecretKey进行加解密
    //加密
    public String Encrypt(String data, String password) {
        try {
            //1. 将现有明密码换成规范密钥SecretKey对象(两种方式都可以,注意SecretKeyFactory使用局限性)
            //SecretKey secretKey = convertSercetKeyUseSecretKeyFactory(password);
            SecretKey secretKey = convertSecretKeyUseSecretKeySpe(password);
            //1.2. 注册内容提供者BC(如果使用JCE之外的算法)
            //Security.addProvider(new BouncyCastleProvider());
            //2. 创建Cipher对象(指定具体加密算法)
            Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); //AES/CBC/PKCS5Padding
            //3. 初始化加密向量(仅CBC模式使用)
            IvParameterSpec iv = new IvParameterSpec(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
            //4. 初始化加密算法(工作模式+密钥对象+向量)
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
            //5. 执行加密
            byte[] response = cipher.doFinal(data.getBytes("UTF-8"));
            //6. 将加密后的字节数组转换为十六进制字符串
            return byte2HexString(response);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    //解密
    public String Decrypt(String data, String password) {
        try {
            //1. 将现有明密码换成规范密钥SecretKey对象(两种方式都可以,注意SecretKeyFactory使用局限性)
            //SecretKey secretKey = convertSercetKeyUseSecretKeyFactory(password);
            SecretKey secretKey = convertSecretKeyUseSecretKeySpe(password);
            //1.2. 注册内容提供者BC(如果使用JCE之外的算法)
            //Security.addProvider(new BouncyCastleProvider());
            //2. 创建Cipher对象(指定具体加密算法)
            Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); //AES/CBC/PKCS5Padding
            //3. 初始化加密向量(仅CBC模式使用)
            IvParameterSpec iv = new IvParameterSpec(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
            //4 初始化Cipher对象(工作模式+密钥对象+向量)
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
            //3.5 先将十六进制字符串密文转换为字节数组
            byte[] encryptData = hexString2byte(data);
            //3.6 执行解密
            byte[] result = cipher.doFinal(encryptData);
            //3.7 最后还原为字符串
            return new String(result, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    //测试
    public static void main(String[] args){
        //24字节长度密码
    	String password = "aac9^*sds(12z88vsd+d03f.";
    	String data ="test enctypt and decrypt!"
    	String encryptData = Encrypt(data,password);
    	String decryptData = Decrypt(encryptData,password);
    	System.out.println("encryptData:" + encryptData);
    	System.out.println("decryptData:" + decryptData);
    }
    //输出结果
    encryptData:
        1c321ff2a72498e656058597b75c7697a128d8ea3fd78f5ff16a73d31ab75eeebcb457f8d0bd6ef7
    decryptData:
        test enctypt and decrypt!
    
    ➤ 指定随机源种子创建规范密钥SecretKey进行加解密
    //加密
    public String Encrypt(String data, String randomSeed) {
        try {
            //1. 通过指定随机数种子创建规范密钥SecretKey
            SecretKey secretKey = createSecretKeyFromSeed(randomSeed);
            //1.2. 注册内容提供者BC(如果使用JCE之外的算法)
            //Security.addProvider(new BouncyCastleProvider());
            //2. 创建Cipher对象(指定具体加密算法)
            Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); //AES/CBC/PKCS5Padding
            //3. 初始化加密向量(仅CBC模式使用)
            IvParameterSpec iv = new IvParameterSpec(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
            //4. 初始化加密算法(工作模式+密钥对象+向量)
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
            //5. 执行加密
            byte[] response = cipher.doFinal(data.getBytes("UTF-8"));
            //6. 将加密后的字节数组转换为十六进制字符串
            return byte2HexString(response);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    //解密
    public String Decrypt(String data, String randomSeed) {
        try {
            //1. 通过指定随机数种子创建规范密钥SecretKey
            SecretKey secretKey = createSecretKeyFromSeed(randomSeed);
            //1.2. 注册内容提供者BC(如果使用JCE之外的算法)
            //Security.addProvider(new BouncyCastleProvider());
            //2. 创建Cipher对象(指定具体加密算法)
            Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); //AES/CBC/PKCS5Padding
            //3. 初始化加密向量(仅CBC模式使用)
            IvParameterSpec iv = new IvParameterSpec(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
            //4 初始化Cipher对象(工作模式+密钥对象+向量)
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
            //5 先将十六进制字符串密文转换为字节数组
            byte[] encryptData = hexString2byte(data);
            //6 执行解密
            byte[] result = cipher.doFinal(encryptData);
            //7 最后还原为字符串
            return new String(result, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    //测试
    public static void main(String[] args){
        //任意字符串作为随机源种子用于创建新的规范密钥SecretKey
    	String randomSeed = "zcmain12345";
    	String data ="test enctypt and decrypt!"
    	String encryptData = Encrypt(data,randomSeed);
    	String decryptData = Decrypt(encryptData,randomSeed);
    	System.out.println("encryptData:" + encryptData);
    	System.out.println("decryptData:" + decryptData);
    } 
    //输出结果
    encryptData:
        f44e44cf153388ab7314809502333638f00a89e5258e80b5d53f621c0be416d292e1afb95e88272d
    decryptData:
        test enctypt and decrypt!
    
    ➤ 不指定随机源种子创建规范密钥SecretKey进行加解密
    注意
    由于规范密钥创建是使用未指定种子的随机数算法,每次产生的随机源不一样,因此创建的规范密钥每次都不同,需要将加密使用的规范密钥保存用于解密(即加解密需使用同一个随机源创建的规范密钥),否则无法解密。
    /**
    *加密
    *@param data 加密的明文数据
    *@param secretKey 加密使用的规范密钥 (随机生成,每次都不一样)
    */
    public String Encrypt(String data,SecretKey secretKey) {
         try {
             //. 注册内容提供者BC(如果使用JCE之外的算法)
             //Security.addProvider(new BouncyCastleProvider());
             //1. 创建Cipher对象(指定具体加密算法)
             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); //AES/CBC/PKCS5Padding
             //2. 初始化加密向量(仅CBC模式使用)
             IvParameterSpec iv = new IvParameterSpec(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
             //3. 初始化加密算法(工作模式+密钥对象+向量)
             cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
             //4. 执行加密
             byte[] response = cipher.doFinal(data.getBytes("UTF-8"));
             //5. 将加密后的字节数组转换为十六进制字符串
             return byte2HexString(response);
         } catch (Exception e) {
             e.printStackTrace();
             return null;
         }
     }
     
    /**
    *解密
    *@param data 解密的密文数据
    *@param secretKey 加密使用的规范密钥 
    *PS:使用加密时创建的规范密钥(因为加密使用随机源创建的规范密钥未指定种子,
    *因此解密不可再次创建会导致与加密创建的密钥不一致,从而解密失败)
    */
    public String Decrypt(String data,SecretKey secretKey) {
         try {
             //注册内容提供者BC(如果使用JCE之外的算法)
             //Security.addProvider(new BouncyCastleProvider());
             //1. 创建Cipher对象(指定具体加密算法)
             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); //AES/CBC/PKCS5Padding
             //2. 初始化加密向量(仅CBC模式使用)
             IvParameterSpec iv = new IvParameterSpec(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
             //3 初始化Cipher对象(工作模式+密钥对象+向量)
             cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
             //4 先将十六进制字符串密文转换为字节数组
             byte[] encryptData = hexString2byte(data);
             //5 执行解密
             byte[] result = cipher.doFinal(encryptData);
             //6 最后还原为字符串
             return new String(result, "utf-8");
         } catch (Exception e) {
             e.printStackTrace();
             return null;
         }
     }
     //测试
    public static void main(String[] args){
            //不指定随机种子生成规范密钥(每次都不同,因此加解密需要使用同一个)
    	    SecretKey secretKey = createSecretKeyFromRandom();
    		String data ="test enctypt and decrypt!"
    		String encryptData = Encrypt(data,secretKey);
    		String decryptData = Decrypt(encryptData,secretKey);
    		System.out.println("encryptData:" + encryptData);
    		System.out.println("decryptData:" + decryptData);
    } 
    //输出结果
     encryptData:
         ade53eff21e4da363889968fc35bf3a36dc450c90b18520c9b379e5a045bab479c556059bd0ed7a9
     decryptData:
         test enctypt and decrypt!
    

非对称加密

DH(Diffie-Hellman)秘钥交换算法,和RSA基于因子分解的算法,在非堆成算法中有些同时支持公钥加密私钥解密和私钥加密公钥解密,有些只支持其中一种。RSA既能作为数据加密也可以用于数字签名。DH算法原理:B方依据A方的公钥生成自己的密钥对,然后再利用自己的密钥对生成一个秘钥P,A方收到B方的公钥,再利用B方的这个个公钥的X509规范生成一个自己的公钥,然后利用此公钥和之前自己的私钥构建一个秘钥Q,秘钥Q=秘钥P,然后双方根据这个相同的秘钥进行堆成加解密。

RSA
  • 非对称公钥加密。
  • 密钥长度:64整倍数(512、1024、2048位)。
  • 最大加密明文长度:与密钥长度和填充模式决定。
    • PKCS1_PADDING:密钥长度(字节)-11(字节);
    • NO_PADDING:可以和密钥长度一致;
    • OAEP_PADDING:密钥长度(字节) - 41(字节)
  • 密文长度:与密钥长度一致。
    éžå¯¹ç§°åŠ å¯†ä¹‹RSA
    注意
    1. 公钥支持:只有RSAPublicKeySpecX509EncodedKeySpec这两个规范;
    2. 私钥支持:只有RSAPrivate(Crt)KeySpecPKCS8EncodedKeySpec规范;
    3. KeyPairGenerator.getInstance(String algorithm, String provider):返回生成指定算法的的 KeyPairGenerator对象。该实现取自指定提供者。指定提供者必须在安全提供者列表中注册
    4. Cipher.getInstance(String algorithm, String provider):返回生成指定算法的的 Cipher对象。该实现取自指定提供者。指定提供者必须在安全提供者列表中注册
DH

éžå¯¹ç§°åŠ å¯†ä¹‹RSA

代码示例,更多可参考这里

public class LabAsymmetricCipher {
	// 待处理的字符串
	private static String src = "com.zhaiqiafneng";

	public static void main(String[] args) {
		jdkRSA();
		jdkDH();
	}

	// JDK实现的RSA算法
	public static void jdkRSA() {
		try {
			// 获取密钥对
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
			keyPairGenerator.initialize(512);
			KeyPair keyPair = keyPairGenerator.generateKeyPair();
			PublicKey rsaPublicKey = keyPair.getPublic();
			PrivateKey rsaPrivateKey = keyPair.getPrivate();

			// 私钥加密 公钥解密之私钥加密
			// 获取规范私钥
			// Only RSAPrivate(Crt)KeySpec and PKCS8EncodedKeySpec supported for RSA private keys
			PKCS8EncodedKeySpec pkcs8EncodeKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			Key privateKey = keyFactory.generatePrivate(pkcs8EncodeKeySpec);
			// 私钥加密
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, privateKey);
			byte[] encodeRes = cipher.doFinal(src.getBytes());
			System.out.println("私钥加密 公钥解密之私钥加密结果:" + Hex.toHexString(encodeRes));

			// 私钥加密 公钥解密之公钥解密
			// 获取规范公钥
			// Only RSAPublicKeySpec and X509EncodedKeySpec supported for RSA public keys
			X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
			Key publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			byte[] decodeRes = cipher.doFinal(encodeRes);
			System.out.println("私钥加密 公钥解密之公钥解密结果:" + new String(decodeRes));

		
			// 公钥加密 私钥解密之公钥加密
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			encodeRes = cipher.doFinal(src.getBytes());
			System.out.println("\r\n公钥加密 私钥加密之公钥加密结果:" + Hex.toHexString(encodeRes));
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			decodeRes = cipher.doFinal(encodeRes);
			System.out.println("公钥加密 私钥加密之私钥结果:" + new String(decodeRes));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	//JDK实现的DH算法
	public static void jdkDH(){
		try {
			//初始化发送发秘钥
			KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance("DH");
			keyPairGenerator.initialize(512);
			KeyPair senderKeyPair=keyPairGenerator.generateKeyPair();
			byte[] senderPublicKeyEnc=senderKeyPair.getPublic().getEncoded();
		
			//初始化接收方秘钥
			X509EncodedKeySpec x509EncodedKeySpec=new X509EncodedKeySpec(senderPublicKeyEnc);
			//---通过发送方的公钥获取公钥参数规范
			KeyFactory receiverKeyFactory=KeyFactory.getInstance("DH");
			PublicKey receiverPublicKey=receiverKeyFactory.generatePublic(x509EncodedKeySpec);
			DHParameterSpec dhParameterSpec=((DHPublicKey)receiverPublicKey).getParams();
			//---通过发送方的公钥参数来生成接收方的秘钥
			keyPairGenerator.initialize(dhParameterSpec);
			KeyPair receiverKeyPair=keyPairGenerator.generateKeyPair();
			PrivateKey receiverPrivateKey=receiverKeyPair.getPrivate();
			byte[] receiverPublicKeyEnc=receiverKeyPair.getPublic().getEncoded();
		
			//秘钥构建
			KeyAgreement receiverKeyAgreement=KeyAgreement.getInstance("DH");
			receiverKeyAgreement.init(receiverPrivateKey);
			receiverKeyAgreement.doPhase(receiverPublicKey, true);
			SecretKey receiverDESKey=receiverKeyAgreement.generateSecret("DES");
		
		
		
			KeyFactory senderKeyFactory=KeyFactory.getInstance("DH");
			x509EncodedKeySpec=new X509EncodedKeySpec(receiverPublicKeyEnc);
			PublicKey senderPublicKey =senderKeyFactory.generatePublic(x509EncodedKeySpec);
		
			KeyAgreement senderKeyAgreement=KeyAgreement.getInstance("DH");
			senderKeyAgreement.init(senderKeyPair.getPrivate());
			senderKeyAgreement.doPhase(senderPublicKey, true);
			SecretKey senderDEStKey=senderKeyAgreement.generateSecret("DES");
		
			if(Objects.equal(senderDEStKey, receiverDESKey)){
				System.out.println("\r\nDH算法,发送发和接收方成功交换了秘钥");
			}
		
		
			//发送方加密
			Cipher cipher=Cipher.getInstance("DES");
			cipher.init(Cipher.ENCRYPT_MODE, senderDEStKey);
			byte[] encodeResult=cipher.doFinal(src.getBytes());
			System.out.println("DH之发送方发加密结果:"+Hex.toHexString(encodeResult));
		
			//接收方解密
			cipher.init(Cipher.DECRYPT_MODE, receiverDESKey);
			byte[] decodeResult=cipher.doFinal(encodeResult);
			System.out.println("DH之接收方发解密结果:"+new String(decodeResult));
		} catch (Exception e) {
			e.printStackTrace();
		}
	
	}
}

数字签名

  • 数字签名是带有秘钥(公钥和私钥)的摘要信息算法,它和摘要算法用途一样,用于验证数据来源、数据完整性和抗否认性等,私钥签名,公钥验证。常用的签名算法有RSA、DSA和ECDSA。在经典的RSA数字签名算法广泛的使用,就形成了DSS(Digital Signature Standard)标准,而DSA(Digital Signature Algorithm)是对标准的实现,DSA只能实现签名,而RSA既可以签名,也可以加解密,这是他们最大的区别。ECDSA是微软贡献的签名算法,全称是Elliptic Curve Digital Signature Algorithm,椭圆曲线数字签名算法,具有速度快,强度高,签名短的特点。

  • RSA有MD和SHA两类算法,如下:

    img

  • DSA只有SHA一类算法:

    img

  • ECDSA:

    img

    代码示例,更多可参考这里

public class LabSign {

	// 待处理的字符串
	private static String src = "com.zhaiqianfeng";

	public static void main(String[] args) {
		jdkRSASigin();
		jdkDSASigin();
		jdkECDASigin();
	}

	// jdk 实现的RSA签名算法
	public static void jdkRSASigin() {
		try {
			// 构建秘钥
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
			keyPairGenerator.initialize(512);
			KeyPair keyPair = keyPairGenerator.generateKeyPair();

			// 构建符合规范的秘钥
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyPair.getPrivate().getEncoded());
			PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

			// 私钥签名
			Signature signature = Signature.getInstance("MD5withRSA");
			signature.initSign(privateKey);
			signature.update(src.getBytes());
			byte[] encodeResult = signature.sign();
			System.out.println("MD5withRSA签名结果:" + Hex.toHexString(encodeResult));

			// 公钥验证签名
			X509EncodedKeySpec X509EncodedKeySpec = new X509EncodedKeySpec(keyPair.getPublic().getEncoded());
			PublicKey publicKey = keyFactory.generatePublic(X509EncodedKeySpec);
			signature.initVerify(publicKey);
			signature.update(src.getBytes());
			boolean verfyResult = signature.verify(encodeResult);
			System.out.println("MD5withRSA验证签名结果:" + verfyResult);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	// jdk 实现的DSA签名算法
	public static void jdkDSASigin() {
		try {
			// 构建秘钥
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
			keyPairGenerator.initialize(512);
			KeyPair keyPair = keyPairGenerator.generateKeyPair();

			// 构建符合规范的秘钥
			KeyFactory keyFactory = KeyFactory.getInstance("DSA");
			PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyPair.getPrivate().getEncoded());
			PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

			// 私钥签名
			Signature signature = Signature.getInstance("SHA1withDSA");
			signature.initSign(privateKey);
			signature.update(src.getBytes());
			byte[] encodeResult = signature.sign();
			System.out.println("\r\nSHA1withDSA签名结果:" + Hex.toHexString(encodeResult));

			// 公钥验证签名
			X509EncodedKeySpec X509EncodedKeySpec = new X509EncodedKeySpec(keyPair.getPublic().getEncoded());
			PublicKey publicKey = keyFactory.generatePublic(X509EncodedKeySpec);
			signature.initVerify(publicKey);
			signature.update(src.getBytes());
			boolean verfyResult = signature.verify(encodeResult);
			System.out.println("SHA1withDSA验证签名结果:" + verfyResult);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	// jdk 实现的ECDSA签名算法
	public static void jdkECDASigin() {
		try {
			// 构建秘钥
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
			keyPairGenerator.initialize(112);
			KeyPair keyPair = keyPairGenerator.generateKeyPair();

			// 构建符合规范的秘钥
			KeyFactory keyFactory = KeyFactory.getInstance("EC");
			PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyPair.getPrivate().getEncoded());
			PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

			// 私钥签名
			Signature signature = Signature.getInstance("SHA1withECDSA");
			signature.initSign(privateKey);
			signature.update(src.getBytes());
			byte[] encodeResult = signature.sign();
			System.out.println("\r\nSHA1withECDSA签名结果:" + Hex.toHexString(encodeResult));

			// 公钥验证签名
			X509EncodedKeySpec X509EncodedKeySpec = new X509EncodedKeySpec(keyPair.getPublic().getEncoded());
			PublicKey publicKey = keyFactory.generatePublic(X509EncodedKeySpec);
			signature.initVerify(publicKey);
			signature.update(src.getBytes());
			boolean verfyResult = signature.verify(encodeResult);
			System.out.println("SHA1withECDSA验证签名结果:" + verfyResult);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

扩展

加密算法中SecretKeyFactory、KeyGenerator、KeyPairGenerator区别

相同点

  • 他们都是密钥管理器,用于生成(或将已有的密码转换成)SecretKey规范密钥;然后通过Cipher.init(Cipher_MODE,SecretKey,Iv)初始化加解密对象,然后执行doFinal进行加解密。

不同点

  • KeyGeneratorSecretKeyFactory用于对称加密体系中生成(或转换成)规范密钥
    • KeyGenerator:用于对称加密算法中生成新的规范密钥
    • SecretKeyFactory:用于对称加密体系中将已有的密码转换成规范的密钥。与SecretKeySpe实现功能相同,SecretKeySpe不依赖于算法提供商(Provider);而SecretKeyFactory是基于算法提供商(Provider)
  • KeyPairGenerator: keyPairGenerator用于非对称加密体系中生成新的规范密钥;

BouncyCastle使用

BouncyCastle是什么?

  • Java标准库提供了一系列常用的哈希算法。但如果我们要用的某种算法或者加密模式和填充模式,Java标准库没有提供怎么办?
    例如如下代码:
    //注意:JDK自带的AES算法不支持PKCS7Padding填充方式
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".getBytes("UTF-8"), "AES"));
    cipher.doFinal("zcmain".getBytes("UTF-8"));
    
    上述代码会报错,因为JDK自带的加密包不支持AES的PKCS7Padding填充方式。
    此时我们就可以选择BouncyCastle就是一个提供了很多哈希算法和加密算法的第三方库。它提供了Java标准库没有的一些算法和加解密模式及填充模式,例如,RipeMD160哈希算法 AES的PKCS7Padding填充模式等。

如何使用BouncyCastle?

  1. 官网下载BouncyCastle包(bcprov-jdk15on-xxx.jar)或者直接通过Maven仓库依赖添加
  2. 代码中进行注册BouncyCastle。(使用前需要注册,后续就可以使用BouncyCastle提供的所有哈希算法和加密算法)
    // 注册BouncyCastle:
    Security.addProvider(new BouncyCastleProvider());
    
  3. 按照按名称正常调用即可
    // 注册BouncyCastle:
    Security.addProvider(new BouncyCastleProvider());
    //正常调用即可,此时程序会从注册的Provider中查找满足的算法、模式、填充模式
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".getBytes("UTF-8"), "AES"));
    cipher.doFinal("zcmain".getBytes("UTF-8"));
    

BouncyCastle与JDK所包含的算法区别?

  • 参见上面文中算法的不同实现方区别

随机数SecureRandom介绍

Random类中实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,生成的随机数字完全相同。所以在需要频繁生成随机数,或者安全要求较高的时候,不要使用Random,因为其生成的值其实是可以预测的。

SecureRandom类提供加密的强随机数生成器 (RNG)SecureRandom和Random都是,也是如果种子一样,产生的随机数也一样: 因为种子确定,随机数算法也确定,因此输出是确定的。

总结: SecureRandom类收集了一些随机事件,比如鼠标点击,键盘点击等等,SecureRandom 使用这些随机事件作为种子。这意味着,种子是不可预测的,而不像Random默认使用系统当前时间的毫秒数作为种子,有规律可寻。

为什么RSA公钥每次加密得到的结果都不一样?

前往阅读

漫游对称加密算法

前往阅读

对称加密算法IV介绍
  • IV
    简介:在密码学的领域里,初始向量(英语:initialization vector,缩写为IV),或译初向量,又称初始变量(starting variable,缩写为SV),是一个固定长度的输入值。一般的使用上会要求它是随机数或拟随机数(pseudorandom)。使用随机数产生的初始向量才能达到语义安全(散列函数与消息验证码也有相同要求),并让攻击者难以对原文一致且使用同一把密钥生成的密文进行破解。在区块加密中,使用了初始向量的加密模式被称为区块(或分组)加密模式。

    IV长度:在分组加密时,和分组长度相同(块大小),不是和密钥长度相同。比如AES是128bit(16字节)分组,但密钥可以是128、192、256。IV必须也是128bit;DES是64bit(8字节)分组,所以IV长度也必须是8字节。
    流加密IV值和具体算法有关。

详细前往阅读

对称加密的填充方式

前往阅读

关于 PKCS5 和 PKCS7 填充问题(AES并没有64位的块, 如果采用PKCS5, 那么实质上就是采用PKCS7)

加密算法-流加密

前往阅读

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值