(
史帝芬
, idealist@gcn.net.tw)
1.
密码学简介
–
加密与解密
加密是一个将欲加密的资料用一些数学运算转成一团令人看不懂的东西的过程
;
解密则是将加密文转换回原始文字的过程。这个过程中,扮演原始文字与加密文字之间转换的数学算法称为
Cipher
。
现代的
Cipher
多半会用
Key
来加密与解密资料。所谓
Key
是指一个机密值,我们可将它视为一通行密码。加密文字必需使用对映的
Key
才能解密为原始文字。
A.
对称型
Cipher
对称型
Cipher
在传送端与接收端所用的
Key
是一样的,
对称型
Cipher
又叫
Private Key Cipher
,因为
Key
的值只有传送端和接收端知道。如果有第三者知道了
Private Key
值,也就能解开加密的资料。
B.
非对称型
Cipher
非对称型的
Cipher
又叫
Public Key Cipher
,
Cipher
除了
Private Key
外,还会引进一可以随意散发的
Public Key
。被
Public Key
加密的资料只有相对映的
Private Key
可以解开,同样的被
Private Key
加密的资料也只有相对映的
Public Key
可以解开。
C.
讯息摘要
(Message Digest)
讯息摘要是从一组输入资料计算所得的一个特别数字,其原理运作就如
hash function
一般。在密码学的运用里,一般是用来验证资料是否被窜改。
2. JCE
下载
因为美国法规的限制,
Sun
在
JDK
里只提供了少数的加密方法,其余大部份则只在
SunJCE
里提供,而且
SunJCE
的
API
限制只有美国、加拿大地区可以下载。表
1
为
Sun
及
SunJCE
分别支持的加密算法。
|
名称
|
型别
|
Sun
|
MD5
|
讯息摘要
|
SHA-1
|
讯息摘要
|
DSA
|
签章
|
SunJCE
|
HmacMD5
|
MAC
|
HmacSHA1
|
MAC
|
DES
|
对称型
Cipher
|
DESede
|
非对称型
Cipher
|
PBEWithMD5AndDES
|
对称型
Cipher
|
DH
|
Key
的交换
|
表
1 Sun
及
SunJCE
支持的加密算法
虽然美国法规有这样的限定,但是在美国境外也已经有厂商实作出
JCE
,并且可以在网络上直接下载,表
2
就是下载网址的列表。
套件
|
网址
|
免费
|
JCE
|
http://java.sun.com/products/jdk/1.2/jce/
|
是
|
Cryptix
|
http://www.cryptix.org/
|
是
|
IAIK
|
http://wwwjce.iaik.tu-graz.ac.at/
|
否
|
表
2 JCE
软件下载网址
3. JCE
安装
-
解压缩到
JDK
目录下
-
Set ClassPath= C:\JDK\bin\cryptix-jce-api.jar;C:\JDK\bin\cryptix-jce-compat.jar;C:\JDK\bin\cryptix-jce-provider.jar …
-
在
JDK/lib/security/java.security
中加入
security.provider.1=sun.security.provider.Sun (
原来就有的
)
security.provider.2=cryptix.jce.provider.Cryptix (
加入
)
4.
程序范例
在举例之前,我先完成一个公用类别,用来将字符串转成十六进制表示法。
public class Msg {
public static String toHexString(byte[] b) {
StringBuffer hexString = new StringBuffer();
String plainText;
for (int i = 0; i < b.length; i++) {
plainText = Integer.toHexString(0xFF & b[i]);
if (plainText.length() < 2) {
plainText = "0" + plainText;
}
hexString.append(plainText);
}
return hexString.toString();
}
}
5.
讯息摘要
(message digest,
以
SHA1
为例
)
产生讯息摘要的步骤
:
-
呼叫
getInstance
取得
MessageDigest
实体
-
呼叫
update
将资料喂给
MessageDigest
-
呼叫
digest
产生讯息摘要
import java.security.*;
public class SHA extends Object {
public static void main(String[] args) throws Exception
{
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(args[0].getBytes());
byte[] digest = md.digest();
System.out.println(Msg.toHexString(digest));
}
}
ps.
比较两个讯息摘要是否相同时,可呼叫
isEqual
。
6.
讯息认证码
(MAC,
以
HmacSHA1
为例
)
讯息认证码只是在产生讯息摘要的过程里,加进一把
key
做为保护,目的是使讯息摘要更难被破解。
产生讯息认证码的步骤
:
-
利用密码产生一把
key
-
呼叫
getInstance
取得
Mac
实体
-
呼叫
init
,初始化
Mac
-
呼叫
update
喂资料给
Mac
-
呼叫
doFinal
产生讯息认证码
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class MacSHA {
public static void main(String[] args)
{
SecureRandom sr = new SecureRandom();
byte[] keyBytes = new byte[20];
sr.nextBytes(keyBytes);
SecretKey key = new SecretKeySpec(keyBytes, "HmacSHA");
try {
Mac m = Mac.getInstance("HmacSHA");
m.init(key);
m.update(args[0].getBytes());
byte[] mac = m.doFinal();
System.out.println(Msg.toHexString(mac));
}
catch (Exception e) {
System.out.println("Exception!!");
}
}
}
7.
加密与解密
(
以
DES
为例
)
这里举的加密
/
解密是属对称型
Cipher;
在金融交易里,常用对称型
Cipher
来加
/
解密资料。
加密
/
解密的步骤
:
-
利用密码产生一把
key
-
呼叫
getInstance
产生一个
Cipher
对象
-
呼叫
init
设定为加密或解密
-
加密
/
解密
import java.io.*;
import java.security.*;
import javax.crypto.*;
public class PwdDES {
public static final int kBufferSize = 8192;
public static void main(String[] args) throws Exception {
if (args.length < 4) {
System.out.println("Usage: java PwdDES -e|-d passwd inputfile outputfile");
return;
}
//Get or create key.
Key key;
KeyGenerator generator = KeyGenerator.getInstance("DES");
generator.init(new SecureRandom(args[1].getBytes()));
key = generator.generateKey();
//Get a cipher object
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS#5");
//Encrypt or decrypt
if (args[0].indexOf("e") != -1)
cipher.init(Cipher.ENCRYPT_MODE, key);
else
cipher.init(Cipher.DECRYPT_MODE, key);
FileInputStream in = new FileInputStream(args[2]);
FileOutputStream fileOut = new FileOutputStream(args[3]);
CipherOutputStream out = new CipherOutputStream(fileOut, cipher);
byte[] buffer = new byte[kBufferSize];
int length;
while ((length = in.read(buffer)) != -1)
out.write(buffer, 0, length);
in.close();
out.close();
}
}
8.
产生签章与认证
(
以
DSA
为例
)
数字签章常用在网络上做个人身份确认。
产生签章的步骤
:
-
呼叫
getInstance
取得一个
Signature
实体
-
呼叫
initSign
初始化
Signature
-
呼叫
sign
产生签章
认证的步骤
:
-
呼叫
getInstance
取得一个
Signature
实体
-
呼叫
initVerify
初始化
Signature
-
呼叫
verify
认证
Sample1:
产生
Private/Public Key
import java.security.*;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.io.*;
public class KeyPair1 {
public static void main(String[] args)
{
try {
KeyPairGenerator genKeyPair = KeyPairGenerator.getInstance("DSA");
genKeyPair.initialize(1024, new SecureRandom());
KeyPair kpKey = genKeyPair.genKeyPair();
PrivateKey prKey = kpKey.getPrivate();
PublicKey puKey = kpKey.getPublic();
ObjectOutputStream osPrivate = new ObjectOutputStream(new FileOutputStream("D:\\Private.Key"));
ObjectOutputStream osPublic = new ObjectOutputStream(new FileOutputStream("D:\\Public.Key"));
osPrivate.writeObject(prKey);
osPublic.writeObject(puKey);
osPrivate.close();
osPublic.close();
}
catch (Exception e) {
System.out.println("Error");
}
}
}
Sample2:
产生签章与认证
import java.io.*;
import java.security.*;
import java.security.Signature;
import java.security.cert.*;
public class GenSign {
public static void main(String[] args) throws Exception {
String options = args[0];
String messagefile = args[1];
String signaturefile = args[2];
Signature signature = Signature.getInstance("DSA");
if (options.indexOf("s") != -1) {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("D:\\Private.key"));
PrivateKey priKey = (PrivateKey) is.readObject();
signature.initSign(priKey);
is.close();
}
else {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("D:\\Public.key"));
PublicKey pubKey = (PublicKey) is.readObject();
signature.initVerify(pubKey);
is.close();
}
FileInputStream in = new FileInputStream(messagefile);
byte[] buffer = new byte[8192];
int length;
while ((length = in.read(buffer))!= -1)
signature.update(buffer, 0, length);
in.close();
if (options.indexOf("s") != -1) {
FileOutputStream out = new FileOutputStream(signaturefile);
byte[] raw = signature.sign();
out.write(raw);
out.close();
}
else {
FileInputStream sigIn = new FileInputStream(signaturefile);
byte[] raw = new byte[sigIn.available()];
sigIn.read(raw);
sigIn.close();
if (signature.verify(raw))
System.out.println("The signature is good.");
else
System.out.println("The signature is bad.");
}
}
}