浅入浅出常见敏感数据处理的加密算法
对称加密算法
概念
对称加密算法是应用较早的加密算法,又称为
共享密钥
加密算法。在对称加密算法中,使用的密钥只有一个,发送
和接收
双方都使用这个密钥对数据进行加密和解密。这就要求加密
和解密方
事先都必须知道加密的密钥。
常见算法类型
列说明(对应算法表格)
列名 | 含义说明 |
---|---|
算法 | 加密算法的名称(如AES、DES、3DES、SM1、SM4等)。 |
输出长度 | 每次加密操作输出的密文块长度。通常等于分组长度。 |
分组长度 | 加密算法每次处理数据的块大小(通常以比特或字节为单位,比如128位=16字节)。 |
密钥长度 | 用于加密和解密的密钥长度(通常以比特为单位,某些算法支持多种密钥长度)。 |
工作模式 | 算法的数据加密模式,如ECB(电子密码本模式)、CBC(密文分组链接)、CFB、OFB、CTR等。 |
填充模式 | 明文不足一个分组长度时的填充方式,如PKCS#5、PKCS#7、ZeroPadding、NoPadding等。 |
!
块加密(分组加密):加密算法无法一次性处理过长的明文,这种情况下,将明文以密钥长度分割,分成一个个固定长度的数据组(块),分别进行加密然后组合,该方式即为块加密,也称分组加密。
算法
算法 | 输出长度(位) | 输出长度(字节) | 分组长度 | 秘钥长度(位) | 工作模式 | 填充模式 |
---|---|---|---|---|---|---|
AES | 128 bit | 16 bytes | 16 bytes | 128/192/256 | ECB/CBC/PCBC/CTR/… | NoPadding/PKCS5Padding/PKCS7Padding/… |
DES | 64 bit | 8 bytes | 8 bytes | 56 bit | ECB/CBC/PCBC/CTR/… | NoPadding/PKCS5Padding/… |
3DES | 64 bit | 8 bytes | 8 bytes | 112/168 | ECB/CBC/CFB/OFB/CTR/… | NoPadding/ZeroPadding/PKCS7Padding/PKCS5Padding |
SM1 | 128 bit | 16 bytes | 16 bytes | 128 bit | ECB/CBC/CFB/OFB/CTR/… | NoPadding/ZeroPadding/PKCS7Padding/PKCS5Padding |
SM4 | 128 bit | 16 bytes | 16 bytes | 128 bit | ECB/CBC/CFB/OFB/CTR/GCM | ISO10126Padding/NoPadding/ZeroPadding/PKCS7Padding/PKCS5Padding |
使用案例
- 确定使用算法,以下示例:
- 算法名称:AES(推荐安全、广泛应用)
- 分组长度:128 bit(16字节)
- 密钥长度:128/192/256 bit(常用128)
- 常用模式:CBC(密文分组链接模式)
- 常用填充方案:PKCS7 / PKCS5
- 场景:
用户密码加密存储,假设有一个后台服务,需要将用户的敏感信息进行加密后存储到数据库。选择了AES-128-CBC算法。
- 代码演示
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;import java.security.SecureRandom;
import java.util.Base64;public class AESDemo {public static void main(String[] args) throws Exception {String plaintext = "Hello, Alice!";// 1. 生成128位密钥KeyGenerator keyGen = KeyGenerator.getInstance("AES");keyGen.init(128); // 128-bit AESSecretKey secretKey = keyGen.generateKey();byte[] keyBytes = secretKey.getEncoded();// 2. 生成IV(16字节)byte[] iv = new byte[16];new SecureRandom().nextBytes(iv);IvParameterSpec ivSpec = new IvParameterSpec(iv);// 3. 加密Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);byte[] ciphertextBytes = cipher.doFinal(plaintext.getBytes("UTF-8"));// 4. 输出加密结果(Base64 编码便于可视化和存储)String cipherBase64 = Base64.getEncoder().encodeToString(ciphertextBytes);String keyBase64 = Base64.getEncoder().encodeToString(keyBytes);String ivBase64 = Base64.getEncoder().encodeToString(iv);System.out.println("密文Base64: " + cipherBase64);System.out.println("密钥Base64: " + keyBase64);System.out.println("IV Base64: " + ivBase64);// 5. 解密Cipher cipher2 = Cipher.getInstance("AES/CBC/PKCS5Padding");cipher2.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);byte[] decryptedBytes = cipher2.doFinal(Base64.getDecoder().decode(cipherBase64));System.out.println("解密后: " + new String(decryptedBytes, "UTF-8"));}
}
- 核心步骤总结
- 选定算法(如AES-128-CBC),明确参数(密钥长度、分组长度)
- 准备密钥、IV:安全产生并保管
- 填充明文:保证数据块长度是分组长度整数倍
- 调用加密程序得到密文
- 存储密文/传输密文和必要的IV
- 实际使用时解密,获得原文
非对称加密算法
概念
非对称加密算法,又称为公开密钥加密算法。它需要两个密钥,一个称为
公开密钥
(public key),即公钥,另一个称为私有密钥
(private key),即私钥
。因为加密和解密使用的是两个不同的密钥,所以这种算法称为非对称加密算法。
常见类型
算法 | 常用密钥长度(位) | 常见输出长度/密文长度* | 主要用途 | 常用填充方式(如有) |
---|---|---|---|---|
RSA | 1024/2048/3072/4096 | 与密钥长度对应(如2048位密钥加密输出256字节) | 加密、签名 | PKCS#1 v1.5, OAEP |
ECC | 256/384/521 | 与曲线及实现相关(如256位曲线密文约65-133字节) | 加密、签名 | 通常无固定填充 |
SM2 | 256 | 密文约97字节(实现相关略有不同) | 加密、签名 | 通常无固定填充 |
DH | 1024/2048/3072 | 共享密钥长度与参数和实现相关 | 密钥协商 | 不适用(非加密用途) |
输出长度/密文长度指一次加密输出的字节数,依密钥长度、填充、协议而异。DH不用于数据加密,输出为协商的共享密钥。
使用案例
- 原理简述
- 用户B生成RSA密钥对(公钥、私钥),将公钥发给用户A
- 用户A用公钥对消息加密并发送密文
- 用户B用私钥对密文解密,还原出原始消息
- 生成秘钥对java代码示例(java8及以上)
import java.security.*;public class GenerateRSAKeys {public static void main(String[] args) throws Exception {KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");keyGen.initialize(2048);KeyPair pair = keyGen.generateKeyPair();// 得到对象PublicKey pubKey = pair.getPublic();PrivateKey priKey = pair.getPrivate();System.out.println("公钥:" + java.util.Base64.getEncoder().encodeToString(pubKey.getEncoded()));System.out.println("私钥:" + java.util.Base64.getEncoder().encodeToString(priKey.getEncoded()));}
}
- 公钥加密,私钥解密
import java.security.*;
import javax.crypto.Cipher;
import java.util.Base64;public class RSADemo {public static void main(String[] args) throws Exception {// 1. 生成密钥对KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");keyGen.initialize(2048);KeyPair pair = keyGen.generateKeyPair();PublicKey publicKey = pair.getPublic();PrivateKey privateKey = pair.getPrivate();String plaintext = "这是一个敏感信息";// 2. 公钥加密Cipher encryptCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);byte[] encryptedBytes = encryptCipher.doFinal(plaintext.getBytes("UTF-8"));String encryptedBase64 = Base64.getEncoder().encodeToString(encryptedBytes);System.out.println("密文(Base64):" + encryptedBase64);// 3. 私钥解密Cipher decryptCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);byte[] decryptedBytes = decryptCipher.doFinal(Base64.getDecoder().decode(encryptedBase64));String decryptedText = new String(decryptedBytes, "UTF-8");System.out.println("解密后明文:" + decryptedText);}
}
- 小结
- 加密算法:RSA/ECB/OAEPWithSHA-256AndMGF1Padding(更安全,推荐)
- 明文与密文都是字节数组,密文经Base64编码方便显示和传输
- RSA适合小数据加密(通常克长度 < 200 字节),大数据需分段或用混合加密
摘要算法
概念
摘要算法通过哈希运算将原始数据转换为固定长度的摘要。MD5、SHA1等同样可用于摘要算法。摘要算法的特点是摘要值与原始数据密切相关,任何微小的数据变化都会导致摘要值的显著变化。摘要算法常用于检测数据的篡改和验证数据的完整性,但请注意,它并非一种加密算法,而是数据保护的一种辅助手段。
常见类型
算法 | 输出长度(位) | 输出长度(字节) |
---|---|---|
MD5 | 128 | 16 |
SHA-1 | 160 | 20 |
SHA-256 | 256 | 32 |
SHA-384 | 384 | 48 |
SHA-512 | 512 | 64 |
SHA3-256 | 256 | 32 |
SM3 | 256 | 32 |
主要用途
- 数字签名:对数据生成摘要,再对摘要签名,提高处理效率和安全。
- 文件完整性校验:如MD5校验文件传输和下载过程中的完整性。
- 密码存储:将密码哈希后存储,提升账号数据安全性。
- 数据去重和索引:快速查找和排重数据。
使用案例(SM3)
- 使用BouncyCastle库,因为标准Java暂不支持SM2。引入依赖:
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk18on</artifactId><version>1.78</version>
</dependency>
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk18on</artifactId><version>1.78</version>
</dependency>
- 生成SM2秘钥对
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.*;public class SM2KeyPairGenerator {public static KeyPair generateSM2KeyPair() throws Exception {Security.addProvider(new BouncyCastleProvider());KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC", "BC");keyPairGen.initialize(new ECGenParameterSpec("sm2p256v1"), new SecureRandom());return keyPairGen.generateKeyPair();}
}
- 电子合同签名
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.*;public class SM2SignerUtil {static {Security.addProvider(new BouncyCastleProvider());}// 签名public static byte[] sign(byte[] data, PrivateKey privateKey) throws Exception {Signature signature = Signature.getInstance("SM3withSM2", "BC");signature.initSign(privateKey);signature.update(data);return signature.sign();}
}
- 验签
public class SM2VerifyUtil {// 验签public static boolean verify(byte[] data, byte[] sig, PublicKey publicKey) throws Exception {Signature signature = Signature.getInstance("SM3withSM2", "BC");signature.initVerify(publicKey);signature.update(data);return signature.verify(sig);}
}
- 完整使用示例
import java.security.KeyPair;public class ContractSignDemo {public static void main(String[] args) throws Exception {// 1. 生成密钥对KeyPair keyPair = SM2KeyPairGenerator.generateSM2KeyPair();// 2. 模拟合同内容String contract = "甲方向乙方出售一台设备,金额10000元。";byte[] contractBytes = contract.getBytes("UTF-8");// 3. 签名byte[] signature = SM2SignerUtil.sign(contractBytes, keyPair.getPrivate());System.out.println("签名Base64: " + java.util.Base64.getEncoder().encodeToString(signature));// 4. 验签boolean isValid = SM2VerifyUtil.verify(contractBytes, signature, keyPair.getPublic());System.out.println("验签结果: " + isValid);// 5. 模拟合同被篡改byte[] tampered = "甲方向乙方出售一台设备,金额10001元。".getBytes("UTF-8");boolean isValid2 = SM2VerifyUtil.verify(tampered, signature, keyPair.getPublic());System.out.println("篡改内容验签: " + isValid2);}
}
- 输出示例
签名Base64: MCwCFHz...91AIFc...
验签结果: true
篡改内容验签: false
- 小结
- 签名方用私钥对合同内容生成数字签名
- 验签方用公钥对签名及原文进行校验,内容若被篡改则验签失败
- 适用于电子合同、票据、区块链等需要防篡改场景
问题记录(Q&A)
为什么加密用的是字节(byte[])?
- 本质:加密算法(如AES、DES等)本质只处理二进制数据(即一串0和1),在编程里表现为字节数组
- 不像文本:文本(如字符串)可以有各种编码(UTF-8、GBK等),而加密算法不认识什么字符,只认具体的字节
- 准确性:只有用字节,数据的原始位不会丢失,也能保证加密、解密的精准复原
举例
String s = "hello";
byte[] data = s.getBytes("UTF-8"); // 明确转成字节流
cipher.doFinal(data); // 加密本质操作字节
什么是国密算法?
国密算法(中文全称为“国家商用密码算法”),通常指中国国家密码管理局(原国家密码管理局,现属于国家密码管理局和密码科学技术研究所双重管理)发布的一系列面向数据加密、信息安全的密码算法标准。这些算法广泛应用于金融、政务、通信等需要高安全等级的场合,特别是在国产化、合规与自主可控场景下,具有重要意义。
国密算法都有哪些?
算法名 | 类别 | 用途 | 主要特点 |
---|---|---|---|
SM1 | 对称分组加密 | 专用芯片/硬件加密 | 算法未公开,仅限硬件实现,与AES类似,许可使用 |
SM2 | 公钥密码算法 | 数字签名、加密、密钥交换 | 基于椭圆曲线ECC,自主研发,签名加密快于RSA,用于国密证书等 |
SM3 | 杂凑(哈希) | 消息摘要、完整性校验 | 输出256位,功能类似SHA-256,但原理不同,用于防篡改、签名摘要 |
SM4 | 对称分组加密 | 数据加密、VPN、无线通讯加密 | 128位块长和密钥,公开标准,类似于AES,适合软硬件系统效率高 |
ZUC | 流加密算法 | 3G/4G/5G移动通信空口加密 | 面向无线通信设计,运算高效,适合实时加密 |
SM9 | 标识密码算法 | 基于标识的加解密、签名、协商 | 以用户标识(如邮箱、手机号)为公钥,去中心化密钥管理,便于物联网等 |
国际算法都有哪些?
类型 | 代表算法 | 标准组织 | 主要特点 |
---|---|---|---|
对称加密 | AES、3DES、DES | NIST、ISO | 加密解密效率高,适合大数据量加解密;实现简单 |
非对称加密 | RSA、ECC(ECDSA、ECDH)、DSA | NIST、ISO/IETF | 适合密钥交换、数字签名;密钥管理方便;安全性高 |
哈希算法 | SHA-1、SHA-2、SHA-3、MD5 | NIST、ISO | 单向运算、不可逆、防篡改,常用于完整性校验 |
流加密 | ChaCha20、RC4 | IETF | 加解密速度快,适合流式数据(如VPN、TLS) |
消息认证码 | HMAC、CMAC | NIST、ISO | 基于哈希/分组加密,验证消息完整性和认证 |
密钥协商/交换 | DH、ECDH | NIST、IETF | 用于安全地在线协商加密密钥,抵抗窃听和中间人攻击 |
密钥派生 | PBKDF2、HKDF | IETF、NIST | 基于密码/主密钥安全地产生子密钥,增强密码抗攻击性 |
为什么很多时候展示、保存用16进制/BASE64?
- 人类不习惯直接看字节(看不到…也可能是乱码);加密后数据经常包含不可打印字符(比如0x00、0xA7、0xFF等),直接用字符串展现会乱码,写文件/网络传输也可能被破坏
- 16进制和BASE64是常用安全可见表示方法,16进制:每个字节用两位0-9A-F表示,占空间大些,但清晰直观
- Base64:把任意字节流转成ASCII字符,便于在网络、文本里安全传递和保存,比16进制更节省空间,实际用得更多
举例
byte[] b = { (byte)0xAB, (byte)0xCD, (byte)0x01};
String hex = javax.xml.bind.DatatypeConverter.printHexBinary(b); // "ABCD01"
String base64 = java.util.Base64.getEncoder().encodeToString(b); // "q80B"
实际场景
- 数据库保存密文:用16进制字符串或BASE64字符串,而不是原始字节
- 接口传输密文:通常也是Base64
- 加密函数参数:都要求字节流,不要直接用字符串