Java加密总结:常见的哈希算法总结,对称式加密与非对称式加密的对比

本文详细介绍了URL编码、Base64编码、哈希算法(包括MD5、SHA-1和Hmac)以及对称式加密算法(如AES的ECB和CBC模式)和非对称式加密算法(如RSA)。通过实例展示了各种算法的使用方法,强调了它们在数据处理、安全性及效率方面的特性。哈希算法用于验证数据完整性,而加密算法则确保数据的安全传输。

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

目录

URL编码

Base64编码

哈希算法: 

概述

 MD5算法

SHA-1算法

Hmac算法

对称式加密算法

概述

使用AES加密​​​​​​​

ECB模式

CBC模式

非对称式加密算法

概述

RSA算法


URL编码

之所以需要 URL 编码,是因为出于兼容性考虑,很多服务器只识别 ASCII 字符。但如果 UR L 中包含中文、日文这些非 ASCII 字符怎么办?不要紧, URL 编码有一套规则: 如果字符是 A ~ Z , a ~ z , 0 ~ 9 以及 - 、 _ 、 . 、 * ,则保持不变; 如果是其他字符,先转换为 UTF-8 编码,然后对每个字节以 %XX 表示。

要特别注意: URL 编码是编码算法,不是加密算法。 URL 编码的目的是把任意文本数据编码为 % 前缀表示的文本,编码后的文本仅包含 A ~ Z , a ~ z , 0 ~ 9 , - , _ , . , * 和 % , 便于浏览器和服务器处理。

基于URL编码的解码实例:

public class Test01 {
	public static void main(String[] args) throws UnsupportedEncodingException {
		String url="http://www.baidu.com/s?wd=";
		String value="抖音";
		
		//对URL中的中文进行编码
		String result=URLEncoder.encode(value,"utf-8");
		System.out.println(result);
		System.out.println(url+result);
		
		//对URL中的中文进行解码
		String param="https://www.baidu.com/s?wd=%E6%88%91%E6%9C%AC%E5%B0%86%E5%BF%83%E5%90%91%E6%98%8E%E6%9C%88";
		String content=URLDecoder.decode(param, "utf-8");
		System.out.println(content);
		
	}

}

Base64编码

URL 编码是对字符进行编码,表示成 %xx 的形式,而 Base64 编码是对二进制数据进行编 码,表示成文本格式。

Base64 编码可以把任意长度的二进制数据变为纯文本,并且纯文本内容中且只包含指定字符 内容: A ~ Z 、 a ~ z 、 0 ~ 9 、 + 、 / 、 = 。它的原理是把 3 字节的二进制数据按 6bi t 一组,用 4 个int整数表示,然后查表,把 int 整数用索引对应到字符,得到编码后的字符串。

6 位整数的范围总是 0 ~ 63 ,所以,能用 64 个字符表示:字符 A ~ Z 对应索引 0 ~ 25 ,字符 a ~ z 对应索引 26 ~ 51 ,字符 0 ~ 9 对应索引 52 ~ 61 ,最后两个索引 62 、 63 分别用字符 + 和 / 表示。

基于Base64编码的实例(读取一张图片):

public class Test02 {
	public static void main(String[] args) throws IOException {
		//读取图片(字节数组)
		byte[] imageByteArray=Files.readAllBytes(Paths.get("D:\\test\\qyqx.jpg"));
		
		//将字节数组进行Base64编码,转换成“字符串形式”
		String imageDataStr=Base64.getEncoder().encodeToString(imageByteArray);
		System.out.println(imageDataStr);
		
		//Base64解码
		byte[] imageResultByteArray=Base64.getDecoder().decode(imageDataStr);
		Files.write(Paths.get("D:\\image\\qyqx.jpg"), imageResultByteArray);
		
	}

}

Base64 编码的目的是把二进制数据变成文本格式,这样在很多文本中就可以处理二进制数据。例 如,电子邮件协议就是文本协议,如果要在电子邮件中添加一个二进制文件,就可以用 Base64 编 码,然后以文本的形式传送。

Base64 编码的缺点是传输效率会降低,因为它把原始数据的长度增加了1/3。和 URL 编码一 样, Base64 编码是一种编码算法,不是加密算法。

如果把 Base64 的 64 个字符编码表换成 32 个、 48 个或者 58 个,就可以使用 Base32 编码, Base48 编码和 Base58 编码。字符越少,编码的效率就会越低。

URL编码与Base编码的总结:

URL 编码和 Base64 编码都是编码算法,它们不是加密算法;

URL 编码的目的是把任意文本数据编码为 % 前缀表示的文本,便于浏览器和服务器处理;

Base64 编码的目的是把任意二进制数据编码为文本,但编码后数据量会增加 1/3

哈希算法: 

概述

哈希算法( Hash )又称摘要算法( Digest ),它的作用是:对任意一组输入数据进行计 算,得到一个固定长度的输出摘要。哈希算法的目的:为了验证原始数据是否被篡改。

哈希算法最重要的特点就是:

相同的输入一定得到相同的输出;

不同的输入大概率得到不同的输出。

哈希算法会产生哈希碰撞 ,这样的碰撞也是无法避免的因为输出的字节长度是固定的, String 的 hashCode() 输出是 4 字节整数,最多只有 42949672 96 种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集 合映射到一个有限的输出集合,必然会产生碰撞。

碰撞不可怕,我们担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的 安全性。一个安全的哈希算法必须满足:

碰撞概率低;

不能猜测输出。

不能猜测输出是指:输入的任意一个 bit 的变化会造成输出完全不同,这样就很难从输出反推 输入(只能依靠暴力穷举)。

常用的哈希算法:

常用的哈希算法有:根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。

 MD5算法

MD5算法的实例:

public class Test02 {
	public static void main(String[] args) {
		String password="hhcbb1007";
		try {
			//获取基于MD5加密算法的工具对象
			MessageDigest digest=MessageDigest.getInstance("MD5");
			//更新原始数据
			digest.update(password.getBytes());
			//加密后的字节数组,转换字符串
			byte[] resultByteArray=digest.digest();
			StringBuilder sb=new StringBuilder();
			for(byte by:resultByteArray) {
				sb.append(String.format("%02x", by));
			}
			System.out.println(sb.length());
			System.out.println(sb);
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

使用 MessageDigest 时,我们首先根据哈希算法获取一个 MessageDigest 实例,然后,反复调用 u pdate(byte[]) 输入数据。当输入结束后,调用 digest() 方法获得 byte[] 数组表示的摘要,最 后,把它转换为十六进制的字符串。

SHA-1算法

HA-1 也是一种哈希算法,它的输出是 160 bits ,即 20 字节。 SHA-1 是由美国国家安全 局开发的, SHA 算法实际上是一个系列,包括 SHA-0 (已废弃)、 SHA-1 、 SHA-256 、 SHA512 等。 在 Java 中使用 SHA-1 ,和 MD5 完全一样,只需要把算法名称改为" SHA-1 ":

SHA-1算法实例:
 

public class Test04 {
	public static void main(String[] args) {
		try {
			String password="qwertyu";
			String salt=UUID.randomUUID().toString().substring(0,5);
			System.out.println(salt);
			
			MessageDigest digestSalt=MessageDigest.getInstance("SHA-1");

			digestSalt.update((password+salt).getBytes());//SHA-1
			byte[] saltByteArray=digestSalt.digest();
			StringBuilder sb=new StringBuilder();
			for(byte by:saltByteArray) {
				sb.append(String.format("%02x", by));
			}
			System.out.println(sb.length());
			System.out.println("SHA-1:"+sb);
		} catch (NoSuchAlgorithmException e) {
			
			e.printStackTrace();
		}

类似的,计算 SHA-256 ,我们需要传入名称" SHA-256 ",计算 SHA-512 ,我们需要传入名称" SH A-512 "。

Hmac算法

Hmac 算法就是一种基于密钥的消息认证码算法,它的全称是 Hash-based Message Authen tication Code ,是一种更安全的消息摘要算法。

Hmac 算法总是和某种哈希算法配合起来用的。例如,我们使用 MD5 算法,对应的就是 Hmac MD5 算法,它相当于“加盐”的 MD5 : HmacMD5 ≈ md5(secure_random_key, input)

因此, HmacMD5 可以看作带有一个安全的 key 的 MD5 。使用 HmacMD5 而不是用 MD5 加 salt ,有如下好处:

HmacMD5 使用的 key 长度是 64 字节,更安全;

Hmac 是标准算法,同样适用于 SHA-1 等其他哈希算法;

Hmac 输出和原有的哈希算法长度一致。

可见, Hmac 本质上就是把 key 混入摘要的算法。验证此哈希时,除了原始的输入数据,还要 提供 key 。为了保证安全,我们不会自己指定 key ,而是通过 Java 标准库的 KeyGenerator 生成一个安全的随机的 key 。

HmacMD5代码实例:

public class Test07 {
	public static void main(String[] args) {
		//HmacMD5
		String password="hhcbc";
		
		try {
			//秘钥连接器
			KeyGenerator keyStr=KeyGenerator.getInstance("HmacMD5");
			//生成秘钥
			SecretKey key=keyStr.generateKey();
			//获取秘钥key字节数组
			byte[] resultByteKey=key.getEncoded();
			System.out.println(resultByteKey.length);
			StringBuilder sb=new StringBuilder();
			for(byte b:resultByteKey) {
				sb.append(String.format("%02x", b));
				
			}
			System.out.println("秘钥内容:"+sb);
			//获取算法对象
			Mac mac=Mac.getInstance("HmacMD5");
			//初始化秘钥、
			mac.init(key);
			//更新原始内容、
			mac.update(password.getBytes());
			//加密
			byte[] by=mac.doFinal();
			System.out.println("加密结果:"+by.length);
			StringBuilder HmacMD5=new StringBuilder();
			for(byte b:by) {
				HmacMD5.append(String.format("%02x", b));
			}
			System.out.println("加密后的结果:"+HmacMD5);
		} catch (InvalidKeyException e) {
			
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			
			e.printStackTrace();
		} catch (IllegalStateException e) {
			
			e.printStackTrace();
		}
		
	}

}

和 MD5 相比,使用 HmacMD5 的步骤是:

1 通过名称 HmacMD5 获取 KeyGenerator 实例;

2 通过 KeyGenerator 创建一个 SecretKey 实例;

3 通过名称 HmacMD5 获取 Mac 实例;

4 用 SecretKey 初始化Mac实例;

5 对 Mac 实例反复调用 update(byte[]) 输入数据;

6 调用 Mac 实例的 doFinal() 获取最终的哈希值。

有了 Hmac 计算的哈希和 SecretKey ,我们想要验证怎么办?这时, SecretKey 不能从 KeyGener ator 生成,而是从一个 byte[] 数组恢复

代码实现:

public class Test06 {
	public static void main(String[] args) {
	  String password="qwertyuiop";
		try {
			//通过“秘钥的字节数组”,恢复秘钥
			byte[] keyByteArray= {-102, -84, 31, 95, 62, -53, 44, -8, 41, -58, -1, 110, -72, 41, 45, -94, -6, 22, 115, -125, -23, 6, 96, -101, 90, 82, -27, -26, 69, 111, -2, -115, 53, -53, -31, -68, -52, 27, 46, -62, -113, -78, 22, -89, -35, -87, 33, 50, 33, -3, -92, 42, 3, 64, 107, -120, 60, 42, 84, 84, 23, 57, -69, -24};
			SecretKey key=new SecretKeySpec(keyByteArray, "HmacMD5");
			
			//加密
			Mac mac=Mac.getInstance("HmacMD5");
			mac.init(key);
			mac.update(password.getBytes());
			byte[] resultByteArray=mac.doFinal();
			
			StringBuilder resultStr=new StringBuilder();
			for(byte b:resultByteArray) {
				resultStr.append(String.format("%02x", b));
			}
			System.out.println("加密结果:"+resultStr);
		} catch (InvalidKeyException e) {
			
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			
			e.printStackTrace();
		} catch (IllegalStateException e) {
			  
			e.printStackTrace();
		}
	}

}

对称式加密算法

概述

对称加密算法就是传统的用一个密码就进行加密和解密。

从程序的角度看,所谓加密,就是这样一个函数,它接收密码和明文,然后输出密文:

secret = encrypt(key, message);

而解密则相反,它接收密码和密文,然后输出明文:

plain = decrypt(key, secret);

在软件开发中,常用的对称加密算法有:

 密钥长度直接决定加密强度,而工作模式和填充模式可以看成是对称加密算法的参数和格式选 择。Java标准库提供的算法实现并不包括所有的工作模式和所有填充模式,但是通常我们只需要挑 选常用的使用就可以了。 最后注意, DES 算法由于密钥过短,可以在短时间内被暴力破解,所以现在已经不安全了。

使用AES加密​​​​​​​

AES算法是目前应用最广泛的加密算法,比较常见的工作模式是ECB和CBC

ECB模式

EBC模式加密并解密:

public class Main02 {
	//AEC对称加密
	//ECB工作模式
	public static void main(String[] args) throws Exception  {
	//原文
	String message="HHCBB";
	System.out.println("Message:"+message);
	
	//128位秘钥=16 bytes key
	byte[] key="123456qwertyuiop".getBytes();
	//加密:
	byte[] data=message.getBytes();
	byte[] encrypted=encrypt(key, data);
	System.out.println("Encrypted(加密):"+Base64.getEncoder().encodeToString(encrypted));
	
	//解密
	byte[] decrypted=decrypy(key, encrypted);
	System.out.println("Decrypted(解密):"+new String(decrypted));
	}
	
	public static byte[] encrypt(byte[] key,byte[] input) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, GeneralSecurityException, BadPaddingException {
		//创建密码对象,需要传入算法/工作模式/填充模式
		Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
		//根据key的字节内容,恢复秘钥对象
		SecretKey keySpec=new SecretKeySpec(key,"AES");
		
		//初始化秘钥:设置加密模式ENCRYPT_MODE
		cipher.init(Cipher.ENCRYPT_MODE, keySpec);
		//根据原始内容(字节),进行加密
		return cipher.doFinal(input);
	}
	
	//解密:
	public static byte[] decrypy(byte[] key,byte[] input) throws GeneralSecurityException, NoSuchPaddingException {
		//创建密码对象,需要传入算法/工作模式/填充模式
		Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
		//根据key的字节内容,恢复秘钥对象
		SecretKey keySpec=new SecretKeySpec(key,"AES");
		//初始化秘钥:设置加密模式DECRYPT_MODE
		cipher.init(Cipher.DECRYPT_MODE, keySpec);
		//根据原始内容(字节),进行解密
		return cipher.doFinal(input);
	}

}

Java标准库提供的对称加密接口非常简单,使用时按以下步骤编写代码:

1 根据算法名称/工作模式/填充模式获取 Cipher 实例;

2 根据算法名称初始化一个 SecretKey 实例,密钥必须是指定长度;

3 使用 SerectKey 初始化 Cipher 实例,并设置加密或解密模式;

4 传入明文或密文,获得密文或明文。

CBC模式

ECB 模式是最简单的 AES 加密模式,它只需要一个固定长度的密钥,固定的明文会生成固定的密 文,这种一对一的加密方式会导致安全性降低,更好的方式是通过 CBC 模式,它需要一个随机数作 为 IV 参数,这样对于同一份明文,每次生成的密文都不同:

//CBC工作模式
public class Main03 {
	//AEC对称加密
		//ECB工作模式
		public static void main(String[] args) throws Exception  {
		//原文
		String message="HHCBB";
		System.out.println("Message:"+message);
		
		//256位秘钥=16 bytes key
		byte[] key="123456qwertyuiopasdfghjklzxcvbnm".getBytes();
		//加密:
		byte[] data=message.getBytes();
		byte[] encrypted=encrypt(key, data);
		System.out.println("Encrypted(加密):"+Base64.getEncoder().encodeToString(encrypted));
		
		//解密
		byte[] decrypted=decrypy(key, encrypted);
		System.out.println("Decrypted(解密):"+new String(decrypted));
		}
		//加密
		public static byte[] encrypt(byte[] key,byte[] input) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, GeneralSecurityException, BadPaddingException {
			//创建密码对象,需要传入算法/工作模式/填充模式
			Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
			//根据key的字节内容,恢复秘钥对象
			SecretKey keySpec=new SecretKeySpec(key,"AES");
			
			//CBC模式需要生成一个16bytes的initialization vector
			SecureRandom sr=SecureRandom.getInstanceStrong();
			byte[] iv=sr.generateSeed(16);
			System.out.println(Arrays.toString(iv));
			IvParameterSpec ivps=new IvParameterSpec(iv); 
			//初始化秘钥:设置加密模式ENCRYPT_MODE
			cipher.init(Cipher.ENCRYPT_MODE, keySpec,ivps);
			//加密
			byte[] data=cipher.doFinal(input);
			//根据原始内容(字节),进行加密
			return join(iv,data);
		}
		
		//解密:
		public static byte[] decrypy(byte[] key,byte[] input) throws GeneralSecurityException, NoSuchPaddingException {
			//把input分割成IV和密文
			byte[] iv=new byte[16];
			byte[] data=new byte[input.length-16];
			
			System.arraycopy(input, 0, iv, 0, 16);
			System.arraycopy(input, 16, data, 0, data.length);
			System.out.println(Arrays.toString(iv));
			//创建密码对象,需要传入算法/工作模式/填充模式
			Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
			//根据key的字节内容,恢复秘钥对象
			SecretKey keySpec=new SecretKeySpec(key,"AES");
			//恢复iv
			IvParameterSpec ivps=new IvParameterSpec(iv);
			
			//初始化秘钥:设置加密模式DECRYPT_MODE
			cipher.init(Cipher.DECRYPT_MODE, keySpec,ivps);
			//根据原始内容(字节),进行解密
			return cipher.doFinal(data  );
		}
		public static byte[] join(byte[] bs1,byte[] bs2) {
			byte[] r=new byte[bs1.length+bs2.length];
			System.arraycopy(bs1, 0, r, 0, bs1.length);
			System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
			return r;
		}

}

在 CBC 模式下,需要一个随机生成的 16 字节IV参数,必须使用 SecureRandom 生成。因为多 了一个 IvParameterSpec 实例,因此,初始化方法需要调用 Cipher 的一个重载方法并传入 IvPara meterSpec 。

观察输出,可以发现每次生成的 IV 不同,密文也不同

非对称式加密算法

概述

从 DH 算法我们可以看到,公钥-私钥组成的密钥对是非常有用的加密方式,因为公钥是可以公 开的,而私钥是完全保密的,由此奠定了非对称加密的基础。

非对称加密:加密和解密使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密。

非对称加密的优点:对称加密需要协商密钥,而非对称加密可以安全地公开各自的公钥,在N个 人之间通信的时候:使用非对称加密只需要N个密钥对,每个人只管理自己的密钥对。而使用对称加 密需要则需要N*(N-1)/2个密钥,因此每个人需要管理N-1个密钥,密钥管理难度大,而且非常容易泄 漏。

非对称加密的缺点:运算速度非常慢,比对称加密要慢很多。 所以,在实际应用的时候,非对称加密总是和对称加密一起使用。

RSA算法

非对称加密的代码实例:

// RSA
public class Main05 {
    public static void main(String[] args) throws Exception {
        // 明文:
        byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
        
        // 创建公钥/私钥对:
        Human alice = new Human("Alice");
        
        // 用Alice的公钥加密:
        // 获取Alice的公钥,并输出
        byte[] pk = alice.getPublicKey();
        System.out.println(String.format("public key(公钥): %x", new BigInteger(1, pk)));
        
        // 使用公钥加密
        byte[] encrypted = alice.encrypt(plain);
        System.out.println(String.format("encrypted: %x", new BigInteger(1, encrypted)));
       
        // 用Alice的私钥解密:
        // 获取Alice的私钥,并输出
        byte[] sk = alice.getPrivateKey();
        System.out.println(String.format("private key: %x", new BigInteger(1, sk)));
        
        // 使用私钥解密
        byte[] decrypted = alice.decrypt(encrypted);
        System.out.println(new String(decrypted, "UTF-8"));
    }
}

// 用户类
class Human {
	// 姓名
    String name;
    
    // 私钥:
    PrivateKey sk;
    
    // 公钥:
    PublicKey pk;

    // 构造方法
    public Human(String name) throws GeneralSecurityException {
    	// 初始化姓名
        this.name = name;
        
        // 生成公钥/私钥对:
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
        kpGen.initialize(1024);
        KeyPair kp = kpGen.generateKeyPair();
        
        this.sk = kp.getPrivate();
        this.pk = kp.getPublic();
    }

    // 把私钥导出为字节
    public byte[] getPrivateKey() {
        return this.sk.getEncoded();
    }

    // 把公钥导出为字节
    public byte[] getPublicKey() {
        return this.pk.getEncoded();
    }

    // 用公钥加密:
    public byte[] encrypt(byte[] message) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, this.pk); // 使用公钥进行初始化
        return cipher.doFinal(message);
    }

    // 用私钥解密:
    public byte[] decrypt(byte[] input) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, this.sk); // 使用私钥进行初始化
        return cipher.doFinal(input);
    }
}

RSA 的公钥和私钥都可以通过 getEncoded() 方法获得以 byte[] 表示的二进制数据,并根据需要 保存到文件中。要从 byte[] 数组恢复公钥或私钥,可以这么写:

byte[] pkData = ...
byte[] skData = ...
KeyFactory kf = KeyFactory.getInstance("RSA");

// 恢复公钥:
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pkData);
PublicKey pk = kf.generatePublic(pkSpec);

// 恢复私钥:
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(skData);
PrivateKey sk = kf.generatePrivate(skSpec);

以 RSA 算法为例,它的密钥有 256 / 512 / 1024 / 2048 / 4096 等不同的长度。长度越长,密 码强度越大,当然计算速度也越慢。

小结:对称式加密算法与非对称式加密算法的比较:

对称加密算法使用同一个密钥进行加密和解密,常用算法有 DES 、 AES 和 IDEA 等;

使用对称加密算法需要指定算法名称、工作模式和填充模式。

非对称加密:加密和解密使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值