Java安全 – JCE Blowfish算法报错
代码里用 Blowfish 算法加解密,结果 jdk 升到 1.7 后算法初始化失败
java.lang.RuntimeException: java.lang.RuntimeException: PANIC: Unreachable code reached.
JCE 不熟悉,更新到原来 jdk6 的 security 后报
java.lang.SecurityException: The jurisdiction policy files are not signed by a trusted signer
解决方法:
单独下载 JCE 扩展:需单独从 Oracle 官网下载对应 JDK 版本的 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files
JDK7对应的 jurisdiction policy files:
http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
JDK8
http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
jdk6
http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
JCE,Java Cryptography Extension,在早期 JDK 版本中,由于受美国的密码出口条例约束,Java 中涉及加解密功能的 API 被限制出口,所以 Java 中安全组件被分成了两部分: 不含加密功能的 JCA(Java Cryptography Architecture )和含加密功能的 JCE(Java Cryptography Extension)。在 JDK1.1-1.3 版本期间,JCE 属于扩展包,仅供美国和加拿大的用户下载,JDK1.4+ 版本后,随 JDK 核心包一起分发;
JCA 和 JCE 的 API 体系架构
JCE 的 API 都在 javax.crypto 包下,核心功能包括:加解密、密钥生成(对称)、MAC 生成、密钥协商,下面我们就详细介绍下这些功能。
一. 加解密
加解密功能由 Cipher 组件提供,其也是 JCE 中最核心的组件。
1. Cipher 的几个知识点:
——————————————————————————————–
a. Cipher 在使用时需以参数方式指定 transformation
b. transformation 的格式为 algorithm/mode/padding,其中 algorithm 为必输项,如: DES/CBC/PKCS5Padding
c. 缺省的 mode 为 ECB,缺省的 padding 为 PKCS5Padding
d. 在 block 算法与流加密模式组合时, 需在 mode 后面指定每次处理的 bit 数, 如 DES/CFB8/NoPadding, 如未指定则使用缺省值, SunJCE 缺省值为 64bits
e. Cipher 有 4 种操作模式: ENCRYPT_MODE(加密), DECRYPT_MODE(解密), WRAP_MODE(导出 Key), UNWRAP_MODE(导入 Key),初始化时需指定某种操作模式
2. 对称加密的算法与密钥长度选择
算法名称 | 密钥长 | 块长 | 速度 | 说明 |
---|---|---|---|---|
DES | 56 | 64 | 慢 | 不安全, 不要使用 |
3DES | 112/168 | 64 | 很慢 | 中等安全, 适合加密较小的数据 |
AES | 128, 192, 256 | 128 | 快 | 安全 |
Blowfish | (4 至 56)*8 | 64 | 快 | 应该安全, 在安全界尚未被充分分析、论证 |
RC4 | 40-1024 | 64 | 很快 | 安全性不明确 |
一般情况下,不要选择 DES 算法,推荐使用 AES 算法。一般认为 128bits 的密钥已足够安全,如果可以请选择 256bits 的密钥。注意:
——————————————————————————————–
a. 密钥长度是在生成密钥时指定的,如:
KeyGenerator generator = KeyGenerator.getInstance("AES/CBC/PKCS5PADDING");
generator.init(256);
SecretKey key = generator.generateKey();
b. 生成长度超 128bits 的密钥,需单独从 Oracle 官网下载对应 JDK 版本的 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files文件,例如JDK7 对应的 jurisdiction policy files
3. 加密示例代码
/**
* 根据密钥 {@link #getKey()} 对指定的明文 plainText 进行加密.
*
* @param plainText 明文
* @return 加密后的密文.
*/
public static final String encrypt(String plainText) {
Key secretKey = getKey();
try {
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] p = plainText.getBytes("UTF-8");
byte[] result = cipher.doFinal(p);
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(result);
return encoded;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
4. 解密示例代码
/**
* 根据密钥 {@link #getKey()} 对指定的密文 cipherText 进行解密.
*
* @param cipherText 密文
* @return 解密后的明文.
*/
public static final String decrypt(String cipherText) {
Key secretKey = getKey();
try {
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
BASE64Decoder decoder = new BASE64Decoder();
byte[] c = decoder.decodeBuffer(cipherText);
byte[] result = cipher.doFinal(c);
String plainText = new String(result, "UTF-8");
return plainText;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
5. 带算法参数的加解密
Cipher 可能用到算法参数(AlgorithmParameterSpec 或 AlgorithmParameters)的情形:
——————————————————————————————–
a. DES, DES-EDE, and Blowfish 使用 feedback 模式时 (如 CBC, CFB, OFB 或 PCBC), 将用到 IV
b. PBEWithMD5AndDES 将用到 salt 和 iteration count
下面是采用 PBE 算法进行加解密的示例:
/**
* 提供基于口令的加密功能.
*
* @param plainText 明文
* @return 加密后的密文.
*/
public static final String pbeEncrypt(String plainText) {
Key pbeSecretKey = getPBEKey();
PBEParameterSpec pbeParamSpec = getParamSpec();
try {
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
cipher.init(Cipher.ENCRYPT_MODE, pbeSecretKey, pbeParamSpec);
byte[] p = plainText.getBytes("UTF-8");
byte[] result = cipher.doFinal(p);
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(result);
return encoded;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
- 提供基于口令的解密功能.
- @param cipherText 密文
- @return 解密后的明文.
*/
public static final String pbeDecrypt(String cipherText) {
Key pbeSecretKey = getPBEKey();
PBEParameterSpec pbeParamSpec = getParamSpec();
try {
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
cipher.init(Cipher.DECRYPT_MODE, pbeSecretKey, pbeParamSpec);
BASE64Decoder decoder = new BASE64Decoder();
byte[] c = decoder.decodeBuffer(cipherText);
byte[] result = cipher.doFinal(c);
String plainText = new String(result, "UTF-8");
return plainText;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
- 获取 PBE 算法的密钥. 注意 PBE 密钥由用户提供的口令构造出来的,
- 用户提供的口令务必使用 char 数组, 而不能使用字符串, 字符数
- 组用完即清空.
- @return PBE 算法的密钥.
*/
private static final Key getPBEKey() {
// TODO come from db or System.in, NOTE: MUST be char array, not java.lang.String
char[] pwd = {'%', '_', 'A', 's', '9', 'K'};
SecretKey pbeKey = null;
PBEKeySpec pbeKeySpec = new PBEKeySpec(pwd);
try {
SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
pbeKey = keyFac.generateSecret(pbeKeySpec);
return pbeKey;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
Arrays.fill(pwd, ' ');
}
}
/**
- 获取 PBE 的算法参数, 涉及 salt 和 iterate count 两个参数.
@return PBE的算法参数.
*/
private static final PBEParameterSpec getParamSpec() {
byte[] salt = { (byte) 0xab, (byte) 0x58, (byte) 0xa1, (byte) 0x8c,
(byte) 0x3e, (byte) 0xc8, (byte) 0x9d, (byte) 0x7a };
int count = 20;
PBEParameterSpec paramSpec = new PBEParameterSpec(salt, count);
return paramSpec;
}
测试代码:
String pbePlainText = "127Kjk$%2^";
String pbeCipherText = pbeEncrypt(pbePlainText);
String pbePlainText2 = pbeDecrypt(pbeCipherText);
if (!pbePlainText.equals(pbePlainText2)) {
System.out.println("PBE Something wrong");
}
二. 密钥生成
非对称密钥的生成请参考 java.security.KeyPairGenerator,样例代码请参考JCA中的示例,对称密钥生成的示例代码如下:
KeyGenerator gen = KeyGenerator.getInstance("DES");
gen.init(56, new SecureRandom());
Key key= gen.generateKey();
三. MAC 生成
MAC 技术用于确认数据的完整性,Mac 要求通讯双方共享一个 secret key,示例代码如下:
Key key = KeyGeneratorDemo.generateMac();
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
String msg = "新庄杨渡 10#";
byte[] result = mac.doFinal(msg.getBytes("UTF-8"));
BASE64Encoder encoder = new BASE64Encoder();
System.out.println(encoder.encode(result));
MAC 优于数据摘要的地方在于:MAC 双方要共享一个密钥,所以其也有互相认证的功能,可有效防止数据摘要中明文和数据摘要被同时替换而无法发现的问题。
四. 密钥协商
密钥协商就是在通讯多方间不直接交换通讯密钥的情况下而选择一个大家达成一致的密钥 (session key),这个 session key 是对称密钥。
1. 密钥协商可以通过 2 种途径实现:
——————————————————————————————–
a. 通过 KeyAgreement 组件完成,常用算法包括 DH(Diffie-Hellman),ECDH(Elliptic Curve Diffie-Hellman),ECMQV(Elliptic Curve Menezes-Qu-Vanstone)等。
b. 通过数字信封完成,常用算法包括 RSA 等。
2. 通过 KeyAgreement 使用 DH 算法协商密钥
a. DH 算法由 PKCS#3 定义,DH 算法需在多方间交换公钥,大素数 p,私钥的基数 g,和私钥的长度 l。设协商密钥的双方为 Alice 和 Bob,则协商共涉及 5 个阶段:
——————————————————————————————–
i. Alice 生成 DH 公私密钥对
ii. Alice 将公钥和算法参数 p,g 和 l 发送给 Bob
iii. Bob 根据算法参数生成自己的公私密钥对,并将公钥发送给 Alice
iv. Alice 使用自己的私钥和 Bob 的公钥通过 KeyAgreement 得到通讯密钥
v. Bob 使用自己的私钥和 Alice 的公钥通过 KeyAgreement 得到与 Alice 相同的通讯密钥
b. 下面的代码演示了使用 DH 算法实现密钥协商,设通讯一方为 Alice,另一方为 Bob,分别由两个线程模拟,实际数据交换由共享内存模拟:
/*
* @(#)KeyAgreementDemo.java 1.0 2012-4-24
*
* Copyright 2010 Richard Chen(utopia_rabbi@sse.buaa.edu.cn) All Rights Reserved.
* PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package charpter.security.keyagreement;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DHParameterSpec;
/**
-
演示 Deffie-Hellman 密钥交换组件的用法.
-
密钥交换就是在通讯多方间不直接交换通讯密钥的情况下而选择一个大家达成一致的密钥.
-
@author Rich, 2012-4-24.
-
@version 1.0
-
@since 1.0
/
public class KeyAgreementDemo implements Runnable {
/* bob 和 alice 的公钥内容. /
byte bob[], alice[];
/* alice 侧是交换的发起方, 是否已启动. /
boolean doneAlice = false;
/* 使用计算出来的交换密钥加密过的报文. /
byte[] ciphertext;
/* DH 算法涉及的算法参数, alice 侧的大素数 p, alice 侧的私钥基数 g. /
BigInteger aliceP, aliceG;
/* DH 算法涉及的算法参数, alice 的私钥长度 l. */
int aliceL;
public synchronized void run() {
if (!doneAlice) {
doAlice();
doneAlice = true;
} else
doBob();
}
public synchronized void doAlice() {
try {
// Step 1: Alice generates a key pair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
// Step 2: Alice sends the public key and the
// Diffie-Hellman key parameters to Bob
Class dhClass = Class.forName("javax.crypto.spec.DHParameterSpec");
DHParameterSpec dhSpec = ((DHPublicKey) kp.getPublic()).getParams();
aliceG = dhSpec.getG();
aliceP = dhSpec.getP();
aliceL = dhSpec.getL();
alice = kp.getPublic().getEncoded();
notify();
// Step 4 part 1: Alice performs the first phase of the
// protocol with her private key
KeyAgreement ka = KeyAgreement.getInstance("DH");
ka.init(kp.getPrivate());
// Step 4 part 2: Alice performs the second phase of the
// protocol with Bob's public key
while (bob == null) {
wait();
}
KeyFactory kf = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(bob);
PublicKey pk = kf.generatePublic(x509Spec);
ka.doPhase(pk, true);
// Step 4 part 3: Alice can generate the secret key
byte secret[] = ka.generateSecret();
// Step 6: Alice converts a secret key
SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
DESKeySpec desSpec = new DESKeySpec(secret);
SecretKey key = skf.generateSecret(desSpec);
// Step 7: Alice encrypts data with the key and sends
// the encrypted data to Bob
Cipher c = Cipher.getInstance("DES/ECB/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, key);
ciphertext = c.doFinal("Stand and unfold yourself".getBytes());
notify();
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void doBob() {
try {
// Step 3: Bob uses the parameters supplied by Alice
// to generate a key pair and sends the public key
while (alice == null) {
wait();
}
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
DHParameterSpec dhSpec = new DHParameterSpec(aliceP, aliceG, aliceL);
kpg.initialize(dhSpec);
KeyPair kp = kpg.generateKeyPair();
bob = kp.getPublic().getEncoded();
notify();
// Step 5 part 1: Bob uses his private key to perform the
// first phase of the protocol
KeyAgreement ka = KeyAgreement.getInstance("DH");
ka.init(kp.getPrivate());
// Step 5 part 2: Bob uses Alice's public key to perform
// the second phase of the protocol.
KeyFactory kf = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(alice);
PublicKey pk = kf.generatePublic(x509Spec);
ka.doPhase(pk, true);
// Step 5 part 3: Bob generates the secret key
byte secret[] = ka.generateSecret();
// Step 6: Bob generates a DES key
SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
DESKeySpec desSpec = new DESKeySpec(secret);
SecretKey key = skf.generateSecret(desSpec);
// Step 8: Bob receives the encrypted text and decrypts it
Cipher c = Cipher.getInstance("DES/ECB/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, key);
while (ciphertext == null) {
wait();
}
byte plaintext[] = c.doFinal(ciphertext);
System.out.println("Bob got the string" + new String(plaintext));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
KeyAgreementDemo test = new KeyAgreementDemo();
new Thread(test).start(); // Starts Alice
new Thread(test).start(); // Starts Bob
}
}
以上代码参考了 Java Cryptography Extension (JCE) Reference Guide(JDK5.0) 中 Appendix F 的例子。
3. 通过数字信封使用 RSA 算法协商密钥
数字信封的原理就是利用通讯对方的公钥加密目标密钥(session key,对称密钥),使用目标密钥对报文进行加密,然后将密钥密文与报文密文一起发送给接收方。接收方首先使用自己的私钥对密钥报文进行解密,这样就得到了协商后的密钥,再使用解密后的密钥解密报文,这样就得到了业务数据。过程图示如下:
代码示例如下,密钥协商双方由两个线程模拟:
/*
* @(#)DigitalEnvelopeDemo.java 1.0 2012-6-14
*
* Copyright 2010 Richard Chen(utopia_rabbi@sse.buaa.edu.cn) All Rights Reserved.
* PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package charpter.security.keyagreement;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import charpter.security.key.KeyGeneratorDemo;
/**
演示使用Digital envelope技术进行密钥交换.
*
* 服务器端与客户端分别由两个独立的线程模拟, 双方指间的通讯由共享内存实现. * * @author Rich, 2012-6-14. * @version 1.0 * @since 1.0 */ public final class DigitalEnvelopeDemo { /** * @param args */ public static void main(String[] args) {KeyPair pair = generatorKeyPair(); Thread client = new Client(pair); Thread server = new Server(pair.getPublic()); server.start(); client.start(); } /** * 生成 RSA 算法的公私密钥对. * * @return 生成 RSA 算法的公私密钥对. */ public static final KeyPair generatorKeyPair() { KeyPairGenerator keyGen = null; try { keyGen = KeyPairGenerator.getInstance("RSA");} catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } SecureRandom random = null; try { random = SecureRandom.getInstance("SHA1PRNG");} catch (Exception e) { throw new RuntimeException(e); } random.setSeed(53); keyGen.initialize(1024, random); KeyPair pair = keyGen.generateKeyPair(); return pair; } /** * 模拟密钥交换的服务器端, 服务器端与客户端通过共享内存来交换 Digital Envelope. * * @author Rich, 2012-6-14. * @version 1.0 * @since 1.0 */ static class Server extends Thread { /** * 实际中有可能是客户端在请求服务器端时上送了自己的公钥, 也有可能是在注册 * 时就在服务器端登记了公钥. * * @param clientPublicKey 客户端的公钥. */ public Server(PublicKey clientPublicKey) { this.clientPublicKey = clientPublicKey; } /* (non-Javadoc) * @see java.lang.Thread#run() */
KeyAgreement 的 DH 与数字信封的 RSA 比较:
——————————————————————————————–
a. DH 仅限于交换共享密钥,而无法对交换双方的身份进行认证,易受中间人攻击
b. RSA 可以用于交换共享密钥也可以用于身份认证
c. 建议:在双方都有数字证书时,使用 RSA,一方或两方都没有数字证书则使用 Diffie-Hellman,SSL3.0 就是采用的此策略
五. 总结
JCE 中最常用和最核心的功能就是加解密,此功能由 Cipher 组件提供,在使用 Cipher 前需对加密算法及参数先做出选择:
1. 算法选择
对称算法一般速度较快,非对称算法速度较慢;对称算法的密钥管理比较困难,非对称算法密钥管理简单;非对称算法一般用于认证和加密会话密钥,通讯双方大部分也就是在开启会话时使用一次,对称算法一般用来加密双方之间的报文 / 交换的数据,使用频度较高。
2. 块 / 流模式选择
块(Block)模式加密以块为基本单位,适用于明文长度已知的情形;流(Stream)模式以 bit 或 byte 为加解密单位, 适用于明文长度未知、内容较大的情形,如加密一个套接字管道或文件读写流等,一般仅适用于硬件实现。块模式下不同算法的块大小可能不一样,一般都是 2 的次方数,大部分长度为 64bits,整个明文长度不是块长度整倍数时,需在最后一个 Block 进行补长(Padding)
3. 反馈模式选择
使用块算法加密,如果明文有大量重复的内容,则对块加密后得到的密文也会存在大量的重复,这对密文分析、破解提供了极大的便利,为消除这方面的威胁,有个思路就是对不同块密文再进行运算,这样就极大去除了块密文与块明文几间的特征关联,这种做法称为块反馈模式。常见的反馈模式有:ECB、CBC、CFB、OFB 等。对于第 1 个 block,因没有其它块密文可供运算,有的模式引入了初始矢量(Initialization Vector,IV,由用户指定)作为第 1 个 block 内容,这样就进一步解决了第 1 个 block 密文的脆弱性。注意:尽量不要使用 ECB 模式。
4. 补长方案选择
下面是一些常见的补长(Padding)方案,以 DES 算法加密明文 for 为例,因 for 不足 8bytes,所以需补长 5bytes(for ?? ?? ?? ?? ??),则这 5bytes 可能选择(16 进制):
——————————————————————————————–
i. 所有 Padding 以长度为值:66 6F 72 05 05 05 05 05
ii. Padding 以 0×80 开始后面全部为 0×00:66 6F 72 80 00 00 00 00
iii. 最后一个字节为 Padding 长度, 其它为 0×00:66 6f 72 00 00 00 00 05
iv. 全部 Padding 为 0×00:66 6f 72 00 00 00 00 00
v. 全部 Padding 为 0×20(空格):66 6f 72 20 20 20 20 20
JCE 中支持的补长方案包括:NoPadding、PKCS5Padding、ISO10126Padding、OAEPWithAndPadding 和 SSL3Padding,NoPadding 即不补长,其中最常用的就是 PKCS5Padding 和 ISO10126Padding。
PKCS5Padding,具体规范请参考 RSA 实验室的文档:PKCS #5 Password-Based Encryption Standard,Version 1.5。简单的说 PKCS5Padding 就 2 个规则:
——————————————————————————————–
i. 补长的内容为待补长字节数
ii. 补长的字节数为:8 – 明文长度 % 8,即补长长度在 1 至 8bytes 之间
如前述的明文 for 将补长为:66 6F 72 05 05 05 05 05
ISO10126Padding,具体规范请参考ISO 10126。简单的说 ISO10126Padding 就是补长的长度作为补长内容的最后一个 byte,之前的补长内容为随机数。如前述的明文 for 可能补长为:66 6F 72 2A 75 EF F8 05
7. 密钥的选择
密钥可以使用 KeyGenerator/KeyPairGenerator 生成,也可以由外部导入,还可以有密钥参数构造 KeySpec 再转换为 Key。
6. 密钥长度选择
对于对称加密算法,128bits 的密钥足够安全,条件许可请选择 256bits,注意密钥长度大于 128bits 需单独下载并安装 jurisdiction policy files;对于非对称加密算法,1024bits 的密钥足够安全。
最后,如选用基于口令的算法或在用户输入密码时,请避免使用 String 来引用,使用 char[],用完立刻置空 char[],避免内存攻击,如 heap dump 分析等。
参考链接:http://www.ithao123.cn/content-10346659.html