阅读完需:约 18 分钟
javax.crypto.Cipher,翻译为密码,其实叫做密码器更加合适。Cipher是JCA(Java Cryptographic Extension,Java加密扩展)的核心,提供基于多种加解密算法的加解密功能。
对称加密和非对称加密的区别
简介:
对称加密: 加密和解密的秘钥使用的是同一个.
非对称加密: 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。
对称加密算法: 密钥较短,破译困难,除了数据加密标准(DES),另一个对称密钥加密系统是国际数据加密算法(IDEA),它比DES的加密性好,且对计算机性能要求也没有那么高.
优点 : 算法公开、计算量小、加密速度快、加密效率高
缺点 : 在数据传送前,发送方和接收方必须商定好秘钥,然后 使双方都能保存好秘钥。其次如果一方的秘钥被泄露,那么加密信息也就不安全了。另外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的唯一秘钥,这会使得收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担。
常见的对称加密算法有: DES、3DES、Blowfish、IDEA、RC4、RC5、RC6 和 AES
非对称加密算法: 公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。甲方只能用其专用密钥解密由其公用密钥加密后的任何信息。
优点 : 安全
缺点 : 速度较慢
常见的非对称加密算法有: RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
Hash算法(摘要算法)
Hash算法特别的地方在于它是一种单向算法,用户可以通过hash算法对目标信息生成一段特定长度的唯一hash值,却不能通过这个hash值重新获得目标信息。因此Hash算法常用在不可还原的密码存储、信息完整性校验等。
常见的摘要算法有: MD2、MD4、MD5、HAVAL、SHA
常用加密算法对比




Cipher 密码加密概述
1、javax.crypto.Cipher 类提供加密和解密的功能,它构成了 Java Cryptography Extension (JCE) —— Java 加密扩展框架的核心。这些都是 Java JDK 原生的 API,不是第三方的。
2、Cipher 的 getInstance(String transformation) 方法可以获取实例,参数 transformation 表示转换名称,包括:加密算法/反馈模式/填充方案。加密算法是必选项,反馈模式与填充方案可以不写使用默认值。如:
Cipher cipher = Cipher.getInstance("AES");
Cipher c = Cipher.getInstance("DES/CBC/PKCS5Padding");
3、Cipher 支持 4 种加密算法:AES、DES、DESede、RSA。

Cipher 常量与常用方法



参考例子
1、使用 CBC 有向量模式时,cipher.init 必须传入 {@link AlgorithmParameterSpec}-算法参数规范。如果使用的是 ECB-无向量模式,那么 cipher.init 则加解密都不需要传 {@link AlgorithmParameterSpec} 参数.
2、生成密钥 SecretKey 与 算法参数规范 AlgorithmParameterSpec 的 key ,AES加密算法时必须是 16 个字节,DES 时必须是 8 字节.
3、加/解密都是调用 Cipher.doFinal(byte[] content) 方法,具体取决于 cipher.init 时 使用的是 Cipher.ENCRYPT_MODE 加密模式,还是 Cipher.DECRYPT_MODE 解密模式.
4、下面演示的是 AES 加密算法,DES 加密算法使用也是同理,区别在于参数:secret_key、vector_key : AES 时必须是 16 个字节,DES 时必须是 8 字节.
加密算法 | 密匙长度 | 向量长度 |
---|---|---|
AES | 16 | 16 |
DES | 8 | 8 |
DES3 | 24 | 8 |
对称加密
/**
* Cipher 密码工具类
*
* @author
* @version 1.0
* @date 2021/5/12 13:09
*/
public class CipherUtils {
/**
* CIPHER_TRANSFORMS : cipher 实例化时的 加密算法/反馈模式/填充方案
* ALGORITHM: 创建密钥时使用的算法
*/
private static final String CIPHER_TRANSFORMS = "AES/CBC/PKCS5Padding";
private static final String ALGORITHM = "AES";
/**
* AES/CBC/PKCS5Padding 加密
*
* @param content :待加密的内容. 内容长度不限制.
* @param secret_key :用于生成密钥的 key,自定义即可,加密与解密必须使用同一个,如果不一致,则抛出异常
* @param vector_key 用于生成算法参数规范的 key,自定义即可,加密与解密必须使用同一个,如果不一致,解密的内容可能会造成与源内容不一致.
* <p>
* 1、secret_key、vector_key: AES 时必须是 16 个字节,DES 时必须是 8 字节.
* 2、secret_key、vector_key 值不建议使用中文,如果是中文,注意一个汉字是3个字节。
* </p>
* @return 返回 Cipher 加密后的数据,对加密后的字节数组用 Base64 进行编码转成了可视字符串,如 7giH2bqIMH3kDMIg8gq0nY
* @throws Exception
*/
public static String encrypt(String content, String secret_key, String vector_key) throws Exception {
//实例化 Cipher 对象。使用:AES-高级加密标准算法、CBC-有向量模式、PKCS5Padding-填充方案:(加密内容不足8位时用余位数补足8位)
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMS);
//使用 SecretKeySpec(byte[] key, String algorithm) 创建密钥. 算法要与 Cipher.getInstance 保持一致.
SecretKey secretKey = new SecretKeySpec(secret_key.getBytes(), ALGORITHM);
/**
* init(int opMode,Key key,AlgorithmParameterSpec params):初始化 Cipher,
* 1、Cipher.ENCRYPT_MODE 表示加密模式
* 2、key 表示加密密钥
* 3、params 表示算法参数规范,使用 CBC 有向量模式时,必须传入,如果是 ECB-无向量模式,那么可以不传
* 4、所有参数规范都必须实现 {@link AlgorithmParameterSpec} 接口.
*/
IvParameterSpec parameterSpec = new IvParameterSpec(vector_key.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
/**
* byte[] doFinal(byte[] content):对 content 完成加密操作,如果 cipher.init 初始化时使用的解密模式,则此时是解密操作.
* 返回的是加密后的字节数组,如果直接 new String(byte[] bytes) 是会乱码的,可以借助 BASE64 转为可视字符串,或者转成 16 进制字符
*/
byte[] encrypted = cipher.doFinal(content.getBytes());
//BASE64Encoder.encode:BASE64 对字节数组内容进行编码,转为可视字符串,这样方便存储和转换.
String base64Encode = new BASE64Encoder().encode(encrypted);
return base64Encode;
}
/**
* AES/CBC/PKCS5Padding 解密。内容长度不限制.
*
* @param base64Encode :待解密的内容,因为加密时使用了 Base64 进行了编码,所以这里传入的也是 Base64 编码后的可视化字符串
* @param secret_key :用于生成密钥的 key,自定义即可,加密与解密必须使用同一个,如果不一致,则抛出异常
* @param vector_key 用于生成算法参数规范的 key,自定义即可,加密与解密必须使用同一个,如果不一致,解密的内容可能会造成与源内容不一致.
* <p>
* 1、secret_key、vector_key:AES 时必须是 16 个字节,DES 时必须是 8 字节.
* 2、secret_key、vector_key 值不建议使用中文,如果是中文,注意一个汉字是3个字节。
* </p>
* @return
* @throws Exception
*/
public static String decrypt(String base64Encode, String secret_key, String vector_key) throws Exception {
//实例化 Cipher 对象。加密算法/反馈模式/填充方案,解密与加密需要保持一致.
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMS);
//创建密钥。算法也要与实例化 Cipher 一致.
SecretKey secretKey = new SecretKeySpec(secret_key.getBytes(), ALGORITHM);
//有向量模式(CBC)需要传入 AlgorithmParameterSpec 算法参数规范参数.
IvParameterSpec parameterSpec = new IvParameterSpec(vector_key.getBytes());
//初始化 cipher。使用解密模式.
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
//将 Base64 编码的内容解码成字节数组(因为加密的时候,对密文使用了 Base64编码,所以这里需要先解码)
byte[] content = new BASE64Decoder().decodeBuffer(base64Encode);
//执行解密操作。返回解密后的字节数组,此时可以使用 String(byte bytes[]) 转成源字符串.
byte[] decrypted = cipher.doFinal(content);
return new String(decrypted);
}
public static void main(String[] args) throws Exception {
String content = "实际上Cipher类实现了多种加密算法,在创建Cipher对象时,传入不同的参数就可以进行不同的加密算法。而这些算法不同的地方只是创建密匙的方法不同而已。";
//生成密钥的 key 与 生成算法参数规范的 key 加解密必须一致,它就像是一把钥匙或者口令,不能忘记.
//AES 加密算法时必须是 16 个字节,DES 时必须是 8 字节.
String slatKey = "wYDJty8o8HE6YjJS";
String vectorKey = "SC35fdGrQozSM中";
String encrypt = encrypt(content, slatKey, vectorKey);
System.out.println("源内容字节大小:" + content.getBytes().length + " 字节" + ", " + content.length() + " 个字符");
System.out.println("源内容:\n" + content);
System.out.println("加密后:\n" + encrypt);
//解密时只要重新拿着同样的 slatKey、vectorKey 就能对数据进行解密.
System.out.println("解密后:\n" + decrypt(encrypt, slatKey, vectorKey));
}
}
结果
源内容字节大小:207 字节, 77 个字符
源内容:
实际上Cipher类实现了多种加密算法,在创建Cipher对象时,传入不同的参数就可以进行不同的加密算法。而这些算法不同的地方只是创建密匙的方法不同而已。
加密后:
8943oqLIGvtU8vmhJFC+jGgQPXCmYExXwo0R+rlHqOAmPCg/Ypii+DMwcE2vHK9ZBBhxb8poyaVy
dqzEqvKTEu+g2lkxuKDPgDP2FdI9+/sHH9TphVCqYPYTpTXViZg1lmEmLw8jhtsn0/7uzsIFLh9v
EgdSud0ulE95+xx0P7XWvHglZStHZ1EppCx57FkvEGfIjka9Uasa4Z5dwuHmOwvNtfua4HIteykO
y7IXQZxsyu8a7qtwVwUoM+q2Qac/T91DSTS+vQ4Aflbz90jMPg==
解密后:
实际上Cipher类实现了多种加密算法,在创建Cipher对象时,传入不同的参数就可以进行不同的加密算法。而这些算法不同的地方只是创建密匙的方法不同而已。
上面简单实现了AES(“AES/CBC/PKCS5Padding”)的加密和解密。可以看到代码中主要的是cipher对象,并有以下调用
(1)新建Cipher对象时需要传入一个参数”AES/CBC/PKCS5Padding”
(2)cipher对象使用之前还需要初始化,共三个参数(“加密模式或者解密模式”,“密匙”,“向量”)
(3)调用数据转换:cipher.doFinal(content),其中content是一个byte数组
实际上Cipher类实现了多种加密算法,在创建Cipher对象时,传入不同的参数就可以进行不同的加密算法。而这些算法不同的地方只是创建密匙的方法不同而已。
如传入“AES/CBC/NoPadding”可进行AES加密,传入”DESede/CBC/NoPadding”可进行DES3加密。
RSA 非对称加密
1. 非对称加密,需要使用 PK(PUBLIC_KEY 公钥) 与 SK( SECRET_KEY 密钥),PK 加密时,必须用 SK 解密、反之 SK 加密时,必须用 PK 解密。
2. 安全性最高,但是速度慢,适合对少量数据加密。
/**
* RSA 非对称加密工具类。
* 1、公钥(PUBLIC_KEY)、私钥:PRIVATE_KEY 必须分开使用,比如公钥加密时,必须是私钥解密,反之私钥加密时,必须是公钥解密
*
* @author
* @version 1.0
* @date 2021/5/12 15:41
*/
public class CipherRsaUtils {
/**
* CIPHER_TRANSFORMS : cipher 实例化时的 加密算法/反馈模式/填充方案。ECB 表示无向量模式
* ALGORITHM: 创建密钥时使用的算法
* KEY_PAIR_LENGTH: 秘钥对长度。数值越大,能加密的内容就越大。
* <p>
* 如 KEY_PAIR_LENGTH 为 1024 时加密数据的长度不能超过 117 字节
* 如 KEY_PAIR_LENGTH 为 2048 时加密数据的长度不能超过 245 字节
* 依次类推
* </p>
*/
private static final String CIPHER_TRANSFORMS = "RSA/ECB/PKCS1Padding";
private static final int KEY_PAIR_LENGTH = 1024;
/**
* 生成 RSA 密钥对:公钥(PUBLIC_KEY)、私钥:PRIVATE_KEY
*
* @param secureRandomSeed :SecureRandom 随机数生成器的种子,只要种子相同,则生成的公钥、私钥就是同一对.
* <p>randomSeed 长度可以自定义,加/解密必须是同一个.</p>
* @return
*/
public static KeyPair generateRsaKeyPair(String secureRandomSeed) {
//KeyPair 是密钥对(公钥和私钥)的简单持有者。加密、解密都需要使用.
KeyPair keyPair = null;
try {
//获取生成 RSA 加密算法的公钥/私钥对 KeyPairGenerator 对象
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
//获取实现指定算法(SHA1PRNG)的随机数生成器(RNG)对象.
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
//重新设定此随机对象的种子.
secureRandom.setSeed(secureRandomSeed.getBytes());
/**
* initialize(int keySize, SecureRandom random):使用给定的随机源(random)初始化特定密钥大小的密钥对生成器。
* keySize: 健的大小值,这是一个特定于算法的度量。值越大,能加密的内容就越多,否则会抛异常:javax.crypto.IllegalBlockSizeException: Data must not be longer than xxx bytes
* 如 keySize 为 2048 时加密数据的长度不能超过 245 字节。
*/
keyPairGenerator.initialize(KEY_PAIR_LENGTH, secureRandom);
//genKeyPair(): 生成密钥对
keyPair = keyPairGenerator.genKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return keyPair;
}
/**
* 使用私钥(PrivateKey)对数据进行加密 或 解密
*
* @param content :待加/解密的数据。如果待解密的数据,则是 Base64 编码后的可视字符串.
* @param model :1 表示加密模式(Cipher.ENCRYPT_MODE),2 表示解密模式(Cipher.DECRYPT_MODE)
* @param secureRandomSeed :SecureRandom 随机数生成器的种子,只要种子相同,则生成的公钥、私钥就是同一对.
* 加解密必须是同一个.
* @return :返回加/解密后的数据,如果是加密,则将字节数组使用 Base64 转为可视字符串.
*/
public static String cipherByPrivateKey(String content, int model, String secureRandomSeed) {
String result = "";
try {
//获取 RSA 密钥对
KeyPair keyPair = generateRsaKeyPair(secureRandomSeed);
/**getPrivate():获取密钥对的私钥。
* getEncoded(): 返回已编码的密钥,如果密钥不支持编码,则返回 null*/
byte[] privateEncoded = keyPair.getPrivate().getEncoded();
/**PKCS8EncodedKeySpec(byte[] encodedKey): 使用给定的编码密钥创建新的 PKCS8EncodedKeySpec
* PKCS8EncodedKeySpec 表示 ASN.1 号私钥的编码*/
EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(privateEncoded);
//创建 KeyFactory 对象,用于转换指定算法(RSA)的公钥/私钥。
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//从提供的密钥规范生成私钥对象
PrivateKey keyPrivate = keyFactory.generatePrivate(encodedKeySpec);
//实例化 Cipher 对象。
result = encryptionDecryption(content, model, keyPrivate);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 使用公钥(PublicKey)对数据进行加密 或 解解
*
* @param content :待加解密的数据
* @param model :1 表示加密模式(Cipher.ENCRYPT_MODE),2 表示解密模式(Cipher.DECRYPT_MODE)
* @param secureRandomSeed :SecureRandom 随机数生成器的种子,只要种子相同,则生成的公钥、私钥就是同一对.
* 加解密必须是同一个.
* @return :返回加密 或者 解密后的数据
*/
public static String cipherByPublicKey(String content, int model, String secureRandomSeed) {
String result = "";
try {
// 得到公钥
KeyPair keyPair = generateRsaKeyPair(secureRandomSeed);
EncodedKeySpec keySpec = new X509EncodedKeySpec(keyPair.getPublic().getEncoded());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey keyPublic = keyFactory.generatePublic(keySpec);
// 数据加/解密
result = encryptionDecryption(content, model, keyPublic);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 加密或解密
*
* @param content :待加解密的数据
* @param model :1 表示加密模式(Cipher.ENCRYPT_MODE),2 表示解密模式(Cipher.DECRYPT_MODE)
* @param key :公钥(PUBLIC_KEY)或 私钥(PRIVATE_KEY)的 key
* @return
*/
private static String encryptionDecryption(String content, int model, Key key) {
String result = "";
try {
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMS);
//init(int opMode, Key key):初始化 Cipher.
cipher.init(model, key);
//如果是解密模式,则需要使用 Base64 进行解码.
byte[] contentBytes = content.getBytes();
if (model == Cipher.DECRYPT_MODE) {
contentBytes = new BASE64Decoder().decodeBuffer(content);
}
/**执行加密 或 解密操作。如果 contentBytes 内容过长,则 doFinal 可能会抛出异常.
*javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 bytes :数据不能超过xxx字节
* 此时需要调大 KEY_PAIR_LENGTH 的值*/
byte[] finalBytes = cipher.doFinal(contentBytes);
//如果是加密,则将加密后的字节数组使用 Base64 转成可视化的字符串。否则是解密时,直接 new String 字符串.
if (model == Cipher.ENCRYPT_MODE) {
result = new BASE64Encoder().encode(finalBytes);
} else if (model == Cipher.DECRYPT_MODE) {
result = new String(finalBytes);
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
String content = "安全性最高,但是速度慢,适合对少量数据加密";
String secureRandomSeed = "TPCz0lDvTHybGMsHTJi3mJ7Pt48llJmRHb";
System.out.println("被加密数据字节大小:" + content.getBytes().length + " 字节," + content.length() + " 个字符");
System.out.println("源内容:\n" + content);
try {
//公钥、私钥必须分开使用,公钥解密时,必须是私钥解密,反之亦然.
String encrypted = CipherRsaUtils.cipherByPublicKey(content, 1, secureRandomSeed);
String decrypted = CipherRsaUtils.cipherByPrivateKey(encrypted, 2, secureRandomSeed);
System.out.println("加密后:\n" + encrypted);
System.out.println("解密后:\n" + decrypted);
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果
被加密数据字节大小:63 字节,21 个字符
源内容:
安全性最高,但是速度慢,适合对少量数据加密
加密后:
Mm5e1tcsg8I5yA+BjdrNrEnZzClcg0GcYgNlUrc2vcUWwDCW3j9pHID6NLL2oaLm9UtWg4AGa0D+
WB3xAxXqWiPKttM+4xsYXqgatQzxBCDdBRNKnxcUXjGREpHRwt6yA6KBjd5o4UI3YxUv7pMEh+LR
L4DUk9iVUhG5LWx+OpQ=
解密后:
安全性最高,但是速度慢,适合对少量数据加密