Files
Knowledge/0 编程/java/JCE.md
T

666 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 一、简介
Java Cryptography Extension(JCE)是一组包,它们提供用于加密、[密钥](https://cloud.tencent.com/developer/tools/blog-entry?target=http%3A%2F%2Fbaike.baidu.com%2Fview%2F934.htm&source=article&objectId=2040606)生成和协商以及 Message Authentication Code(MAC)算法的框架和实现。它提供对对称、不对称、块和流密码的加密支持,它还支持安全流和密封的对象。它不对外出口,用它开发完成封装后将无法调用。
在早期JDK版本中,由于受美国的密码出口条例约束,Java中涉及加解密功能的API被限制出口,所以Java中安全组件被分成了两部分: 不含加密功能的JCA(Java Cryptography Architecture )和含加密功能的JCE(Java Cryptography Extension)。
在JDK1.1-1.3版本期间,JCE属于扩展包,仅供美国和加拿大的用户下载,JDK1.4+版本后,随JDK核心包一起分发。
- [Java加密体系结构(JCA)参考指南](https://cloud.tencent.com/developer/tools/blog-entry?target=http%3A%2F%2Fdocs.oracle.com%2Fjavase%2F7%2Fdocs%2Ftechnotes%2Fguides%2Fsecurity%2Fcrypto%2FCryptoSpec.html&source=article&objectId=2040606)
- [**Java密体系结构标准算法的名字的文档**](https://cloud.tencent.com/developer/tools/blog-entry?target=http%3A%2F%2Fdocs.oracle.com%2Fjavase%2F7%2Fdocs%2Ftechnotes%2Fguides%2Fsecurity%2FStandardNames.html&source=article&objectId=2040606)
## 二、加解密
JCE的API都在 javax.crypto 包下,核心功能包括:加解密、密钥生成(对称)、MAC生成、密钥协商。
### 1. Cipher
加解密功能由Cipher组件提供,其也是JCE中最核心的组件。 Cipher的几个知识点:
1. Cipher在使用时需以参数方式指定transformation
2. transformation的格式为algorithm/mode/padding(_算法/模式/填充_),其中algorithm(算法)为必输项,如: DES/CBC/PKCS5Padding
3. 缺省的mode为ECB,缺省的padding为PKCS5Padding
4. 在block算法与流加密模式组合时, 需在mode后面指定每次处理的bit数, 如DES/CFB8/NoPadding, 如未指定则使用缺省值, SunJCE缺省值为64bits
5. 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的密钥。
<1>密钥长度是在生成密钥时指定的。如:
```javascript
KeyGenerator generator = KeyGenerator.getInstance("AES/CBC/PKCS5PADDING");
generator.init(256);
SecretKey key = generator.generateKey();
```
复制
<2>生成长度超128bits的密钥,需单独从Oracle官网下载对应JDK版本的Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files文件《详见: [java加密——Jasypt开源工具包](https://cloud.tencent.com/developer/tools/blog-entry?target=http%3A%2F%2Fblog.csdn.net%2Ffanxiaobin577328725%2Farticle%2Fdetails%2F51707882&source=article&objectId=2040606)》,例如[JDK7对应的jurisdiction policy files。](https://cloud.tencent.com/developer/tools/blog-entry?target=http%3A%2F%2Fwww.oracle.com%2Ftechnetwork%2Fjava%2Fjavase%2Fdownloads%2Fjce-7-download-432124.html&source=article&objectId=2040606)
### 3.加密示例代码
```javascript
/**
* 根据密钥{@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.解密示例代码
```javascript
/**
* 根据密钥{@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算法进行加解密的示例:
```javascript
/**
* 提供基于口令的加密功能.
*
* @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;
}
```
复制
测试代码:
```javascript
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](https://cloud.tencent.com/developer/tools/blog-entry?target=http%3A%2F%2Fjloser.com%2Fjava%25E5%25AE%2589%25E5%2585%25A8jca%25E7%25BB%2584%25E4%25BB%25B6%2F&source=article&objectId=2040606)中的示例,对称密钥生成的示例代码如下
```javascript
KeyGenerator gen = KeyGenerator.getInstance("DES");
gen.init(56, new SecureRandom());
Key key= gen.generateKey();
```
复制
## 四、MAC生成
MAC技术用于确认数据的完整性,Mac要求通讯双方共享一个secret key,示例代码如下:
```javascript
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种途径实现
1. 通过KeyAgreement组件完成,常用算法包括DH(Diffie-Hellman),ECDH(Elliptic Curve Diffie-Hellman),ECMQV(Elliptic Curve Menezes-Qu-Vanstone)等。
2. 通过数字信封完成,常用算法包括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,分别由两个线程模拟,实际数据交换由共享内存模拟:
```javascript
/*
* @(#)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,对称密钥),使用目标密钥对报文进行加密,然后将密钥密文与报文密文一起发送给接收方。接收方首先使用自己的私钥对密钥报文进行解密,这样就得到了协商后的密钥,再使用解密后的密钥解密报文,这样就得到了业务数据。过程图示如下:
代码示例如下,密钥协商双方由两个线程模拟:
```javascript
/*
* @(#)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()
*/
@Override
public void run() {
try {
String msg = "Legend of AK47";
Key sessionKey = KeyGeneratorDemo.generatePlainDES();
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, sessionKey);
byte[] p = msg.getBytes("UTF-8");
byte[] msgCipher = cipher.doFinal(p);
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, clientPublicKey);
byte[] keyCipher = cipher.doFinal(sessionKey.getEncoded());
Map result = new HashMap();
result.put("msg", msgCipher);
result.put("key", keyCipher);
CHANNEL.offer(result);
} catch (KeyException e) {
// TODO Exception handling...
}
}
/** 客户端的公钥. */
private final PublicKey clientPublicKey;
}
/**
* 模拟密钥交换的客户端, 服务器端与客户端通过共享内存来交换Digital Envelope.
*
* @author Rich, 2012-6-14.
* @version 1.0
* @since 1.0
*/
static class Client extends Thread {
/**
* 密钥对应该在客户端内部产生, 然后客户端在请求服务器端时上送了自己的公钥, 也有可能是在注册时就在服务器端登记了公钥.
*
* @param keyPair 客户端的公私密钥对.
*/
public Client(KeyPair keyPair) {
this.keyPair = keyPair;
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try {
Map received = CHANNEL.take();
byte[] msgCipher = received.get("msg");
byte[] keyCipher = received.get("key");
PrivateKey privateKey = keyPair.getPrivate();
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] encoded = cipher.doFinal(keyCipher);
KeySpec keySpec = new DESKeySpec(encoded);
SecretKeyFactory fac = SecretKeyFactory.getInstance("DES");
Key key = fac.generateSecret(keySpec);
cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] msg = cipher.doFinal(msgCipher);
String plainText = new String(msg, "UTF-8");
System.out.println(plainText);
} catch (Exception e) {
// TODO Exception handling...
}
}
/** 客户端的公私密钥对. */
private final KeyPair keyPair;
}
/** Client端与Server端交换数据的队列, 模拟两者之间通讯的通道, 现实中两者可能是通过Socket通讯的. */
private static final BlockingQueue> CHANNEL = new LinkedBlockingQueue>(1);
}
```
复制
**KeyAgreement的DH与数字信封的RSA比较:**
- a. DH仅限于交换共享密钥,而无法对交换双方的身份进行认证,易受中间人攻击
- b. RSA可以用于交换共享密钥也可以用于[身份认证](https://cloud.tencent.com/solution/tb-digitalid?from_column=20065&from=20065)
- 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就2个规则:
- i. 补长的内容为待补长字节数
- ii. 补长的字节数为:8 – 明文长度 % 8,即补长长度在1至8bytes之间
如前述的明文for将补长为:66 6F 72 05 05 05 05 05ISO10126Padding,具体规范请参考[ISO10126](https://cloud.tencent.com/developer/tools/blog-entry?target=http%3A%2F%2Fwww.iso.org%2Fiso%2Fiso_catalogue%2Fcatalogue_tc%2Fcatalogue_detail.htm%3Fcsnumber%3D18114&source=article&objectId=2040606)。简单的说ISO10126Padding就是补长的长度作为补长内容的最后一个byte,之前的补长内容为随机数。
如前述的明文for可能补长为:66 6F 72 2A 75 EF F8 05
### 5. 密钥的选择
密钥可以使用KeyGenerator/KeyPairGenerator生成,也可以由外部导入,还可以有密钥参数构造KeySpec再转换为Key。
### 6. 密钥长度选择
对于对称加密算法,128bits的密钥足够安全,条件许可请选择256bits,注意密钥长度大于128bits需单独下载并安装jurisdiction policy files;对于非对称加密算法,1024bits的密钥足够安全。
最后,如选用基于口令的算法或在用户输入密码时,请避免使用String来引用,使用char[],用完立刻置空char[],避免内存攻击,如heap dump分析等。
参考资料:
- [Java安全 – JCE](https://cloud.tencent.com/developer/tools/blog-entry?target=http%3A%2F%2F87426628.blog.163.com%2Fblog%2Fstatic%2F606936182013104883902%2F&source=article&objectId=2040606)