PBE的随笔

本文介绍了PBE(Password-Based Encryption)加密机制,它通过用户易记的口令和随机salt,结合多次迭代的散列函数,生成强密钥进行数据加密。以JDK中的PBEWithHmacSHA256AndAES_128为例,展示了加密和解密的过程,强调了口令、salt和迭代次数在安全中的作用,以及为何PBE能够提高安全性,抵御暴力破解攻击。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

建议先看文章做初步了解:Password-Based Encryption (PBE)_厚积薄发者,轻舟万重山-优快云博客

补充:

  • 因为具备密码学安全意义的密钥对于人来说是很难记忆的,所以密钥保存的安全性也需要慎重考虑,2022年建议使用的密钥安全强度已经是256bit起(当然128按照当前的算力也是有生之年系列,但是随着算力的提升,如果系统准备用10年以上,建议安全强度256bit,十年以内可以使用128bit);
  • 但是口令很容易,一段具有意义的文本对用户来说记忆是很容易的
  • 如何通过弱口令接近或达到密钥的安全强度?这就是PBE的价值

我用JDK中现役、常见的PBE算法PBEWithHmacSHA256AndAES_128为例作简述,简述思路如下:

  1. 先看看JDK中PBEWithHmacSHA256AndAES_128的应用代码;
  2. 通过应用代码来简述PBE过程;
  3. 通过应用代码和PBE过程来简述安全性;

应用代码

package wxy.secret;

import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

/**
 * PBEWithHmacSHA256AndAES_128 示例代码
 * @author 王大锤
 * @date 2022年2月15日
 */
public class PBEDemo {
	private static String PBE_MODE = "PBEWithHmacSHA256AndAES_128";
	private static SecureRandom random = new SecureRandom();
	private static int DEFAULT_ITERATION_COUNT = 1000;

	/**
	 * 生成随机salt
	 * @param seed
	 * @return
	 */
	public static byte[] initSalt(int seed) {
		return random.generateSeed(seed);
	}

	/**
	 * PBEWithHmacSHA256AndAES_128中AES默认是CBC模式,分组128bit,初始化向量也是128bit
	 * @param length 16字节
	 * @return
	 */
	public static byte[] initIV(int length) {
		return random.generateSeed(length);
	}

	/**
	 * 虽然是生成SecretKey对象,但实际上这并不是pbe的密钥
	 * 你通过SecretKey.getEncoded方法拿到密钥的字节数组,你会发现和password.toCharArray是一样
	 * 实际上,这一步得到的密钥的字节数组就是口令password字符编码的二进制
	 * @param password
	 * @return
	 * @throws Exception
	 */
	public static SecretKey genereateKey(String password) throws Exception {
		PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
		return SecretKeyFactory.getInstance(PBE_MODE).generateSecret(pbeKeySpec);
	}

	/**
	 * 注意加密方法每次调用都要传入salt和iv
	 * @param key 其实是用户口令password的字符编码
	 * @param salt 随机salt
	 * @param iv AES CBC模式需要的初始化向量,128bit
	 * @param data 待加密数据
	 * @return pbe密文
	 * @throws Exception
	 */
	public static byte[] encryption(SecretKey key, byte[] salt, byte[] iv, byte[] data) throws Exception {
		Cipher cipher = Cipher.getInstance(PBE_MODE);
		IvParameterSpec ivp = new IvParameterSpec(iv);
		PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, DEFAULT_ITERATION_COUNT, ivp);
		cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
		return cipher.doFinal(data);
	}

	/**
	 * 注意解密方法入参的salt和iv和加密的一致,都是可以公开的数据
	 * @param key 用户口令的字符编码的二进制
	 * @param salt 随机salt
	 * @param iv AES CBC模式需要的初始化向量
	 * @param data 密文
	 * @return 明文
	 * @throws Exception
	 */
	public static byte[] decryption(SecretKey key, byte[] salt, byte[] iv, byte[] data) throws Exception {
		Cipher cipher = Cipher.getInstance(PBE_MODE);
		IvParameterSpec ivp = new IvParameterSpec(iv);
		PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, DEFAULT_ITERATION_COUNT, ivp);
		cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);
		return cipher.doFinal(data);
	}
}

看一看测试:

		String password = "my_csdn_blog";
		SecretKey key = PBEDemo.genereateKey(password);
		byte[] salt = PBEDemo.initSalt(32);
		byte[] iv = PBEDemo.initIV(16);
		
		String message = "关于Java pbe的简述";
		Charset UTF8 = Charset.forName("UTF-8");
		
		byte[] ciphertext = PBEDemo.encryption(key, salt, iv, message.getBytes(UTF8));
		HexStringTool.print(ciphertext);
		byte[] plaintext = PBEDemo.decryption(key, salt, iv, ciphertext);
		HexStringTool.print(plaintext);
		System.out.println(new String(plaintext, UTF8));
		
		System.out.println("-------------------");
		System.out.println("基于password生成的SecretKey对象实际就是口令的字符编码的二进制:");
		HexStringTool.print(password.getBytes());
		HexStringTool.print(key.getEncoded());

测试结果:

833459198d56b24c7d3392035075a8c41145c75b5ab326c2687c4e482a977e5b
e585b3e4ba8e4a61766120706265e79a84e7ae80e8bfb0
关于Java pbe的简述
-------------------
基于password生成的SecretKey对象实际就是口令的字符编码的二进制:
6d795f6373646e5f626c6f67
6d795f6373646e5f626c6f67

jdk加解密各种getInstance的入参字符串不需要记忆,懂得查就行了,例如本例PBEWithHmacSHA256AndAES_128,查询页面Java Security Standard Algorithm Names (oracle.com)各种类各种密码学术语的入参,会找这个页面就行了。

更多的信息,看Java Platform, Standard Edition Security Developer’s Guide, Release 15 (oracle.com)

PBE过程

图下图:

通过上面测试代码就能知道,通过口令password得到的SecreteKey对象并非加密密钥,实际上是password的字符编码的二进制。

实际上是两个步骤:

  1. 用户口令password、随机salt在单向散列函数SHA256中经过iterationCount次迭代,得到AES的密钥(这里你可能不理解,但是没关系,周末我写一篇Java中diffie-hellman密钥协商的文章,那里面会给出这个过程的代码示例,就两句代码,很简单的,那里是Oracle官方推荐的转换方式);
  2. 然后PBE中AES默认是CBC模式,密钥+分组模式的初始化向量IV对明文进行加密得到密文;
  3. 将密文、salt、iv和iterationCount发送给解密方,解密方根据事先共享的password就能解密出明文;

同样的,PBE属于对称加密,一样存在密钥共享的问题,也就是加密方和解密方如何共享口令password。

安全性

对于PBEWithHmacSHA256AndAES_128而言,PBE过程是:

  1. 用户口令+salt经过iterationCount次的散列函数SHA256迭代,生成AES的密钥;
  2. 根据AES(默认是CBC模式)的模式选择好初始化向量IV,明文+密钥经过AES cipher得到密文;

如果要爆破:

A.对于攻击者而言,直接选择攻击AES 128的密钥爆破出明文是不现实的,就当前的算力而言这还属于有生之年系列(假设一秒钟能做100亿次计算,接近2^33次方,一年365*24*60*60差不多3KW秒,2^(128-33)/3KW,即2^95/3KW,你可以算算是多少年,有生之年系列)

B.用户口令就弱得多了,而且salt、iv和iterationCount都是公开的,这么来看,pbe的安全强度似乎就相当于用户的弱口令了?

实际上直接攻击用户弱口令这个选择也很难:

  • iterationCount会加倍攻击者的攻击代价,iterationCount通常是1000次以上迭代,对用户来说加解密就是迭代一千次,而对攻击者来说,是要付出一千倍的代价;
  • salt一直在变化,虽然salt是可以公开的,但是每次调用encryption方法输入的salt都不一样,一次通信是很多个data[]片段组成,每个data[]片段的salt都不一样,所以生成的AES密码也不一样,所以攻击者付出极大的代价爆破了一个data[]片段,也拿不到完整的信息;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值