Base64
Base64 是一种基于 64 个可打印字符的编码方式,用于将二进制数据转换为文本格式,核心作用是解决二进制数据在文本协议中传输或存储的兼容性问题。
1. Base64 的设计初衷
计算机中二进制数据(如图片、文件、加密数据等)可能包含不可打印的控制字符(如 \0、\n 或 ASCII 码小于 32 的字符),这些字符在文本协议(如 HTTP、SMTP、XML)或文本存储场景中可能被误解析或过滤,导致数据损坏。
Base64 通过将二进制数据映射为 64 个安全的可打印字符(A-Z、a-z、0-9、+、/,共 64 个),确保数据能在文本环境中安全传输和存储。
2. 编码原理(简化版)
Base64 编码的核心是 “分组转换”,步骤如下:
- 二进制分组:将原始二进制数据按 3 字节(24 位) 一组拆分(3×8=24 位)。
- 拆分位段:将 24 位拆分为 4 个 6 位的片段(4×6=24 位)。
- 映射字符:每个 6 位片段对应 0-63 的整数,再映射到 Base64 字符表(如 0→A,1→B,…,63→/)。
特殊情况:
- 若原始数据长度不是 3 的倍数,会用
=填充:- 余 1 字节(8 位):拆分为 2 个 6 位段,后 2 个补
=(最终 4 个字符,后 2 个为=); - 余 2 字节(16 位):拆分为 3 个 6 位段,最后 1 个补
=(最终 4 个字符,最后 1 个为=)。
- 余 1 字节(8 位):拆分为 2 个 6 位段,后 2 个补
3. 字符表(标准 Base64)
| 数值范围 | 对应字符 |
|---|---|
| 0-25 | A-Z(大写字母) |
| 26-51 | a-z(小写字母) |
| 52-61 | 0-9(数字) |
| 62 | + |
| 63 | / |
| 填充符 | = |
4. 应用场景
- 数据传输:在仅支持文本的协议中传输二进制数据(如 HTTP 的 URL 参数、邮件附件、API 接口的二进制字段)。
- 数据存储:将图片、音频等二进制数据编码为字符串后,嵌入 HTML、CSS、JSON 等文本格式中(例如网页中用
data:image/png;base64,...直接嵌入图片)。 - 简单加密:虽不是加密算法,但可模糊原始数据(易解码,仅用于 “非安全级” 的信息隐藏)。
5. 优缺点
- 优点:兼容性强,能在文本环境中安全传输二进制数据;实现简单。
- 缺点:编码后数据体积会增大约 1/3(3 字节→4 字节);不适合对体积敏感的场景。
示例
- 原始字符
ABC(ASCII 码对应二进制01000001 01000010 01000011):分组为 24 位 → 拆分为 4 个 6 位010000 010100 001001 000011→ 对应数值 16、20、9、3 → 映射为QUJD。 - 编码后若有填充:例如字符
A编码为QQ==。
总结:Base64 是二进制到文本的 “桥梁”,通过标准化的字符映射解决跨场景数据传输的兼容性问题,是计算机领域中应用极广的编码方式。
URL Base64
URL Base64 是 Base64 的一种变体,专门针对 URL(统一资源定位符)和 URI(统一资源标识符)场景设计,解决了标准 Base64 中部分字符与 URL 语法冲突的问题。
1. 为什么需要 URL Base64?
标准 Base64 使用的字符集中包含 + 和 /,而这两个字符在 URL 中有特殊含义:
+在 URL 中会被解析为空格;/是 URL 中路径分隔符(如https://example.com/path1/path2)。
如果直接将标准 Base64 编码结果放入 URL 中,这些特殊字符可能被误解析,导致数据损坏或 URL 无效。URL Base64 正是为解决这一问题而设计的。
2. URL Base64 与标准 Base64 的区别
- 替换冲突字符:标准 Base64 中的
+被替换为-(连字符),/被替换为_(下划线),避免与 URL 语法冲突。 - 可选的填充符处理:标准 Base64 用
=作为填充符(当数据长度不是 3 的倍数时),但=在 URL 的查询参数中可能被编码为%3D,增加冗余。因此,URL Base64 通常省略填充符=(解码时可根据长度自动补全)。
3. 字符表对比
| 场景 | 62 号字符 | 63 号字符 | 填充符处理 |
|---|---|---|---|
| 标准 Base64 | + | / | 必须用 = 填充 |
| URL Base64 | - | _ | 通常省略 = |
4. 应用场景
- URL 参数传递:当需要在 URL 的查询参数(
?key=value)中携带二进制数据(如令牌、加密信息)时,使用 URL Base64 编码可避免字符冲突。 - 文件名或路径:在 URL 路径中包含编码后的字符串(如短链接、资源标识)时,防止
/等字符被误解析为路径分隔符。 - JSON/Web Token(JWT):JWT 的签名部分使用 URL Base64 编码(且省略填充符),确保能安全嵌入 URL 或 HTTP 头中。
示例
- 标准 Base64 编码结果:
a+b/c= - 对应的 URL Base64 编码:
a-b_c(替换+→-、/→_,省略=)
总结
URL Base64 是标准 Base64 的 “URL 友好版”,通过替换冲突字符(+→-、/→_)和省略填充符 =,确保编码结果能在 URL/URI 中安全传输,避免解析错误。它是处理 URL 中二进制数据的首选编码方式。
哈希算法(Hash Algorithm)
哈希算法(Hash Algorithm)是一种将任意长度的输入数据(消息)映射为固定长度输出值(哈希值 / 散列值) 的函数,核心特性是 “压缩性” 和 “单向性”,广泛用于数据校验、加密、存储等场景。
1. 核心特性
- 固定长度输出:无论输入数据是 1 字节还是 1GB,哈希算法都会生成固定长度的哈希值(如 MD5 输出 128 位,SHA-256 输出 256 位)。
- 单向性:从哈希值无法反向推导出原始输入数据(计算不可逆)。
- 抗碰撞性:
- 弱抗碰撞:给定原始数据,很难找到另一个数据与之产生相同的哈希值;
- 强抗碰撞:很难找到任意两个不同数据,产生相同的哈希值(理想状态,实际算法需尽可能接近)。
- 高效性:计算哈希值的过程快速,即使对大文件也能高效处理。
2. 常见哈希算法
-
MD5(Message-Digest Algorithm 5):输出 128 位哈希值(通常表示为 32 个十六进制字符),曾广泛使用,但因抗碰撞性被攻破(可人为构造相同哈希值的不同数据),不适合安全场景,仅用于非安全校验(如文件完整性检查)。
-
SHA 系列(Secure Hash Algorithm):
- SHA-1:输出 160 位,抗碰撞性已被破解,逐步淘汰;
- SHA-2:包括 SHA-256(256 位)、SHA-384(384 位)、SHA-512(512 位)等,目前安全性较高,是主流标准(如区块链、数字签名、HTTPS 证书等);
- SHA-3:基于全新设计的算法,与 SHA-2 互补,用于替代场景。
-
其他算法:
- CRC32:用于简单校验(如文件传输校验),但安全性极低(易碰撞);
- bcrypt、Argon2:专为密码存储设计,加入 “盐值(Salt)” 和慢哈希机制,抵御暴力破解。
3. 应用场景
- 数据完整性校验:下载文件时,对比本地计算的哈希值与官方提供的哈希值,判断文件是否被篡改(如操作系统镜像校验)。
- 密码存储:系统不直接存储明文密码,而是存储其哈希值。用户登录时,计算输入密码的哈希值与存储值对比(配合盐值防止彩虹表攻击)。
- 唯一标识:为大文件、数据块生成唯一哈希值(如 Git 用 SHA-1 标识代码提交,BT 下载用哈希值标识文件分片)。
- 数字签名:对原始数据的哈希值加密,接收方解密后与本地计算的哈希值对比,验证数据真实性和完整性(如区块链交易签名)。
4. 注意事项
- 安全性差异:MD5、SHA-1 因碰撞漏洞,禁止用于加密场景(如密码、签名),需使用 SHA-2 及以上算法。
- 哈希冲突不可避免:根据鸽巢原理,固定长度输出无法唯一对应无限输入,好的算法会让冲突概率极低(几乎可忽略)。
- 盐值的重要性:存储密码时,同一密码加不同盐值会生成不同哈希值,避免彩虹表(预计算哈希值字典)攻击。
示例
- 输入字符串
hello,MD5 哈希值为5d41402abc4b2a76b9719d911017c592; - 输入
hello world,SHA-256 哈希值为b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9。
总结:哈希算法是 “数据的指纹”,通过固定长度、不可逆的映射,在数据校验、安全存储等领域发挥核心作用,选择时需根据场景权衡安全性和效率。
base64应用举例
Base64 在 Java 中应用广泛,常见场景包括:二进制数据与字符串互转、图片 / 文件的编码传输、小文件嵌入文本(如 HTML)等。以下是几个典型示例,基于 Java 内置的 java.util.Base64 工具类实现(Java 8+ 内置,无需额外依赖)。
示例 1:字符串与 Base64 编码互转
将普通字符串编码为 Base64 格式,再解码还原原字符串。
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Base64Example {
public static void main(String[] args) {
// 原始字符串
String original = "Hello, Base64! 这是一个测试字符串";
// 1. 编码:字符串 → Base64 编码的字符串
// 先将字符串转为字节数组(指定编码,如 UTF-8),再进行 Base64 编码
byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8);
String encoded = Base64.getEncoder().encodeToString(originalBytes);
System.out.println("编码后:" + encoded);
// 输出:编码后:SGVsbG8sIEJhc2U2NCEg6L+Z5piv5LiA5pWw5o2u5Lqe5Yqo5pa554K5
// 2. 解码:Base64 编码的字符串 → 原始字符串
byte[] decodedBytes = Base64.getDecoder().decode(encoded);
String decoded = new String(decodedBytes, StandardCharsets.UTF_8);
System.out.println("解码后:" + decoded);
// 输出:解码后:Hello, Base64! 这是一个测试字符串
}
}
示例 2:图片文件与 Base64 互转(常用于网页嵌入)
将本地图片文件编码为 Base64 字符串(可直接嵌入 HTML 的 img 标签),再解码还原为图片文件。
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class ImageBase64Example {
public static void main(String[] args) {
String imagePath = "test.png"; // 原始图片路径
String base64Path = "image_base64.txt"; // 存储 Base64 编码的文件
String outputImagePath = "decoded_test.png"; // 解码后输出的图片路径
try {
// 1. 图片文件 → Base64 字符串(并保存到文件)
byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
// 保存编码结果(可选,用于模拟传输或存储)
Files.write(Paths.get(base64Path), base64Image.getBytes());
System.out.println("图片编码完成,Base64 长度:" + base64Image.length());
// 2. Base64 字符串 → 还原图片文件
byte[] decodedImageBytes = Base64.getDecoder().decode(base64Image);
Files.write(Paths.get(outputImagePath), decodedImageBytes);
System.out.println("图片解码完成,已保存到:" + outputImagePath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
网页嵌入用法:编码后的字符串可直接用于 HTML,无需单独加载图片文件:
<!-- 格式:data:[文件类型];base64,编码字符串 -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...(省略后续编码)" />
示例 3:URL 安全的 Base64 编码(URL Base64)
使用 Base64.getUrlEncoder() 和 Base64.getUrlDecoder() 处理 URL 场景,自动替换 +// 为 -/_,并可省略填充符 =。
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class UrlBase64Example {
public static void main(String[] args) {
String original = "https://example.com/path?param=1+2/3";
// URL 安全的编码(自动替换 + → -,/ → _,默认保留填充符 =)
byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8);
String urlEncoded = Base64.getUrlEncoder().encodeToString(originalBytes);
System.out.println("URL 编码后:" + urlEncoded);
// 输出:URL 编码后:aHR0cHM6Ly9leGFtcGxlLmNvbS9wYXRoP3BhcmFtPTEtMi9z
// 解码(URL 专用解码器)
byte[] decodedBytes = Base64.getUrlDecoder().decode(urlEncoded);
String decoded = new String(decodedBytes, StandardCharsets.UTF_8);
System.out.println("URL 解码后:" + decoded);
// 输出:URL 解码后:https://example.com/path?param=1+2/3
// 可选:编码时省略填充符 =(调用 withoutPadding())
String urlEncodedNoPadding = Base64.getUrlEncoder().withoutPadding().encodeToString(originalBytes);
System.out.println("无填充符编码:" + urlEncodedNoPadding);
}
}
核心 API 说明
Java 8+ 提供的 Base64 类包含三种编码器 / 解码器:
Base64.getEncoder():标准 Base64 编码器(使用+、/、=)。Base64.getUrlEncoder():URL 安全编码器(使用-、_,可省略=)。Base64.getMimeEncoder():MIME 格式编码器(每行 76 字符,适合邮件等场景)。
对应解码器为 getDecoder()、getUrlDecoder()、getMimeDecoder(),使用方式一致。
这些示例覆盖了 Base64 的主流应用场景,实际开发中可根据需求选择合适的编码方式。
哈希算法应用举例
哈希算法在实际开发中应用广泛,以下是几个典型场景及对应的 Java 实现示例,涵盖数据校验、密码存储、唯一标识等核心用途。
示例 1:文件完整性校验(SHA-256)
通过计算文件的哈希值,验证文件是否被篡改(如下载文件时与官方提供的哈希值对比)。
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileHashCheck {
// 计算文件的SHA-256哈希值
public static String calculateFileHash(String filePath) throws NoSuchAlgorithmException, IOException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (FileInputStream fis = new FileInputStream(filePath)) {
byte[] buffer = new byte[8192]; // 缓冲区
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead); // 逐步更新哈希计算
}
}
// 将字节数组转换为十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : digest.digest()) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
public static void main(String[] args) {
String filePath = "test.zip"; // 待校验文件
String officialHash = "a1b2c3d4e5f6..."; // 官方提供的哈希值(示例)
try {
String fileHash = calculateFileHash(filePath);
System.out.println("文件哈希值:" + fileHash);
// 对比哈希值判断文件是否完整
if (fileHash.equalsIgnoreCase(officialHash)) {
System.out.println("文件完整,未被篡改");
} else {
System.out.println("文件已被篡改!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
示例 2:密码存储(加盐哈希,BCrypt)
直接存储明文密码不安全,需对密码进行哈希处理(配合盐值防止彩虹表攻击)。推荐使用专为密码设计的BCrypt算法(自带盐值生成)。
需引入依赖(Maven):
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jBCrypt</artifactId>
<version>0.4</version>
</dependency>
代码示例:
import org.mindrot.jbcrypt.BCrypt;
public class PasswordHashing {
// 加密密码(自动生成盐值)
public static String hashPassword(String plainPassword) {
int workload = 12; // 计算强度(值越大越慢,安全性越高)
return BCrypt.hashpw(plainPassword, BCrypt.gensalt(workload));
}
// 验证密码(对比输入密码与存储的哈希值)
public static boolean verifyPassword(String plainPassword, String hashedPassword) {
return BCrypt.checkpw(plainPassword, hashedPassword);
}
public static void main(String[] args) {
String password = "User@123"; // 用户明文密码
// 存储时:加密密码
String hashed = hashPassword(password);
System.out.println("加密后的密码(存储到数据库):" + hashed);
// 输出类似:$2a$12$V8J8...(包含自动生成的盐值)
// 登录时:验证密码
boolean isMatch = verifyPassword("User@123", hashed); // 正确密码
System.out.println("密码验证结果(正确):" + isMatch); // true
isMatch = verifyPassword("WrongPass", hashed); // 错误密码
System.out.println("密码验证结果(错误):" + isMatch); // false
}
}
说明:BCrypt 会将盐值直接存入哈希结果中(如 $2a$12$V8J8... 中的 V8J8... 部分),验证时无需单独存储盐值。
示例 3:生成数据唯一标识(MD5)
为数据生成固定长度的哈希值作为唯一标识(如缓存键、数据分片标识)。MD5 安全性较低,但速度快,适合非加密场景。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
public class DataUniqueId {
// 生成字符串的MD5哈希值(作为唯一标识)
public static String generateUniqueId(String data) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashBytes = md.digest(data.getBytes(StandardCharsets.UTF_8));
// 转换为32位十六进制字符串
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static void main(String[] args) {
try {
// 为不同数据生成唯一标识
String id1 = generateUniqueId("user:1001:profile");
String id2 = generateUniqueId("order:2023:100001");
System.out.println("用户信息标识:" + id1); // 如:a3b7c2d9...
System.out.println("订单信息标识:" + id2); // 如:f8e6d5c3...
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
应用场景:缓存系统中用哈希值作为键(如 Redis 的 key),避免原始数据过长;分布式系统中用哈希值分片数据。
示例 4:数字签名验证(SHA-256 + 私钥加密)
数字签名通过 “哈希 + 非对称加密” 确保数据完整性和发送者身份(接收方用公钥解密哈希值并验证)。
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class DigitalSignature {
// 生成密钥对(私钥签名,公钥验证)
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048); // 密钥长度
return generator.generateKeyPair();
}
// 用私钥对数据签名(先哈希,再加密哈希值)
public static String sign(String data, PrivateKey privateKey) throws Exception {
// 计算数据的SHA-256哈希值
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
// 用私钥加密哈希值(生成签名)
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(hash);
return Base64.getEncoder().encodeToString(signature.sign());
}
// 用公钥验证签名
public static boolean verify(String data, String signatureStr, PublicKey publicKey) throws Exception {
// 计算数据的哈希值
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
// 用公钥解密签名并对比哈希值
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(hash);
byte[] signatureBytes = Base64.getDecoder().decode(signatureStr);
return signature.verify(signatureBytes);
}
public static void main(String[] args) throws Exception {
// 生成密钥对
KeyPair keyPair = generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// 原始数据
String data = "这是一条需要签名的重要消息";
// 签名
String signature = sign(data, privateKey);
System.out.println("签名结果:" + signature);
// 验证签名(数据未篡改时)
boolean isValid = verify(data, signature, publicKey);
System.out.println("签名验证(正常):" + isValid); // true
// 验证签名(数据被篡改时)
isValid = verify(data + "被篡改了", signature, publicKey);
System.out.println("签名验证(篡改后):" + isValid); // false
}
}
说明:数字签名广泛用于区块链、电子合同、软件发布(如 Jar 包签名)等场景,确保数据未被篡改且来自合法发送者。
总结
哈希算法的核心应用场景包括:
- 数据校验(文件完整性、传输一致性);
- 密码存储(加盐哈希防泄露);
- 唯一标识(缓存键、数据分片);
- 安全领域(数字签名、防篡改)。
选择算法时需注意:MD5、SHA-1 因安全漏洞仅用于非加密场景;密码存储优先用 BCrypt、Argon2 等慢哈希算法;安全场景需用 SHA-256 及以上。
1413

被折叠的 条评论
为什么被折叠?



