对称、非对称加密技术
加密技术可以分为对称与非对称两种.
对称加密,解密,即加密与解密用的是同一把秘钥,常用的对称加密技术有DES,AES等
而非对称技术,加密与解密用的是不同的秘钥,常用的非对称加密技术有RSA等
为什么要有非对称加密,解密技术呢
假设这样一种场景A要发送一段消息给B,但是又不想以明文发送,所以就需要对消息进行加密.如果采用对称加密技术,那么加密与解密用的是同一把秘钥.除非B事先就知道A的秘钥,并且保存好.这样才可以解密A发来的消息.
由于对称技术只有一把秘钥,所以秘钥的管理是一个很麻烦的问题.而非对称技术的诞生就解决了这个问题.非对称加密与解密使用的是不同的秘钥,并且秘钥对是一一对应的,即用A的私钥加密的密文只有用A的公钥才能解密.
这样的话,每个人都有两把秘钥,私钥和公钥,私钥是只有自己才知道的,不能告诉别人,而公钥是公开的,大家都可以知道.这样,当A想要发送消息给B的时候,只需要用B的公钥对消息进行加密就可以了,由于B的私钥只有B才拥有,所以A用B的公钥加密的消息只有B才能解开.而B想更换自己的秘要时也很方便,只须把公钥告诉大家就可以了.
那么,既然非对称加密如此之好,对称加密就没有存在的必要了啊,其实不然,由于非对称加密算法的开销很大,所以如果直接以非对称技术来加密发送的消息效率会很差.那么怎么办呢?解决的办法也很简单,就是把对称加密技术与非对称加密技术结合起来使用.
还是这个例子:A要发送一个消息给B.
一,A先生成一个对称秘钥,这个秘钥可以是随机生成的,
二,A用B的公钥加密第一步生成的这个对称秘钥
三,A把加密过的对称秘钥发给B
四,A用第一步生成的这个对称秘钥加密实际要发的消息
五,A把用对称秘钥加密的消息发给B
对于B
他先收到A发来的对称秘钥,这个秘钥是用B的公钥加密过的,所以B需要用自己的私钥来解密这个秘钥
然后B又收到A发来的密文,这时候用刚才解密出来的秘钥来解密密文
这样子的整个过程既保证了安全,又保证了效率.
接下来是Java实现:
我这个Java实现使用的是AES的对称加密和RSA的非对称加密(DES的对称加密实现方法和AES的是一样的,但是由于DES算法本身有缺陷,容易被破解,所以现在多用其升级版AES对称加密)
AES对称加密,解密
importjava.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
importjavax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
publicclass AES {
private Key key;
/**
* 生成AES对称秘钥
* @throws NoSuchAlgorithmException
*/
public void generateKey() throws NoSuchAlgorithmException {
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom random = new SecureRandom();
keygen.init(random);
this.key = keygen.generateKey();
}
/**
* 加密
* @param in
* @param out
* @throws InvalidKeyException
* @throws ShortBufferException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws IOException
*/
public void encrypt(InputStream in, OutputStream out) throwsInvalidKeyException, ShortBufferException, IllegalBlockSizeException,BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,IOException {
this.crypt(in, out, Cipher.ENCRYPT_MODE);
}
/**
* 解密
* @param in
* @param out
* @throws InvalidKeyException
* @throws ShortBufferException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws IOException
*/
public void decrypt(InputStream in, OutputStream out) throwsInvalidKeyException, ShortBufferException, IllegalBlockSizeException,BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,IOException {
this.crypt(in, out, Cipher.DECRYPT_MODE);
}
/**
* 实际的加密解密过程
* @param in
* @param out
* @param mode
* @throws IOException
* @throws ShortBufferException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
*/
public void crypt(InputStream in, OutputStream out, int mode) throwsIOException, ShortBufferException, IllegalBlockSizeException,BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,InvalidKeyException {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(mode, this.key);
int blockSize = cipher.getBlockSize();
int outputSize = cipher.getOutputSize(blockSize);
byte[] inBytes = new byte[blockSize];
byte[] outBytes = new byte[outputSize];
int inLength = 0;
boolean more = true;
while (more) {
inLength = in.read(inBytes);
if (inLength == blockSize) {
int outLength = cipher.update(inBytes, 0, blockSize,outBytes);
out.write(outBytes, 0, outLength);
} else {
more = false;
}
}
if (inLength > 0)
outBytes = cipher.doFinal(inBytes, 0, inLength);
else
outBytes = cipher.doFinal();
out.write(outBytes);
out.flush();
}
publicvoid setKey(Key key) {
this.key = key;
}
publicKey getKey() {
return key;
}
}
RSA非对称加密,解密对称秘钥
publicclass RSA {
publicstatic final int KEYSIZE = 512;
private KeyPair keyPair;
private Key publicKey;
private Key privateKey;
/**
* 生成秘钥对
* @return
* @throws NoSuchAlgorithmException
*/
public KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator pairgen =KeyPairGenerator.getInstance("RSA");
SecureRandom random = new SecureRandom();
pairgen.initialize(RSA.KEYSIZE, random);
this.keyPair = pairgen.generateKeyPair();
return this.keyPair;
}
/**
* 加密秘钥
* @param key
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
*/
public byte[] wrapKey(Key key) throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.WRAP_MODE, this.privateKey);
byte[] wrappedKey = cipher.wrap(key);
return wrappedKey;
}
/**
* 解密秘钥
* @param wrapedKeyBytes
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
*/
public Key unwrapKey(byte[] wrapedKeyBytes) throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.UNWRAP_MODE, this.publicKey);
Key key = cipher.unwrap(wrapedKeyBytes, "AES",Cipher.SECRET_KEY);
return key;
}
publicKey getPublicKey() {
return publicKey;
}
publicvoid setPublicKey(Key publicKey) {
this.publicKey = publicKey;
}
publicKey getPrivateKey() {
return privateKey;
}
publicvoid setPrivateKey(Key privateKey) {
this.privateKey = privateKey;
}
}
Illegal key size ordefault parameters
Java几乎各种常用加密算法都能找到对应的实现。因为美国的出口限制,Sun通过权限文件(local_policy.jar、US_export_policy.jar)做了相应限制。因此存在一些问题:
●密钥长度上不能满足需求(如:java.security.InvalidKeyException: Illegal key size or defaultparameters);
●部分算法未能支持,如MD4、SHA-224等算法;
●API使用起来还不是很方便;一些常用的进制转换辅助工具未能提供,如Base64编码转换、十六进制编码转换等工具。
Oracle在其官方网站上提供了无政策限制权限文件(Unlimited Strength Jurisdiction Policy Files),我们只需要将其部署在JRE环境中,就可以解决限制问题。
下载地址:
●Java 5.0 无政策限制文件
●Java 6 无政策限制文件
●Java 7 无政策限制文件
●其他版本无政策限制文件
下载的压缩包中仅有一个目录,也就是jce目录。该目录中包含了4个文件:README.txt、COPYRIGHT.html、local_policy.jar和US_export_policy.jar。其中包含的两个jar文件正是此次配置中用到的文件。
我们可以查看上述README.txt文件,你需要在JDK的JRE环境中,或者是JRE环境中配置上述两个jar文件。
切换到%JDK_Home%\jre\lib\security目录下,对应覆盖local_policy.jar和US_export_policy.jar两个文件。同时,你可能有必要在%JRE_Home%\lib\security目录下,也需要对应覆盖这两个文件。
配置权限文件的最终目的是为了使应用在运行环境中获得相应的权限,可以加强应用的安全性。通常,我们在应用服务器上安装的是JRE,而不是JDK。因此,这就很有必要在应用服务器的%JRE_Home%\lib\security目录下,对应覆盖这两个权限文件。很多开发人员往往忽略了这一点,导致事故发生。
DES对称加密算法
由于同时需要用两种方式(Object对象序列化和字节)保存密钥,因此很多人会弄错,只保存了一种形式,而在解密过程中使用这一种形式的密钥去解密,因此导致了java.security.InvalidKeyException:Invalid key length。加密的过程需要对象形式的密钥,解密的过程需要字节方式的密钥。
1.生成并保存密钥
1)获取密钥生成器
KeyGeneratorkg=KeyGenerator.getInstance("DESede");
方法getInstance()的参数为字符串类型,指定加密算法的名称。可以是
20
“Blowfish”、“DES”、“DESede”、“HmacMD5”或“HmacSHA1”等。这些算法都可以实现
加密,这里我们不关心这些算法的细节,只要知道其使用上的特点即可。其中“DES”
是目前最常用的对称加密算法,但安全性较差。针对DES安全性的改进产生了能满足当
前安全需要的TripleDES算法,即“DESede”。“Blowfish”的密钥长度可达448 位,安
全性很好。“AES”是一种替代DES 算法的新算法,可提供很好的安全性。
2)初始化密钥生成器
kg.init(168);
分析:该步骤一般指定密钥的长度。如果该步骤省略的话,会根据算法自动使用默
认的密钥长度。指定长度时,若第一步密钥生成器使用的是“DES”算法,则密钥长度
必须是56 位;若是“DESede”,则可以是112 或168 位,其中112 位有效;若是“AES”,
可以是128,192 或256位;若是“Blowfish”,则可以是32 至448 之间可以被8 整除
的数;“HmacMD5”和“HmacSHA1”默认的密钥长度都是64 个字节。
3)生成密钥
SecretKey k=kg.generateKey( );
分析:使用第一步获得的KeyGenerator类型的对象中generateKey()方法可以获
得密钥。其类型为SecretKey类型,可用于以后的加密和解密。
4)通过对象序列化方式将密钥保存在文件中
FileOutputStream f=new FileOutputStream("keyObject.dat");
ObjectOutputStream b=new ObjectOutputStream(f);
b.writeObject(k);
5)字节方式保存密钥
byte[ ]kb=k.getEncoded( );
FileOutputStreamf2=new FileOutputStream("keyByte.dat");
f2.write(kb);
2.加密、解密
1)加密时需要Object类型的密钥
(1)从文件中获取密钥
FileInputStream f=new FileInputStream("key1.dat");
ObjectInputStream b=new ObjectInputStream(f);
Key k=(Key)b.readObject( );
(2)创建密码器(Cipher对象)
Cipher cp=Cipher.getInstance("DESede");
(3)初始化密码器
cp.init(Cipher.ENCRYPT_MODE, k);
(4)获取等待加密的明文
String s="Hello World!";
byte ptext[]=s.getBytes("UTF8");
(5)执行加密
byte ctext[]=cp.doFinal(ptext);
2)解密时需要字节方式的密钥
(1)获取密文
FileInputStream f=new FileInputStream("SEnc.dat");
int num=f.available();
25
byte[ ] ctext=new byte[num];
f.read(ctext);
(2)获取密钥
FileInputStream f2=new FileInputStream("keykb1.dat");
int num2=f2.available();
byte[ ] keykb=new byte[num2];
f2.read(keykb);
SecretKeySpec k=new SecretKeySpec(keykb,"DESede");
(3)创建密码器(Cipher对象)
Cipher cp=Cipher.getInstance("DESede");
分析:该步骤同2.3.1 小节的第2 步。
(4)初始化密码器
cp.init(Cipher.DECRYPT_MODE, k);
(5)执行解密
byte []ptext=cp.doFinal(ctext);